use std::{io::stdin, thread::{JoinHandle, spawn, sleep}, str::Split, time::Duration, sync::{Arc, atomic::AtomicBool}};

use crate::{board::{Board, io::IO, color::Color}, moves::{Move, MoveKind}};

use super::Grossmeister;

const NAME: &str = "Chessnost";
const AUTHOR: &str = "Eugene Sokolov";

impl Grossmeister {
    pub fn start(&mut self) {
        let mut search_handle: Option<JoinHandle<Self>> = None;

        loop {
            let mut cmd = String::new();
            stdin().read_line(&mut cmd).unwrap();
            let mut tokens = cmd.trim().split(' ');

            if let Some(token) = tokens.next() {
                match token {
                    "uci" => {
                        println!("id name {}", NAME);
                        println!("id author {}", AUTHOR);
                        println!("uciok");
                    }
                    "debug" => {
                        if let Some(token) = tokens.next() {
                            match token {
                                "on" => self.debug = true,
                                "off" => self.debug = false,
                                _ => {
                                    panic!("Wrong option to debug: {}. CMD: {}", token, cmd);
                                },
                            }
                        };
                    }
                    "isready" => {
                        println!("readyok");
                    }
                    "setoption" => {
                        todo!()
                    }
                    "ucinewgame" => {
                        *self = Self::default()
                    }
                    "position" => {
                        if let Some(token) = tokens.next() {
                            match token  {
                                "startpos" => {
                                    self.board = Board::new();

                                    if let Some(token) = tokens.next() {
                                        if token == "moves" {
                                            for token in tokens.by_ref() {
                                                let input_move = Move::from_notation(token.chars());
                                                let moves = self.board.generate_pseudolegal_moves();
                                                if let Some(mov) = moves
                                                    .iter()
                                                    .find(|m| {
                                                        let promo_matches = match input_move.kind {
                                                            MoveKind::Promotion(piece) => match m.kind {
                                                                MoveKind::Promotion(another_piece) => piece.without_color() == another_piece.without_color(),
                                                                _ => false
                                                            },
                                                            _ => true,
                                                        };
                                                        promo_matches && m.source == input_move.source && m.target == input_move.target
                                                    }) {
                                                        self.board.make_move(*mov);
                                                    } else {
                                                        panic!("Illegal move: {}", input_move);
                                                    }
                                            }
                                        } else {
                                            panic!("Unexpected token: {}. CMD: {}", token, cmd);
                                        }
                                    }
                                }
                                "fen" => {
                                    // TODO: handle "moves" command after "fen"?
                                    let index = "position fen".len() + 1;
                                    if let Some(fen) = cmd.get(index..) {
                                        self.board = Board::from_FEN(fen.to_string());
                                    };
                                }
                                _ => {}
                            }
                        } else {
                            todo!("Decide what to do when position is now provided")
                        }
                    }
                    "go" => {
                        // Before we go, let's join to the latest search
                        if let Some(hand) = search_handle.take() {
                            let better_self = hand.join().unwrap();
                            self.transposition_table = better_self.transposition_table;
                        }

                        search_handle = Some(self.parse_go(tokens));
                    }
                    "stop" => {
                        self.should_halt.store(true, std::sync::atomic::Ordering::Relaxed);
                    }
                    "ponderhit" => todo!(),
                    "quit" => break,
                    _ => {},
                }
            }
        }
    }

    fn parse_go(&mut self, mut tokens: Split<char>) -> JoinHandle<Self> {
        if let Some(token) = tokens.next() {
            match token {
                "searchmoves" => todo!(),
                "ponder" => todo!(),
                "wtime" => {
                    if let Some(time) = tokens.next() {
                        let time: u64 = time.parse().unwrap();
                        self.board.clock.time[Color::White as usize] = Duration::from_millis(time);
                    }
                }
                "btime" => {
                    if let Some(time) = tokens.next() {
                        let time: u64 = time.parse().unwrap();
                        self.board.clock.time[Color::Black as usize] = Duration::from_millis(time);
                    }
                }
                "winc" => {
                    if let Some(time) = tokens.next() {
                        let time: u64 = time.parse().unwrap();
                        self.board.clock.increment[Color::White as usize] = Duration::from_millis(time);
                    }
                }
                "binc" => {
                    if let Some(time) = tokens.next() {
                        let time: u64 = time.parse().unwrap();
                        self.board.clock.increment[Color::Black as usize] = Duration::from_millis(time);
                    }
                }
                "movestogo" => {}
                "depth" => {
                    if let Some(depth) = tokens.next() {
                        let depth: u8 = depth.parse().unwrap();
                        return self.create_search_thread(depth, Duration::MAX);
                    }
                },
                "nodes" => todo!(),
                "mate" => todo!(),
                "movetime" => {
                    if let Some(time) = tokens.next() {
                        let time: u64 = time.parse().unwrap();
                        let duration = Duration::from_millis(time);

                        return self.create_search_thread(u8::MAX, duration)
                    }
                },
                "infinite" => {
                    return self.create_search_thread(u8::MAX, Duration::MAX);
                },
                _ => {}
            }
            return self.parse_go(tokens)
        }

        let color = self.board.color();
        let duration = (self.board.clock.time[color as usize] + self.board.clock.increment[color as usize]) / 20;

        self.create_search_thread(u8::MAX, duration)
    }

    fn create_search_thread(&self, depth: u8, duration: Duration) -> JoinHandle<Self> {
        let ordering = std::sync::atomic::Ordering::Relaxed;
        let should_halt = self.should_halt.clone();
        // Make sure we don't halt right away
        should_halt.store(false, std::sync::atomic::Ordering::Relaxed);

        let should_terminate = Arc::new(AtomicBool::new(true));

        if duration < Duration::MAX {
            // Create a thread that will terminate search when the duration is expired
            let should_terminate = should_terminate.clone();
            spawn(move || {
                println!("info string terminating search in {:?}", duration);
                sleep(duration);
                if should_terminate.load(ordering) {
                    should_halt.store(true, ordering);
                }
            });
        }

        // Clone current self and move it into thread to analyze a position
        let mut gm = self.clone();
        spawn(move || {
            gm.iterative_deepening(depth);
            should_terminate.store(false, ordering); // No need to terminate search anymore
            gm // Return better version of Self
        })
    }
}