/*
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>

This file is part of On the Grid.

On the Grid is free software: you can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.

On the Grid is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with On the Grid. If not, see <https://www.gnu.org/licenses/>.
*/

// Currenty my game review is able to show the current game and its tree. Now, I want to start
// tracking where in the tree that I am. This should be a combination of the abstract Tree and the
// gameTree. Chances are, if I just keep track of where I am in the abstract tree, I can find the
// relevant node in the game tree and then reproduce the line to get to that node.
//
// Moving through the game review tree shouldn't require a full invocatian. This object, and most
// other view models, should be exported to the UI.

use crate::{types::SizeNode, DepthTree, Goban};
use nary_tree::{NodeId, NodeRef};
use sgf::{GameRecord, Player};
use std::sync::{Arc, RwLock};

struct GameReviewViewModelPrivate {
    // This is the ID of the current position in the game. The ID is specific to the GameRecord,
    // not the ReviewTree.
    current_position: Option<NodeId>,
    game: GameRecord,
    review_tree: DepthTree,
}

#[derive(Clone)]
pub struct GameReviewViewModel {
    inner: Arc<RwLock<GameReviewViewModelPrivate>>,
}

impl GameReviewViewModel {
    pub fn new(game: GameRecord) -> Self {
        let (review_tree, current_position) = if !game.trees.is_empty() {
            let review_tree = DepthTree::from(&game.trees[0]);
            let current_position = game.mainline().unwrap().last().map(|nr| nr.node_id());
            (review_tree, current_position)
        } else {
            (DepthTree::default(), None)
        };

        Self {
            inner: Arc::new(RwLock::new(GameReviewViewModelPrivate {
                current_position,
                game,
                review_tree,
            })),
        }
    }

    pub fn black_player(&self) -> Player {
        self.inner.read().unwrap().game.black_player.clone()
    }

    pub fn white_player(&self) -> Player {
        self.inner.read().unwrap().game.white_player.clone()
    }

    pub fn game_view(&self) -> Goban {
        let inner = self.inner.read().unwrap();

        let mut path: Vec<NodeId> = vec![];
        let mut current_id = inner.current_position;
        while current_id.is_some() {
            let current = current_id.unwrap();
            path.push(current);
            current_id = inner.game.trees[0]
                .get(current)
                .unwrap()
                .parent()
                .map(|parent| parent.node_id());
        }

        path.reverse();
        Goban::default()
            .apply_moves(
                path.into_iter()
                    .map(|node_id| inner.game.trees[0].get(node_id).unwrap().data()),
            )
            .unwrap()

        /*
            if let Some(start) = inner.current_position {
                let mut current_id = start;
                let mut path = vec![current_id.clone()];

                while let
                /*
                let mut current_node = inner.game.trees[0].get(current_position).unwrap();
                let mut path = vec![current_node.data()];
                while let Some(parent) = current_node.parent() {
                    path.push(parent.data());
                    current_node = parent;
                }
                */

                path.reverse();
                Goban::default().apply_moves(path).unwrap()
            } else {
                Goban::default()
            }
        */
    }

    pub fn map_tree<F>(&self, f: F)
    where
        F: Fn(NodeRef<'_, SizeNode>, Option<NodeId>),
    {
        let inner = self.inner.read().unwrap();

        for node in inner.review_tree.bfs_iter() {
            f(node, inner.current_position);
        }
    }

    pub fn tree_max_depth(&self) -> usize {
        self.inner.read().unwrap().review_tree.max_depth()
    }

    // When moving forward on the tree, I grab the first child by default. I can then just advance
    // the board state by applying the child.
    pub fn next_move(&self) {
        let mut inner = self.inner.write().unwrap();
        let current_position = inner.current_position.clone();
        match current_position {
            Some(current_position) => {
                let current_id = current_position.clone();
                let node = inner.game.trees[0].get(current_id).unwrap();
                if let Some(next_id) = node.first_child().map(|child| child.node_id()) {
                    inner.current_position = Some(next_id);
                }
            }
            None => {
                inner.current_position = inner.game.trees[0].root().map(|node| node.node_id());
            }
        }
    }

    // When moving backwards, I jump up to the parent. I'll then rebuild the board state from the
    // root.
    pub fn previous_move(&mut self) {
        let mut inner = self.inner.write().unwrap();
        if let Some(current_position) = inner.current_position {
            let current_node = inner.game.trees[0]
                .get(current_position)
                .expect("current_position should always correspond to a node in the tree");
            if let Some(parent_node) = current_node.parent() {
                inner.current_position = Some(parent_node.node_id());
            }
        }
    }

    pub fn next_variant(&self) {
        println!("move to the next variant amongst the options available");
    }

    pub fn previous_variant(&self) {
        println!("move to the previous variant amongst the options available");
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::{Color, Coordinate};
    use std::path::Path;

    fn with_game_record<F>(test: F)
    where
        F: FnOnce(GameReviewViewModel),
    {
        let records = sgf::parse_sgf_file(&Path::new("../../sgf/test_data/branch_test.sgf"))
            .expect("to successfully load the test file");
        let record = records[0]
            .as_ref()
            .expect("to have successfully loaded the test record");

        let view_model = GameReviewViewModel::new(record.clone());

        test(view_model);
    }

    #[test]
    fn it_generates_a_mainline_board() {
        with_game_record(|view| {
            let goban = view.game_view();

            for row in 0..18 {
                for column in 0..18 {
                    if row == 3 && column == 3 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::White));
                    } else if row == 15 && column == 3 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::White));
                    } else if row == 3 && column == 15 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
                    } else if row == 15 && column == 14 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
                    } else {
                        assert_eq!(
                            goban.stone(&Coordinate { row, column }),
                            None,
                            "{} {}",
                            row,
                            column
                        );
                    }
                }
            }
        });
    }

    #[test]
    fn it_moves_to_the_previous_mainline_move() {
        with_game_record(|mut view| {
            view.previous_move();
            let goban = view.game_view();

            for row in 0..18 {
                for column in 0..18 {
                    if row == 3 && column == 3 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::White));
                    } else if row == 3 && column == 15 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
                    } else if row == 15 && column == 14 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
                    } else {
                        assert_eq!(
                            goban.stone(&Coordinate { row, column }),
                            None,
                            "{} {}",
                            row,
                            column
                        );
                    }
                }
            }
        });
    }

    #[test]
    fn it_moves_to_the_next_node() {
        with_game_record(|mut view| {
            view.previous_move();
            view.next_move();
            let goban = view.game_view();

            for row in 0..18 {
                for column in 0..18 {
                    if row == 3 && column == 3 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::White));
                    } else if row == 15 && column == 3 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::White));
                    } else if row == 3 && column == 15 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
                    } else if row == 15 && column == 14 {
                        assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
                    } else {
                        assert_eq!(
                            goban.stone(&Coordinate { row, column }),
                            None,
                            "{} {}",
                            row,
                            column
                        );
                    }
                }
            }
        });
    }
}
