pub mod kmeanspp;
pub mod ehs_cluster;
pub mod paemd_cluster;
pub mod krwemd_cluster;
pub mod krwemd_plus_cluster;
pub mod krwemd_clusterpp;
pub use {kmeanspp::*, ehs_cluster::*, paemd_cluster::*, krwemd_cluster::*, krwemd_plus_cluster::*};
// pub use {kmeanspp::*, ehs_cluster::*, paemd_cluster::*, krwemd_cluster::*, krwemd_plus_cluster::*, krwemd_clusterpp::*};

use std::collections::HashSet;
use rocksdb::{ColumnFamilyDescriptor, DBCommon, IteratorMode, Options, SingleThreaded, DB, WriteBatch};
use rayon::iter::{IntoParallelRefIterator, IntoParallelIterator, IndexedParallelIterator, ParallelIterator, IntoParallelRefMutIterator};
use crate::game::component::{self, *};

use std::fs::{self, File};
use std::io::{Write, BufWriter};
use std::path::Path;
use num::ToPrimitive;
use serde::{Serialize, Deserialize};

#[derive(Clone)]
pub enum AbstractAlgorithmStreet{
    Isomorphism{recall_from: usize},
    Krwi{recall_from: usize},
    Kroi{recall_from: usize},
    KrwEmd{recall_from: usize, st_weights: Vec<f64>, centroid_size: usize, train_iteration: usize},
    PaEmd{centroid_size: usize, train_iteration: usize},
    Ehs{centroid_size: usize, train_iteration: usize},
}

fn load_isomorphism_abstr<T>(street: usize, recall_from: usize) -> (Vec<usize>, usize)
    where T: Singleton + Hand + WaughTrait + ShowdownRanker + 'static,
{
    assert!(street>=recall_from);
    let krisosize = T::instance().hand_isomorphism_size_street(street, recall_from);
    let kriso2bucket = (0..krisosize).collect::<Vec<usize>>();
    (kriso2bucket, krisosize)
}

// fn load_isomorphism_abstr<T>(street: usize, recall_from: usize) -> (Vec<usize>, usize)
//     where T: Singleton + Hand + WaughTrait + ShowdownRanker + 'static,
// {
//     assert!(street>=recall_from);
//     if recall_from == 0 {
//         let prisosize = T::instance().hand_isomorphism_size_street(street, 0);
//         let priso2bucket = (0..prisosize).collect::<Vec<usize>>();
//         (priso2bucket, prisosize)
//     }
//     else {
//         let prisosize = T::instance().hand_isomorphism_size_street(street, 0);
//         let krisosize = T::instance().hand_isomorphism_size_street(street, recall_from);
//         let priso2bucket = (0..prisosize)
//             .into_par_iter()
//             .map(|priso|{
//                 let mut hand: Vec<u8> = vec![0;T::HAND_LEN_STREET[street] as usize];
//                 T::instance().hand_unindexify(priso, street, 0, hand.as_mut());
//                 let kriso = T::instance().hand_indexify(hand.as_ref(), street, recall_from);
//                 kriso
//             })
//             .collect::<Vec<usize>>();
//         (priso2bucket, krisosize)
//     }
//     (kriso2bucket, krisosize)
// }

fn load_krxi_abstr<T>(street: usize, recall_from: usize, x_winning_potential: bool) -> (Vec<usize>, usize)
    where T: Singleton + Hand + WaughTrait + ShowdownRanker + 'static,
{
    assert!(street>=recall_from);
    // 确定数据库、列族，以及打开数据库
    let path = std::format!("data/{}", T::GAME_NAME);
    let options = {
        let mut options = Options::default();
        options.create_if_missing(false);
        options
    };
    let winning_potential = if x_winning_potential {
        "winning"
    } else {
        "potential"
    };
    let kriso2distid_cf = if street == recall_from {
        format!(
            "{}_nriso_{}_to_{}_distribution_id",
            T::GAME_NAME,
            street + 1,
            winning_potential
        )
    } else {
        format!(
            "{}_priso_{}_from_{}_to_{}_trace_distribution_id",
            T::GAME_NAME,
            street + 1,
            recall_from + 1,
            winning_potential
        )
    };

    let cf_names = vec!["default", &kriso2distid_cf];
    let cf_descriptors: Vec<_> = cf_names
        .iter()
        .map(|cf_name| {
            let mut cf_opt = Options::default();
            cf_opt.create_if_missing(false);
            ColumnFamilyDescriptor::new(cf_name.clone(), cf_opt)
        })
        .collect();
    let db = DBCommon::<SingleThreaded,_>::open_cf_descriptors_read_only(&options, &path , cf_descriptors, false).expect(&format!("打不开这个数据库{}的列族{}", path, kriso2distid_cf));
    let cf_handle = db.cf_handle(&kriso2distid_cf).expect(&format!("没有这个列族:{}", kriso2distid_cf));

    // 读street中的最后一位数据，代表的是distsize
    let isosize = T::instance().hand_isomorphism_size_street(street, recall_from);
    let distsize = db.get_cf(&cf_handle, (isosize as u32).to_be_bytes()).expect(&format!("isosize/key:{}在{}街的值为None", isosize, street)).unwrap();
    let distsize = u32::from_be_bytes((*distsize).try_into().unwrap()) as usize;
    
    // 把所有distid读出来，并且校验（校验的逻辑是最后一位是否是distsize，把所有数据去重之后长度是否为distsize）
    let dbcf_iter = db.iterator_cf(cf_handle, IteratorMode::Start);
    let mut kriso2distid = vec![distsize+1; isosize+1];
    for item in dbcf_iter {
        let (keybytes, valuebytes) = item.unwrap();
        assert!(keybytes.len() == 4 && valuebytes.len() == 4);
        let iso = u32::from_be_bytes((*keybytes).try_into().unwrap()) as usize;
        let distid = u32::from_be_bytes((*valuebytes).try_into().unwrap()) as usize;
        kriso2distid[iso] = distid;
    }
    assert_eq!(kriso2distid.pop().unwrap(), distsize);
    let unique: HashSet<_> = kriso2distid.iter().cloned().collect();
    assert_eq!(unique.len(), distsize);

    // 返回结果
    (kriso2distid, distsize)
}

// fn load_krxi_abstr<T>(street: usize, recall_from: usize, x_winning_potential: bool) -> (Vec<usize>, usize)
//     where T: Singleton + Hand + WaughTrait + ShowdownRanker + 'static,
// {
//     assert!(street>=recall_from);
//     // 确定数据库、列族，以及打开数据库
//     let path = std::format!("data/{}", T::GAME_NAME);
//     let options = {
//         let mut options = Options::default();
//         options.create_if_missing(false);
//         options
//     };
//     let winning_potential = if x_winning_potential {
//         "winning"
//     } else {
//         "potential"
//     };
//     let kriso2distid_cf = if street == recall_from {
//         format!(
//             "{}_nriso_{}_to_{}_distribution_id",
//             T::GAME_NAME,
//             street + 1,
//             winning_potential
//         )
//     } else {
//         format!(
//             "{}_priso_{}_from_{}_to_{}_trace_distribution_id",
//             T::GAME_NAME,
//             street + 1,
//             recall_from + 1,
//             winning_potential
//         )
//     };

//     let cf_names = vec!["default", &kriso2distid_cf];
//     let cf_descriptors: Vec<_> = cf_names
//         .iter()
//         .map(|cf_name| {
//             let mut cf_opt = Options::default();
//             cf_opt.create_if_missing(false);
//             ColumnFamilyDescriptor::new(cf_name.clone(), cf_opt)
//         })
//         .collect();
//     let db = DBCommon::<SingleThreaded,_>::open_cf_descriptors_read_only(&options, &path , cf_descriptors, false).expect(&format!("打不开这个数据库{}的列族{}", path, kriso2distid_cf));
//     let cf_handle = db.cf_handle(&kriso2distid_cf).expect(&format!("没有这个列族:{}", kriso2distid_cf));

//     // 读street中的最后一位数据，代表的是distsize
//     let isosize = T::instance().hand_isomorphism_size_street(street, recall_from);
//     let distsize = db.get_cf(&cf_handle, (isosize as u32).to_be_bytes()).expect(&format!("isosize/key:{}在{}街的值为None", isosize, street)).unwrap();
//     let distsize = u32::from_be_bytes((*distsize).try_into().unwrap()) as usize;
    
//     // 把所有distid读出来，并且校验（校验的逻辑是最后一位是否是distsize，把所有数据去重之后长度是否为distsize）
//     let dbcf_iter = db.iterator_cf(cf_handle, IteratorMode::Start);
//     let mut kriso2distid = vec![distsize+1; isosize+1];
//     for item in dbcf_iter {
//         let (keybytes, valuebytes) = item.unwrap();
//         assert!(keybytes.len() == 4 && valuebytes.len() == 4);
//         let iso = u32::from_be_bytes((*keybytes).try_into().unwrap()) as usize;
//         let distid = u32::from_be_bytes((*valuebytes).try_into().unwrap()) as usize;
//         kriso2distid[iso] = distid;
//     }
//     assert_eq!(kriso2distid.pop().unwrap(), distsize);
//     let unique: HashSet<_> = kriso2distid.iter().cloned().collect();
//     assert_eq!(unique.len(), distsize);

//     // kriso -> priso
//     let priso2bucket = if recall_from == 0 {
//         kriso2distid
//     }
//     else {
//         let prisosize = T::instance().hand_isomorphism_size_street(street, 0);
//         let priso2bucket = (0..prisosize)
//             .into_par_iter()
//             .map(|priso|{
//                 let mut hand: Vec<u8> = vec![0;T::HAND_LEN_STREET[street] as usize];
//                 T::instance().hand_unindexify(priso, street, 0, hand.as_mut());
//                 let kriso = T::instance().hand_indexify(hand.as_ref(), street, recall_from);
//                 kriso2distid[kriso]
//             })
//             .collect::<Vec<usize>>();
//         priso2bucket
//     };

//     // 返回结果
//     (priso2bucket, distsize)
// }

fn save_cluster_bucket_street<T>(db: &mut DB, street: usize, recall_from: usize, kriso2bucket:&[usize], bucket_size: usize)
    where T: Singleton + Hand + WaughTrait + ShowdownRanker + 'static,
{
    assert!(street >= recall_from);
    let krisosize = T::instance().hand_isomorphism_size_street(street, recall_from);
    assert_eq!(krisosize, kriso2bucket.len(), "street: {}, recall_from: {}", street, recall_from);
    
    let cf_name = format!("street{}", street);

    if db.cf_handle(cf_name.as_str()).is_some() {
        db.drop_cf(cf_name.as_str()).unwrap();
    }
    db.create_cf(cf_name.as_str(), &Options::default()).unwrap();

    let kvcf = db.cf_handle(cf_name.as_str()).unwrap();

    let mut batch = WriteBatch::default();
    for (kriso, bucket) in kriso2bucket.iter().enumerate() {
        let kriso = kriso as u32;
        let bucket = *bucket as u32; 
        batch.put_cf(kvcf, kriso.to_be_bytes(), bucket.to_be_bytes());
    }
    batch.put_cf(kvcf, kriso2bucket.len().to_u32().unwrap().to_be_bytes(), bucket_size.to_u32().unwrap().to_be_bytes());

    db.write(batch).expect("写入失败");
}

// fn save_cluster_bucket_street<T>(db: &mut DB, street: usize, priso2bucket:&[usize], bucket_size: usize){
//     let cf_name = format!("street{}", street);

//     if db.cf_handle(cf_name.as_str()).is_some() {
//         db.drop_cf(cf_name.as_str()).unwrap();
//     }
//     db.create_cf(cf_name.as_str(), &Options::default()).unwrap();

//     let kvcf = db.cf_handle(cf_name.as_str()).unwrap();

//     let mut batch = WriteBatch::default();
//     for (priso, bucket) in priso2bucket.iter().enumerate() {
//         let priso = priso as u32;
//         let bucket = *bucket as u32; 
//         batch.put_cf(kvcf, priso.to_be_bytes(), bucket.to_be_bytes());
//     }
//     batch.put_cf(kvcf, priso2bucket.len().to_u32().unwrap().to_be_bytes(), bucket_size.to_u32().unwrap().to_be_bytes());

//     db.write(batch).expect("写入失败");
// }

pub fn save_cluster_configs_yaml(path: &Path, alg_configs: &[AbstractAlgorithmStreet]) {
    let path = path.join("alg_configs.yaml");
    let mut file = BufWriter::new(File::create(&path).expect("创建文件失败"));

    writeln!(file, "alg_configs:").expect("写入失败");
    for (street, alg) in alg_configs.iter().enumerate() {
        match alg {
            AbstractAlgorithmStreet::Isomorphism { recall_from } => {
                writeln!(file,"  - type: Isomorphism").expect("写入失败");
                writeln!(file,"    recall_from: {}", recall_from).expect("写入失败");
                writeln!(file,"").expect("写入失败");
            },
            AbstractAlgorithmStreet::Krwi { recall_from } => {
                writeln!(file,"  - type: Krwi").expect("写入失败");
                writeln!(file,"    recall_from: {}", recall_from).expect("写入失败");
                writeln!(file,"").expect("写入失败");
            },
            AbstractAlgorithmStreet::Kroi { recall_from } => {
                writeln!(file,"  - type: Kroi").expect("写入失败");
                writeln!(file,"    recall_from: {}", recall_from).expect("写入失败");
                writeln!(file,"").expect("写入失败");
            },
            AbstractAlgorithmStreet::KrwEmd { recall_from, st_weights, centroid_size, train_iteration } => {
                writeln!(file,"  - type: KrwEmd").expect("写入失败");
                writeln!(file,"    recall_from: {}", recall_from).expect("写入失败");
                writeln!(file,"    st_weight:").expect("写入失败");
                st_weights.iter().for_each (|weight| {
                writeln!(file,"      - {}", weight).expect("写入失败");
                });
                writeln!(file,"    centroid_size: {}", centroid_size).expect("写入失败");
                writeln!(file,"    train_iteration: {}", train_iteration).expect("写入失败");
                writeln!(file,"").expect("写入失败");
            },
            AbstractAlgorithmStreet::PaEmd { centroid_size, train_iteration } => {
                writeln!(file,"  - type: PaEmd").expect("写入失败");
                writeln!(file,"    recall_from: {}", street).expect("写入失败");
                writeln!(file,"    centroid_size: {}", centroid_size).expect("写入失败");
                writeln!(file,"    train_iteration: {}", train_iteration).expect("写入失败");
                writeln!(file,"").expect("写入失败");
            },
            AbstractAlgorithmStreet::Ehs { centroid_size, train_iteration } => {
                writeln!(file,"  - type: Ehs").expect("写入失败");
                writeln!(file,"    recall_from: {}", street).expect("写入失败");
                writeln!(file,"    centroid_size: {}", centroid_size).expect("写入失败");
                writeln!(file,"    train_iteration: {}", train_iteration).expect("写入失败");
                writeln!(file,"").expect("写入失败");
            },
        }
    }
}