use std::ops::Add;

use serde::{Serialize, Deserialize};
use either::Either::{self, Right, Left};

type Grid = Vec<Vec<Either<Vec<Proposal>, usize>>>;

type RequestGrid = Vec<Vec<Option<usize>>>;
type ResponseGrid = Vec<Vec<usize>>;
type Coord = (usize, usize);

#[derive(Clone)]
#[derive(Debug, Serialize, Deserialize)]
pub struct RequestPuzzle {
    grid: RequestGrid,
    line_count: usize,
    width: usize,
    height: usize,
}

#[derive(Clone, Debug)]
pub struct Puzzle {
    grid: Grid,
    line_count: usize,
    width: usize,
    height: usize,
}

#[derive(Clone)]
#[derive(Debug, Serialize, Deserialize)]
pub struct ResponsePuzzle {
    grid: ResponseGrid,
    line_count: usize,
    width: usize,
    height: usize,
}

#[derive(Clone, Debug)]
pub struct Proposal {
    code: usize,
    path: Vec<Coord>,
}

impl Proposal {

    fn equal(&self, other: &Proposal) -> bool {
        if self.code != other.code {
            false
        } else if self.path.len() != other.path.len() {
            false
        } else {
            self.path.iter().zip(other.path.iter()).all(|((x1, y1), (x2, y2))| x1 == x2 && y1 == y2)
        }
    }

}

impl Into<Puzzle> for RequestPuzzle {

    fn into(self) -> Puzzle {
        Puzzle {
            grid: self.grid.iter().map(|row| row.iter().map(|option| match option {
                Some(thing) => Right(thing.clone()),
                None => Left(vec![])
            }).collect()).collect(),
            line_count: self.line_count,
            width: self.width,
            height: self.height,

        }
    }

}

impl Puzzle {

    pub fn emoji_visualize(&self) -> String {

        let code_to_emoji = |code: &Either<Vec<Proposal>, usize>| match code {
            Left(_) => "⚪".to_string(), 
            Right(code) => match code {
                0 => "🔴".to_string(), 
                1 => "🟢".to_string(), 
                2 => "🔵".to_string(), 
                3 => "🟠".to_string(), 
                4 => "⚫".to_string(), 
                5 => "🟣".to_string(), 
                6 => "🟡".to_string(), 
                _ => "⭕".to_string(), 
            },
        };

        self.grid.iter().map(|row| row.iter().map(|col| code_to_emoji(col)).collect::<Vec<String>>().join("")).collect::<Vec<String>>().join("\n").add("\n")
    }

    fn get_neighbors(&self, coord: Coord) -> Vec<Coord> {
        let mut neighbors: Vec<Coord> = vec![];
        if coord.0 > 0 {
            neighbors.push((coord.0-1, coord.1))
        }
        if coord.0 < self.width-1 {
            neighbors.push((coord.0+1, coord.1))
        }
        if coord.1 > 0 {
            neighbors.push((coord.0, coord.1-1))
        }
        if coord.1 < self.height-1 {
            neighbors.push((coord.0, coord.1+1))
        }
        neighbors
    }

    fn add_proposals(&self, new_proposals: &Vec<Proposal>) -> Puzzle {
        Puzzle {
            grid: self.grid.iter().enumerate().map(|(y, row)| row.iter().enumerate().map(|(x, col)| {
                match col {
                    Left(proposals) => {
                        let relevant_proposals = new_proposals.iter().filter(|proposal| proposal.path.iter().any(|p| p.0 == x && p.1 == y));
                        let next_proposals = proposals.iter().chain(relevant_proposals).map(|p| p.clone()).collect::<Vec<Proposal>>();
                        Left(next_proposals)
                    },
                    Right(grounded) => Right(*grounded)
                }
            }).collect()).collect(),
            height: self.height,
            width: self.width,
            line_count: self.line_count,
        }
    }

    fn find_tail_ends_for_code(&self, code: usize) -> Vec<Coord> {
        self.grid.iter().enumerate().flat_map(|(y, row)| row.iter().enumerate().flat_map(|(x, col)| match col {
            Left(_) => vec![],
            Right(grounded) => {
                if *grounded == code {
                    vec![(x, y)]
                } else {
                    vec![]
                }
            }
        }).collect::<Vec<Coord>>()).collect()
    }

    fn find_starting_point_for_code(&self, code: usize) -> Coord {
        let tail_ends = self.find_tail_ends_for_code(code);
        tail_ends[0]
    }

    fn find_proposals_for_code(&self, proposal: Proposal) -> Vec<Proposal> {
        
        let coord = proposal.path.last().unwrap();

        let end_reached = match self.grid[coord.1][coord.0] {
            Left(_) => false,
            Right(grounded) => (proposal.path.len() > 1 || self.find_tail_ends_for_code(proposal.code).len() == 1) && grounded == proposal.code
        };

        if end_reached {
            vec![proposal]
        } else {

            let next_steps = self.get_neighbors(*coord).iter().filter(|neighbor| {
                let in_path = proposal.path.iter().find(|p| p.0 == neighbor.0 && p.1 == neighbor.1);
                if in_path.is_some() {
                    false
                } else {

                    let adjacent_in_path: Vec<&Coord> = proposal.path.iter().filter(|(px, py)| (((neighbor.0 as isize) - (*px as isize)).abs() == 1 && neighbor.1 == *py) || (((neighbor.1 as isize) - (*py as isize)).abs() == 1 && neighbor.0 == *px)).collect();

                    if adjacent_in_path.len() > 1 {
                        false
                    } else {
                        match self.grid[neighbor.1][neighbor.0] {
                            Left(_) => true,
                            Right(grounded) => grounded == proposal.code,
                        }
                    }

                }
            }).map(|&n| n).collect::<Vec<Coord>>();

            if next_steps.is_empty() {
                vec![]
            } else {
                next_steps.iter().flat_map(|&next_step| {
                    let new_path = proposal.path.iter().chain(vec![next_step].iter()).map(|&p| p).collect();

                    let new_proposal = Proposal {
                        code: proposal.code,
                        path: new_path,
                    };
    
                    let paths = self.find_proposals_for_code(new_proposal);
    
                    paths
                }).collect()
            }

        }

    }

    fn find_proposals(&self) -> Vec<Proposal> {
        (0..self.line_count).into_iter().flat_map(|code| {
            let starting_point = self.find_starting_point_for_code(code);
            let init_proposal = Proposal {
                code: code,
                path: vec![starting_point]
            };
            self.find_proposals_for_code(init_proposal)
        }).collect()
    }

    fn solidify_proposal(&self, proposal: &Proposal) -> Option<Puzzle> {
        let new_grid = self.grid.iter().map(|row| row.iter().map(|col| match col {
            Left(proposals) => {
                if proposals.iter().any(|p| proposal.equal(p)) {
                    Right(proposal.code)
                } else {
                    Left(proposals.iter()
                        .filter(|p| p.code != proposal.code && p.path.iter().find(|(px, py)| proposal.path.iter().find(|(prx, pry)| px == prx && py == pry).is_some()).is_none())
                        .map(|p| p.clone())
                        .collect::<Vec<Proposal>>()
                    )
                }
            },
            Right(ground) => Right(*ground)
        }).collect::<Vec<Either<Vec<Proposal>, usize>>>()).collect::<Vec<Vec<Either<Vec<Proposal>, usize>>>>();
        let valid_grid = new_grid.iter().all(|row| row.iter().all(|col| match col {
            Left(proposals) => !proposals.is_empty(),
            Right(_) => true
        }));

        if valid_grid {
            Some(Puzzle {
                grid: new_grid,
                line_count: self.line_count,
                height: self.height,
                width: self.width
            })
        } else {
            None
        }
    }

    fn apply_until_complete(&self) -> Vec<Puzzle> {
        let ungrounded_proposals = self.grid.iter().flat_map(|row| row.iter().flat_map(|col| match col {
            Left(proposals) => vec![proposals],
            Right(_) => vec![]
        }).collect::<Vec<&Vec<Proposal>>>()).collect::<Vec<&Vec<Proposal>>>();

        if ungrounded_proposals.len() > 0 {
            let first_ungrounded_proposals = ungrounded_proposals[0];
            first_ungrounded_proposals.iter().flat_map(|proposal| match self.solidify_proposal(proposal) {
                Some(puzzle) => puzzle.apply_until_complete(),
                None => vec![]
            }).collect()
    
            // fn try_all_proposals(puzzle: &Puzzle, proposals: &Vec<Proposal>) -> Option<Puzzle> {
            //     match proposals.split_first() {
            //         Some((head, tail)) => {
            //             match puzzle.solidify_proposal(head) {
            //                 Some(puzzle) => puzzle.apply_until_complete(),
            //                 None => try_all_proposals(puzzle, &tail.to_vec())
            //             }
            //         },
            //         None => None
            //     }
            // }
    
            // try_all_proposals(self, first_ungrounded_proposals)
        } else {
            vec![self.clone()]
        }

    }

    pub fn solve(&self) -> Option<Puzzle> {

        let proposals = self.find_proposals();
        let proposized_puzzle = self.add_proposals(&proposals);

        let all_possible_puzzles = proposized_puzzle.apply_until_complete();

        all_possible_puzzles.iter().for_each(|p| {
            println!("{}", p.emoji_visualize());
        });

        println!("{}", all_possible_puzzles.len());

        if all_possible_puzzles.len() > 0 {
            Some(all_possible_puzzles[0].clone())
        } else {
            None
        }
    }

    pub fn into_response(&self) -> Option<ResponsePuzzle> {
        if self.grid.iter().all(|row| row.iter().all(|col| col.is_right())) {
            Some(ResponsePuzzle {
                grid: self.grid.iter().map(|row| row.iter().map(|col| col.clone().unwrap_right().clone()).collect()).collect(),
                line_count: self.line_count,
                width: self.width,
                height: self.height,
            })
        } else {
            None
        }
    }

}