// #![feature(generic_const_exprs)]

use super::deal::*;
use super::hand_indexer::*;
use const_fn_assert::cfn_assert;

use crate::hand_isomorphism::{hand_indexer_free, hand_indexer_init, hand_unindex, hand_indexer_size, hand_index_last};
use crate::hand_isomorphism::{hand_indexer_t, uint_fast32_t};
use std::marker::PhantomData;
use std::mem::MaybeUninit;

const WAUGH_INDEXER_CONFIG_LEN: usize = 8;


#[cfg(not(feature = "rust_waugh"))]
pub struct WaughStruct {
    pub street_x_recall_indexers_1d: Vec<hand_indexer_t>,
}

#[cfg(not(feature = "rust_waugh"))]
pub trait WaughTrait: Deal {
    const WAUGH_INDEXER_CONFIG_LEN: usize = WAUGH_INDEXER_CONFIG_LEN;
    const INDEXERS_LEN: usize = (Self::NUM_STREET * (Self::NUM_STREET + 1) / 2) as usize;
    const INDEXER_CONFIGS_1D: &'static [[u8; WAUGH_INDEXER_CONFIG_LEN]];
    const INDEXER_STAGES_1D: &'static [i32];
    
    fn get_game_indexer<'a>(&'a self) -> &'a WaughStruct;
    
    fn get_mut_game_indexer<'a>(&'a mut self) -> &'a mut WaughStruct;
    
    fn new_game_indexer() -> WaughStruct {
        let mut street_x_recall_indexers_1d: Vec<hand_indexer_t> =
        Vec::with_capacity(Self::INDEXERS_LEN);
        for i in 0..Self::INDEXERS_LEN {
            street_x_recall_indexers_1d.push(unsafe {
                let mut indexer: MaybeUninit<hand_indexer_t> = MaybeUninit::uninit();
                unsafe {
                    hand_indexer_init(
                        Self::INDEXER_STAGES_1D[i] as uint_fast32_t,
                        Self::INDEXER_CONFIGS_1D[i].as_ptr(),
                        indexer.as_mut_ptr(),
                    );
                    indexer.assume_init()
                }
            });
        }
        
        WaughStruct {
            street_x_recall_indexers_1d,
        }
    }
    
    fn drop_game_indexer(&mut self) {
        unsafe {
            for indexer in &mut self.get_mut_game_indexer().street_x_recall_indexers_1d {
                hand_indexer_free(indexer as *mut hand_indexer_t);
            }
        }
    }
    
    fn hand_isomorphism_size_street(&self, street: usize, recall_from: usize) -> usize {
        let indexer = self.get_game_indexer()
                          .street_x_recall_indexers_1d[id_2d_to_1d(street, recall_from)];
                        let stage_sentinel = Self::INDEXER_STAGES_1D[id_2d_to_1d(street, recall_from)];
                        unsafe{
                            hand_indexer_size(&indexer, stage_sentinel as u64 - 1).try_into().unwrap()
                        }
                    }
                    
                    // hand_index_last
                    fn hand_indexify(&self, hand: &[u8], street: usize, recall_from: usize) -> usize {
        let indexer = self.get_game_indexer()
                          .street_x_recall_indexers_1d[id_2d_to_1d(street, recall_from)];
                        unsafe {
                            hand_index_last(
                                &indexer as *const hand_indexer_t,
                                hand.as_ptr(),
                            ) as usize
                        }
                    }
                    
                    // hand_unindex
                    fn hand_unindexify(&self, iso: usize, street: usize, recall_from: usize, hand: &mut [u8]) {
                        let indexer = self.get_game_indexer()
                        .street_x_recall_indexers_1d[id_2d_to_1d(street, recall_from)];
                    let stage_sentinel = Self::INDEXER_STAGES_1D[id_2d_to_1d(street, recall_from)];
        unsafe {
            hand_unindex(&indexer as *const hand_indexer_t, {stage_sentinel - 1} as uint_fast32_t, iso.try_into().unwrap(), hand.as_mut_ptr());
        }
    }
}

#[cfg(not(feature = "rust_waugh"))]
pub fn id_2d_to_1d(street: usize, recall_from: usize) -> usize {
    assert!(recall_from <= street);
    street * (street + 1) / 2 + recall_from
}

#[cfg(not(feature = "rust_waugh"))]
pub const fn get_street_indexer_configs_1d<const NUM_STREET: usize>(
    deal_hole_street: &'static [u8],
    deal_board_street: &'static [u8],
) -> [[u8; WAUGH_INDEXER_CONFIG_LEN]; NUM_STREET * (NUM_STREET + 1) / 2] {
    let mut configs = [[0 as u8; WAUGH_INDEXER_CONFIG_LEN]; NUM_STREET * (NUM_STREET + 1) / 2];
    
    let mut street = 0;
    while street < NUM_STREET {
        let mut sum_hole = 0;
        let mut sum_board = 0;
        
        let mut recall_street = 0;
        while recall_street <= street {
            let id_1d = street * (street + 1) / 2 + recall_street;

            sum_hole += deal_hole_street[recall_street];
            sum_board += deal_board_street[recall_street];

            // configs[id_1d] -> config

            //hole与board的发牌分别计数，以便后面拼接起来
            let mut temp_chole = [0 as u8; WAUGH_INDEXER_CONFIG_LEN];
            let mut temp_cboard = [0 as u8; WAUGH_INDEXER_CONFIG_LEN];
            let mut chole_len = 0;
            let mut cboard_len = 0;

            // 计数
            if sum_hole > 0 {
                temp_chole[chole_len] = sum_hole;
                chole_len += 1;
            }
            if sum_board > 0 {
                temp_cboard[cboard_len] = sum_board;
                cboard_len += 1;
            }

            let mut i = recall_street + 1;
            while i <= street {
                if deal_hole_street[i] > 0 {
                    temp_chole[chole_len] += deal_hole_street[i];
                    chole_len += 1;
                }
                if deal_board_street[i] > 0 {
                    temp_cboard[cboard_len] += deal_board_street[i];
                    cboard_len += 1;
                }
                i += 1;
            }

            // 拼接
            let mut i = 0;
            while i < chole_len {
                configs[id_1d][i] = temp_chole[i];
                i += 1;
            }
            while i < chole_len + cboard_len {
                configs[id_1d][i] = temp_cboard[i - chole_len];
                i += 1;
            }

            recall_street += 1;
        }

        street += 1;
    }
    configs
}

#[cfg(not(feature = "rust_waugh"))]
pub const fn get_street_indexer_stages_1d<const NUM_STREET: usize>(
    deal_hole_street: &'static [u8],
    deal_board_street: &'static [u8],
) -> [i32; NUM_STREET * (NUM_STREET + 1) / 2] {
    let mut stages = [0 as i32; NUM_STREET * (NUM_STREET + 1) / 2];

    let mut street = 0;
    while street < NUM_STREET {
        let mut sum_hole = 0;
        let mut sum_board = 0;

        let mut recall_street = 0;
        while recall_street <= street {
            let id_1d = street * (street + 1) / 2 + recall_street;

            sum_hole += deal_hole_street[recall_street];
            sum_board += deal_board_street[recall_street];

            // configs[id_1d] -> config

            let mut stage_len = 0;

            // 计数
            if sum_hole > 0 {
                stage_len += 1;
            }
            if sum_board > 0 {
                stage_len += 1;
            }

            let mut i = recall_street + 1;
            while i <= street {
                if deal_hole_street[i] > 0 {
                    stage_len += 1;
                }
                if deal_board_street[i] > 0 {
                    stage_len += 1;
                }
                i += 1;
            }
            cfn_assert!(stage_len <= WAUGH_INDEXER_CONFIG_LEN);
            stages[id_1d] = stage_len as i32;

            recall_street += 1;
        }

        street += 1;
    }
    stages
}

/// [end street][recall started from x street]
/// (2; 1)
/// (2, 2; 1, 3)                (4; 4)
/// (2, 2, 3; 1, 3, 1)          (4, 3; 4, 1)        (7; 5) 
/// (2, 2, 3, 4; 1, 3, 1, 1)    (4, 3, 4; 4, 1, 1)  (7, 4; 5, 1)    (11; 6) 

/// stage
/// 2
/// 4   2
/// 6   4   2
/// 8   6   4   2

/// [end street][recall started from x street]
/// (2;)
/// (2; 3)          (2; 3)
/// (2; 3, 1)       (2; 3, 1)       (2; 4)
/// (2; 3, 1, 1)    (2; 3, 1, 1)    (2; 4, 1)   (2; 5)

/// stage
/// 1
/// 2   2
/// 3   3   2
/// 4   4   3   2

#[cfg(feature = "rust_waugh")]
pub struct WaughStruct {
    pub street_x_recall_indexers_1d: Vec<HandIndexer>,
}

#[cfg(feature = "rust_waugh")]
pub trait WaughTrait: Deal {
    const INDEXERS_LEN: usize = (Self::NUM_STREET * (Self::NUM_STREET + 1) / 2) as usize;
    const SUITS: usize;
    const RANKS: usize;

    fn get_game_indexer<'a>(&'a self) -> &'a WaughStruct;

    fn get_mut_game_indexer<'a>(&'a mut self) -> &'a mut WaughStruct;

    fn new_game_indexer() -> WaughStruct {
        
        fn recalled_deal(deal_street: &[u8], street: usize, recall_from: usize) -> Vec<usize>{
            let mut deal_stage = Vec::new();
            let recall_from_sentinel = recall_from+1;
            let sum_recall_from: usize = deal_street.iter().take(recall_from_sentinel).sum::<u8>() as usize;
            deal_stage.push(sum_recall_from);
            let preserved = &deal_street[recall_from_sentinel..=street].iter().map(|x| *x as usize).collect::<Vec<_>>();
            deal_stage.extend_from_slice(preserved);
            deal_stage
        }
        
        let mut street_x_recall_indexers_1d: Vec<HandIndexer> =
            Vec::with_capacity(Self::INDEXERS_LEN);

        for recall_from in 0..Self::NUM_STREET as usize {
            let deal_hole_stage = recalled_deal(Self::DEAL_HOLE_STREET, Self::NUM_STREET as usize -1, recall_from);
            let deal_board_stage = recalled_deal(Self::DEAL_BOARD_STREET, Self::NUM_STREET as usize -1, recall_from);
            street_x_recall_indexers_1d.push(
                init_hand_indexer(Self::SUITS, Self::RANKS, deal_hole_stage, deal_board_stage)
            );
        }    

        WaughStruct {
            street_x_recall_indexers_1d,
        }
    }

    fn drop_game_indexer(&mut self) {

    }

    fn hand_isomorphism_size_street(&self, street: usize, recall_from: usize) -> usize {
        let indexer = &self.get_game_indexer()
                          .street_x_recall_indexers_1d[recall_from];
        indexer.iso_size_street(street-recall_from)
    }

    // hand_index_last
    fn hand_indexify(&self, hand: &[u8], street: usize, recall_from: usize) -> usize {
        let indexer = &self.get_game_indexer()
                          .street_x_recall_indexers_1d[recall_from];
        indexer.compute_isomorphism(&hand[..Self::HOLE_LEN_STREET[street]], &hand[Self::HOLE_LEN_STREET[street]..], street-recall_from)
    }

    // hand_unindex
    fn hand_unindexify(&self, iso: usize, street: usize, recall_from: usize, hand: &mut [u8]) {
        let indexer = &self.get_game_indexer()
                          .street_x_recall_indexers_1d[recall_from];
        let hand_spilt = indexer.reduction(iso, street-recall_from);
        let hand_spilt = hand_spilt.into_iter().flatten().collect::<Vec<u8>>();
        // assert_eq!(hand_spilt.len(), hand.len());
        hand[..hand_spilt.len()].copy_from_slice(hand_spilt.as_ref());
    }

    fn hand_index_volumn(&self, iso:usize, street: usize, recall_from: usize) -> usize {
        // reduction_class_num(&self, colex: usize, mut streets: usize) -> usize
        let indexer = &self.get_game_indexer().street_x_recall_indexers_1d[recall_from];
        indexer.reduction_class_num(iso, street-recall_from)
    }
}

#[cfg(feature = "rust_waugh")]
pub const fn get_street_indexer_configs_1d<const NUM_STREET: usize>(
    deal_hole_street: &'static [u8],
    deal_board_street: &'static [u8],
) -> [[u8; WAUGH_INDEXER_CONFIG_LEN]; NUM_STREET * (NUM_STREET + 1) / 2] {
    let mut configs = [[0 as u8; WAUGH_INDEXER_CONFIG_LEN]; NUM_STREET * (NUM_STREET + 1) / 2];
    
    let mut street = 0;
    while street < NUM_STREET {
        let mut sum_hole = 0;
        let mut sum_board = 0;
        
        let mut recall_street = 0;
        while recall_street <= street {
            let id_1d = street * (street + 1) / 2 + recall_street;

            sum_hole += deal_hole_street[recall_street];
            sum_board += deal_board_street[recall_street];

            // configs[id_1d] -> config

            //hole与board的发牌分别计数，以便后面拼接起来
            let mut temp_chole = [0 as u8; WAUGH_INDEXER_CONFIG_LEN];
            let mut temp_cboard = [0 as u8; WAUGH_INDEXER_CONFIG_LEN];
            let mut chole_len = 0;
            let mut cboard_len = 0;

            // 计数
            if sum_hole > 0 {
                temp_chole[chole_len] = sum_hole;
                chole_len += 1;
            }
            if sum_board > 0 {
                temp_cboard[cboard_len] = sum_board;
                cboard_len += 1;
            }

            let mut i = recall_street + 1;
            while i <= street {
                if deal_hole_street[i] > 0 {
                    temp_chole[chole_len] += deal_hole_street[i];
                    chole_len += 1;
                }
                if deal_board_street[i] > 0 {
                    temp_cboard[cboard_len] += deal_board_street[i];
                    cboard_len += 1;
                }
                i += 1;
            }

            // 拼接
            let mut i = 0;
            while i < chole_len {
                configs[id_1d][i] = temp_chole[i];
                i += 1;
            }
            while i < chole_len + cboard_len {
                configs[id_1d][i] = temp_cboard[i - chole_len];
                i += 1;
            }

            recall_street += 1;
        }

        street += 1;
    }
    configs
}


// [end street][recall started from x street]
// (2; 1)
// (2, 2; 1, 3)                (4; 4)
// (2, 2, 3; 1, 3, 1)          (4, 3; 4, 1)        (7; 5) 
// (2, 2, 3, 4; 1, 3, 1, 1)    (4, 3, 4; 4, 1, 1)  (7, 4; 5, 1)    (11; 6) 

// stage:= street - recall_from
// 0
// 1   0
// 2   1   0
// 3   2   1   0

// [end street][recall started from x street]
// (2;)
// (2; 3)          (2; 3)
// (2; 3, 1)       (2; 3, 1)       (2; 4)
// (2; 3, 1, 1)    (2; 3, 1, 1)    (2; 4, 1)   (2; 5)

// stage:= street - recall_from
// 0
// 1   0
// 2   1   0
// 3   2   1   0


// test
// const hole2311: [i32; 4] = [2, 0, 0, 0];
// const board2311: [i32; 4] = [0, 3, 1, 1];

// const hole22341311: [i32; 4] = [2, 2, 3, 4];
// const board22341311: [i32; 4] = [1, 3, 1, 1];

// fn print_2d<'a, T: std::fmt::Debug, const NUM_STREET : usize>(slc: &'a [T]){
//     let mut street = 0;
//     while street < NUM_STREET {

//         let mut recall_street = 0;
//         while recall_street <= street{
//             let id_1d = street * (street + 1) / 2 + recall_street;

//             print!("{:?}\t", slc[id_1d]);

//             recall_street+=1;
//         }

//         println!();
//         street += 1;
//     }
// }

// fn main(){

//     let configs2311 = get_street_indexer_configs_1d::<4>(&hole2311, &board2311);
//     let stages2311 = get_street_indexer_stages_1d::<4>(&hole2311, &board2311);

//     print_2d::<_, 4>(&configs2311);
//     print_2d::<_, 4>(&stages2311);

//     let configs22341311 = get_street_indexer_configs_1d::<4>(&hole22341311, &board22341311);
//     let stages22341311 = get_street_indexer_stages_1d::<4>(&hole22341311, &board22341311);

//     print_2d::<_, 4>(&configs22341311);
//     print_2d::<_, 4>(&stages22341311);
// }
