use std::{vec, ops::Add, time::Duration, collections::HashMap};

use rand::seq::SliceRandom;
use rand::thread_rng;
use rand::{Rng, rngs::ThreadRng};
use tokio::time::timeout;
use tokio::sync::oneshot;
use std::sync::mpsc;
use std::thread;
use serde::{Serialize, Deserialize};

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

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

fn code_to_emoji(code: usize) -> String {
    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(), 
    }
}

impl Puzzle {

    pub fn new(width: usize, height: usize, line_count: usize) -> Puzzle {
        let starting_points = vec![0; line_count]
            .iter()
            .fold(vec![], |acc: Vec<Coord>, _| {
                fn neighbors(acc: &Vec<Coord>, coord: Coord) -> Vec<Coord> {
                    acc
                        .iter()
                        .filter(|a|
                            coord.0+1 == a.0 || (coord.0 != 0 && coord.0-1 == a.0) || coord.1+1 == a.1 || (coord.1 != 0 && coord.1-1 == a.1)
                        )
                        .map(|a| a.clone())
                        .collect::<Vec<Coord>>()
                }
                fn gen_valid_coord(width: usize, height: usize, neighbors: fn(&Vec<Coord>, Coord) -> Vec<Coord>, acc: Vec<Coord>) -> Coord {
                    let mut rng = rand::thread_rng();
                    let point = (rng.gen_range(0..width), rng.gen_range(0..height));
                    let already_exists = acc.iter().find(|a| a.0 == point.0 && a.1 == point.1);
                    match already_exists {
                        None => if neighbors(&acc, point).len() >= 4 { gen_valid_coord(width, height, neighbors, acc) } else { point }
                        Some(_) => gen_valid_coord(width, height, neighbors, acc)
                    }
                }

                let acc_clone = acc.clone();
                acc.into_iter().chain(vec![gen_valid_coord(width, height, neighbors, acc_clone)]).collect()
            });
        let empty_grid: Vec<Vec<Option<usize>>> = vec![vec![None; width]; height];
        let grid = empty_grid.iter().enumerate().map(|(y, row)| row.iter().enumerate().map(|(x, col)| {
            starting_points.iter().position(|starting_point| starting_point.0 == x && starting_point.1 == y)
        }).collect::<Vec<Option<usize>>>()).collect::<Vec<Vec<Option<usize>>>>();

        Puzzle { grid, line_count, width, height }
    }

    pub async fn generate_puzzle_multithread(width: usize, height: usize, line_count: usize) -> Result<Puzzle, tokio::sync::oneshot::error::RecvError> {

        let (tx0, rx0) = oneshot::channel();
        let (tx1, rx1) = oneshot::channel();
        let (tx2, rx2) = oneshot::channel();
        let (tx3, rx3) = oneshot::channel();
        let (tx4, rx4) = oneshot::channel();
        let (tx5, rx5) = oneshot::channel();
        let (tx6, rx6) = oneshot::channel();
        let (tx7, rx7) = oneshot::channel();
        let (tx8, rx8) = oneshot::channel();
        let (tx9, rx9) = oneshot::channel();

        tokio::spawn(async move {
            tx0.send(Self::generate_puzzle(width, height, line_count))
        });
        tokio::spawn(async move {
            tx1.send(Self::generate_puzzle(width, height, line_count))
        });
        tokio::spawn(async move {
            tx2.send(Self::generate_puzzle(width, height, line_count))
        });
        tokio::spawn(async move {
            tx3.send(Self::generate_puzzle(width, height, line_count))
        });
        tokio::spawn(async move {
            tx4.send(Self::generate_puzzle(width, height, line_count))
        });
        tokio::spawn(async move {
            tx5.send(Self::generate_puzzle(width, height, line_count))
        });
        tokio::spawn(async move {
            tx6.send(Self::generate_puzzle(width, height, line_count))
        });
        tokio::spawn(async move {
            tx7.send(Self::generate_puzzle(width, height, line_count))
        });
        tokio::spawn(async move {
            tx8.send(Self::generate_puzzle(width, height, line_count))
        });
        tokio::spawn(async move {
            tx9.send(Self::generate_puzzle(width, height, line_count))
        });
        
        let result = tokio::select! {
            val = rx0 => {
                println!("rx0 completed first");
                val
            }
            val = rx1 => {
                println!("rx1 completed first");
                val
            }
            val = rx2 => {
                println!("rx2 completed first");
                val
            }
            val = rx3 => {
                println!("rx3 completed first");
                val
            }
            val = rx4 => {
                println!("rx4 completed first");
                val
            }
            val = rx5 => {
                println!("rx5 completed first");
                val
            }
            val = rx6 => {
                println!("rx6 completed first");
                val
            }
            val = rx7 => {
                println!("rx7 completed first");
                val
            }
            val = rx8 => {
                println!("rx8 completed first");
                val
            }
            val = rx9 => {
                println!("rx9 completed first");
                val
            }
        };

        result

    }

    pub fn generate_puzzle3(width: usize, height: usize, line_count: usize) -> Puzzle {
        let puzzle = Puzzle {
            grid: vec![vec![None; width]; height],
            height: height,
            width: width,
            line_count: line_count
        };

        puzzle.fill_puzzle3()
    }

    pub fn generate_puzzle2(width: usize, height: usize, line_count: usize) -> Puzzle {

        fn f(width: usize, height: usize, line_count: usize) -> Puzzle {
            let puzzle = Puzzle {
                grid: vec![vec![None; width]; height],
                height: height,
                width: width,
                line_count: line_count
            };
            match puzzle.fill_puzzle2() {
                Some(puzzle) => puzzle,
                None => f(width, height, line_count)
            }
        }

        f(width, height, line_count)
    }

    pub fn generate_puzzle(width: usize, height: usize, line_count: usize) -> Puzzle {

        fn f(width: usize, height: usize, line_count: usize) -> Puzzle {
            let puzzle = Puzzle::new(width, height, line_count);
            let puzzle_completable = puzzle.fill_puzzle();
            match puzzle_completable {
                Some(p) => p.leave_only_tail_ends(),
                None => f(width, height, line_count)
            }
        }

        f(width, height, line_count)

    }

    pub fn leave_only_tail_ends(&self) -> Puzzle {

        let binding = self.get_tail_ends();
        let tail_ends = binding.iter().flatten().collect::<Vec<&Coord>>();

        Puzzle {
            grid: self.grid.iter().enumerate().map(|(y1, row)| row.iter().enumerate().map(|(x1, code)| {
                if tail_ends.iter().any(|(x2, y2)| x1 == *x2 && y1 == *y2) {
                    code.clone()
                } else {
                    None
                }
            }).collect()).collect(),
            height: self.height,
            width: self.width,
            line_count: self.line_count,
        }
    }

    pub fn equal(&self, them: &Puzzle) -> bool {
        self.grid.iter().zip(them.grid.iter()).all(|(row1, row2)| row1.iter().zip(row2.iter()).all(|(col1, col2)| col1 == col2))
    }

    pub fn amount_of_code(&self, code: usize) -> usize {
        self.grid.iter().fold(0 as usize, |acc, row| acc + row.iter().fold(0 as usize, |acc, &col| match col {
            Some(col) => if col == code { acc + 1 } else { acc },
            None => acc
        }))
    }

    pub fn is_complete(&self) -> bool {
        self.grid.iter().all(|row| row.iter().all(|col| col.is_some()))
    }

    pub fn is_valid(&self) -> bool{
        let identical_neighbors = self.grid
            .iter()
            .enumerate()
            .map(|(y, row)| row
                .iter()
                .enumerate()
                .map(|(x, &col)| match col {
                    Some(_) => self.get_neighbors((x, y)).iter().fold(0 as usize, |acc, (nx, ny)|
                        if self.grid[*ny][*nx] == col {
                            acc + 1
                        } else {
                            acc 
                        }
                    ),
                    None => 0
                }
                )
                .collect::<Vec<usize>>()
            )
            .collect::<Vec<Vec<usize>>>();
        let no_more_than_2_identical_neighbors = identical_neighbors.iter().all(|row| row.iter().all(|&col| col <= 2));

        if !no_more_than_2_identical_neighbors {
            false
        } else {

            let no_loops = vec![0; self.line_count].iter().enumerate().all(|(code, _)| {
                let tail_ends = self.get_tail_ends_for_code(Some(code));
                self.amount_of_code(code) == 1 || tail_ends.len() == 2
            });
            
            if !no_loops {
                false
            } else {

                let tail_ends_unflattened = self.get_tail_ends();
                let tail_ends: Vec<&Coord> = tail_ends_unflattened.iter().flatten().collect::<Vec<&Coord>>();
                let open_spots: Vec<Coord> = self.grid.iter().enumerate().fold(vec![], |acc, (y, row)| {
                    let res: Vec<Coord> = row.iter().enumerate().fold(vec![], |acc, (x, code)| {
                        match code {
                            Some(_) => acc,
                            None => acc.into_iter().chain(vec![(x, y)].into_iter()).collect::<Vec<Coord>>()
                        }
                    });
                    acc.into_iter().chain(res.into_iter()).collect()
                });

                let islands: Vec<Vec<Coord>> = open_spots.iter().fold(vec![], |acc, &coord| {
                    let island_found = acc.iter().position(|island| island.iter().any(|&c| self.are_neighbors(c, coord)));
                    match island_found {
                        Some(index) => acc.iter().enumerate().map(|(i, island)| if index == i { island.clone().into_iter().chain(vec![coord.clone()].into_iter()).collect() } else { island.clone() }).collect(),
                        None => acc.into_iter().chain(vec![vec![coord.clone()]].into_iter()).collect()
                    }
                });

                let no_isolated_islands = islands.iter().all(|island| {
                    island.iter().any(|&c1| {
                        let neighbors = self.get_neighbors(c1);
                        let mut tail_end_neighbors = neighbors.iter().filter(|n| tail_ends.iter().any(|t| n.0 == t.0 && n.1 == t.1));
                        tail_end_neighbors.any(|tail_end_neighbor| {
                            let neighbor_code = self.grid[tail_end_neighbor.1][tail_end_neighbor.0];
                            neighbors.iter().all(|n| (n.0 == tail_end_neighbor.0 && n.1 == tail_end_neighbor.1) || self.grid[n.1][n.0] != neighbor_code)
                        })
                    })
                });


                return no_isolated_islands
            }
        }
    }

    pub fn update(&self, coord: Coord, code: usize) -> Puzzle {
        Puzzle {
            grid: self.grid.iter().enumerate().map(|(y, row)| row.iter().enumerate().map(|(x, &col)| {
                if coord == (x, y) {
                    Some(code)
                } else {
                    col
                }
            }).collect()).collect(),
            line_count: self.line_count,
            width: self.width,
            height: self.height
        }
    }

    fn moves_left(&self) -> bool {
        let tail_ends = self.get_tail_ends();
        if tail_ends.iter().filter(|tail_end| tail_end.len() > 0).count() == self.line_count {
            tail_ends.iter().enumerate().any(|(code, tail_ends)| {
                let possibilities = tail_ends
                    .iter()
                    .flat_map(|&t| self.get_neighbors(t))
                    .filter(|(x, y)| self.grid[*y][*x].is_none())
                    .filter(|&n| self.get_neighbors(n).iter().filter(|(n2x, n2y)| self.grid[*n2y][*n2x] == Some(code)).count() <= 1)
                    .count();
    
                possibilities > 0
            })
        } else {
            true
        }
    }

    fn hash(&self) -> String {
        self.grid.iter().map(|row| row.iter().map(|code| match code {
            Some(code) => code.to_string(),
            None => "None".to_string()
        }).collect::<Vec<String>>().join("")).collect::<Vec<String>>().join("")
    }

    fn find_random_available_spot(&self) -> Option<Coord> {
        let available_spots = self.grid.iter().enumerate().flat_map(|(y, row)| row.iter().enumerate().flat_map(|(x, col)| match col {
            Some(_) => vec![],
            None => vec![(x, y)]
        }).collect::<Vec<Coord>>()).collect::<Vec<Coord>>();

        match available_spots.choose(&mut rand::thread_rng()) {
            Some(&coord) => Some(coord),
            None => None
        }
    }

    pub fn init_puzzle1(&self, randomizer: &mut ThreadRng) -> Puzzle {
        let mut line_ids = (0..self.line_count).collect::<Vec<usize>>();
        line_ids.shuffle(randomizer);

        let new_grid = self.grid.iter().map(|row| row.iter().enumerate().map(|(x, _)| {
            Some(line_ids[x])
        }).collect()).collect();


        Puzzle{
            grid: new_grid,
            height: self.height,
            width: self.width,
            line_count: self.line_count,
        }
    }

    pub fn init_puzzle2(&self, randomizer: &mut ThreadRng) -> Puzzle {
        let mut line_ids = (0..self.line_count).collect::<Vec<usize>>();
        line_ids.shuffle(randomizer);

        let new_grid = self.grid.iter().enumerate().map(|(y, row)| row.iter().enumerate().map(|(x, col)| {
            if y <= x {
                let code = line_ids[x];
                Some(code)
            } else if x <= y {
                let code = line_ids[y];
                Some(code)
            } else {
                None
            }

        }).collect()).collect();

        Puzzle {
            grid: new_grid,
            height: self.height,
            width: self.width,
            line_count: self.line_count,
        }
    }

    pub fn fill_puzzle3(&self) -> Puzzle {

        let mut randomizer = thread_rng();

        let mut puzzle = self.init_puzzle2(&mut randomizer);
        
        let mut percentage = randomizer.gen_range(0..100);
        let mut steps = 0;
        while steps <= 5000 /*&& steps <= (percentage*10000)*/ {

            let random_code = ((percentage as f64 / 100.0) * puzzle.line_count as f64).round() as usize;
            // let random_code = steps % self.line_count;


            if puzzle.line_length(random_code) > 3 {
                let mut tail_ends = puzzle.get_tail_ends_for_code(Some(random_code));
                tail_ends.shuffle(&mut randomizer);

                let mut found = false;

                for tail_end in tail_ends.iter() {
                    let mut neighboring_tail_ends = puzzle.get_neighboring_tail_ends(*tail_end);   
                    neighboring_tail_ends.shuffle(&mut randomizer);

                    for neighboring_tail_end in neighboring_tail_ends.iter() {
                        let neighboring_tail_end_code = puzzle.grid[neighboring_tail_end.1][neighboring_tail_end.0].unwrap();
                        let possible_puzzle = puzzle.update(*tail_end, neighboring_tail_end_code);
                        if possible_puzzle.is_valid() {
                            found = true;
                            puzzle = possible_puzzle;
                            break
                        } else {
                            continue
                        }
                    }

                    if found {
                        break
                    } else {
                        continue
                    }
                }

                if found {
                    percentage = randomizer.gen_range(0..100);
                    steps = steps + 1;
                } else {
                    percentage = randomizer.gen_range(0..100);
                }
            } else {
                percentage = randomizer.gen_range(0..100);
            }
        };

        puzzle
    }

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

        fn draw_line(puzzle: Puzzle, code: usize) -> Option<Puzzle> {
            println!("Code {}:\n{}", code_to_emoji(code), puzzle.emoji_visualize());
            if !puzzle.moves_left() {
                if puzzle.is_complete() && puzzle.is_valid() {
                    Some(puzzle)
                } else {
                    None
                }
            } else {
                let odds_to_stop = (puzzle.line_count as f64) / (puzzle.width as f64 * puzzle.height as f64);
                let will_stop = rand::thread_rng().gen_range(0.0..1.0) < odds_to_stop;
                if will_stop {
                    draw_line(puzzle.clone(), (code+1)%puzzle.line_count)
                } else {
                    let tail_ends = {
                        let te = puzzle.get_tail_ends_for_code(Some(code));
                        if te.len() > 0 {
                            te
                        } else {
                            match puzzle.find_random_available_spot() {
                                Some(spot) => vec![spot],
                                None => vec![]
                            }
                        }
                    };
    
                    let possibilities = tail_ends
                        .iter()
                        .flat_map(|&t| puzzle.get_neighbors(t))
                        .filter(|(x, y)| puzzle.grid[*y][*x].is_none())
                        .filter(|&n| puzzle.get_neighbors(n).iter().filter(|(n2x, n2y)| puzzle.grid[*n2y][*n2x] == Some(code)).count() <= 1)
                        .collect::<Vec<Coord>>();
        
                    let chosen_possibility = possibilities.choose(&mut rand::thread_rng());
                    match chosen_possibility {
                        Some(chosen_possibility) => {
                            let next_puzzle = puzzle.update(*chosen_possibility, code);
                            draw_line(next_puzzle, code)
                        },
                        None => draw_line(puzzle.clone(), (code+1)%puzzle.line_count)
                    }
    
                }      
            }
        }

        let result = draw_line(self.clone(), 0);
        println!("Done");
        result

    }

    pub fn fill_puzzle(&self) -> Option<Puzzle> {
        
        fn do_fill(puzzle: Option<Puzzle>, code: usize, last_seen_puzzle: Vec<Option<Puzzle>>) -> Option<Puzzle> {
            // println!("===DO FILL FOR {}===", code_to_emoji(code));
            match puzzle {
                Some(puzzle) => {
                    // println!("Puzzle:\n{}", puzzle.emoji_visualize());
                    let last_seen_is_current = match &last_seen_puzzle[code] {
                        Some(last_seen) => puzzle.equal(last_seen),
                        None => false
                    };
                    if last_seen_is_current {
                        // println!("{}: Puzzle is same as last time. Propagating None.", code_to_emoji(code));
                        None
                    } else if puzzle.moves_left() {
                        let tail_ends = puzzle.get_tail_ends_for_code(Some(code));
                        let mut possibilities = tail_ends
                                .iter()
                                .flat_map(|&t| puzzle.get_neighbors(t))
                                .filter(|(x, y)| puzzle.grid[*y][*x].is_none())
                                .filter(|&n| puzzle.get_neighbors(n).iter().filter(|(n2x, n2y)| puzzle.grid[*n2y][*n2x] == Some(code)).count() <= 1)
                                .map(|n| Some(n))
                                .chain(vec![None])
                                .collect::<Vec<Option<Coord>>>();

                        // possibilities.shuffle(&mut thread_rng());

                        // println!("{}: Possible places to expand to: {:?}", code_to_emoji(code), possibilities);
    
                        fn try_every_possibility(puzzle: Puzzle, possibilities: Vec<Option<Coord>>, code: usize, last_seen_puzzle: Vec<Option<Puzzle>>) -> Option<Puzzle> {
                            match possibilities.split_first() {
                                Some((&head, tail)) => {
                                    let next_puzzle = match head {
                                        Some(head) => puzzle.update(head, code),
                                        None => puzzle.clone()
                                    };
                                    // println!("{}: Possible placement: {:?}:\n{}", code_to_emoji(code), head, next_puzzle.emoji_visualize());
                                    if next_puzzle.is_valid() {
                                        // println!("{}: Placement on {:?} is valid", code_to_emoji(code), head);
                                        let add_to_last_seen = last_seen_puzzle.iter().enumerate().map(|(i, p)| if i == code { Some(puzzle.clone()) } else { p.clone() }).collect::<Vec<Option<Puzzle>>>();
                                        let next_next_puzzle = do_fill(Some(next_puzzle), (code+1)%puzzle.line_count, add_to_last_seen);
                                        match next_next_puzzle {
                                            Some(next_next_puzzle) => Some(next_next_puzzle),
                                            None => {
                                                // println!("{}: Placement on {:?} seemed to cause an issue. Trying other possibilities.", code_to_emoji(code), head);
                                                try_every_possibility(puzzle, tail.to_vec(), code, last_seen_puzzle)
                                            }
                                        }
                                    } else {
                                        // println!("{}: Placement on {:?} is invalid. Trying other possibilities.", code_to_emoji(code), head);
                                        try_every_possibility(puzzle, tail.to_vec(), code, last_seen_puzzle)
                                    }
                                },
                                None => {
                                    // println!("{}: No more possibilities remaining. Propagating None.", code_to_emoji(code));
                                    None
                                }
                            }
                        }
    
                        try_every_possibility(puzzle, possibilities, code, last_seen_puzzle)
                    } else {
                        if puzzle.is_complete() && puzzle.is_valid() {
                            Some(puzzle)
                        } else {
                            None
                        }
                    }
                },
                None => None
            }
        }

        do_fill(Some(self.clone()), 0, vec![None; self.line_count])

    }

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

        let mut seen_cache: HashMap<String, bool> = HashMap::new();

        fn do_fill(puzzle: Option<Puzzle>, code: usize, seen_cache: &mut HashMap<String, bool>, last_seen_puzzle: Vec<Option<Puzzle>>) -> Option<Puzzle> {
            match puzzle {
                Some(puzzle) => {
                    let last_seen_is_current = match &last_seen_puzzle[code] {
                        Some(last_seen) => puzzle.equal(last_seen),
                        None => false
                    };
                    if last_seen_is_current {
                        println!("code {}: loop detected. Propagating None", code_to_emoji(code));
                        None
                    } else if puzzle.moves_left() {
                        let tail_ends = puzzle.get_tail_ends_for_code(Some(code));
            
                        let possibilities = tail_ends
                            .iter()
                            .flat_map(|&t| puzzle.get_neighbors(t))
                            .filter(|(x, y)| puzzle.grid[*y][*x].is_none())
                            .filter(|&n| puzzle.get_neighbors(n).iter().filter(|(n2x, n2y)| puzzle.grid[*n2y][*n2x] == Some(code)).count() <= 1)

                            .collect::<Vec<Coord>>();
            
                        fn try_every_possibility(puzzle: Puzzle, possibilities: Vec<Coord>, code: usize, seen_cache: &mut HashMap<String, bool>, last_seen_puzzle: Vec<Option<Puzzle>>) -> Option<Puzzle> {
                            match possibilities.split_first() {
                                Some((&head, tail)) => {
                                    let next_puzzle = puzzle.update(head, code);
                                    println!("Trying {}.{:?}: \n{}", code_to_emoji(code), head, next_puzzle.emoji_visualize());
                                    let already_seen = seen_cache.contains_key(&next_puzzle.hash());
                                    if already_seen {
                                        println!("code {}: already seen. Propagating None", code_to_emoji(code));
                                        None
                                    } else if next_puzzle.is_valid() {
                                        println!("{}.{:?}: valid", code_to_emoji(code), head);
                                        let next_puzzle_hash = next_puzzle.hash();
                                        seen_cache.insert(next_puzzle_hash, true);
                                        let add_to_last_seen = last_seen_puzzle.iter().enumerate().map(|(i, p)| if i == code { Some(puzzle.clone()) } else { p.clone() }).collect::<Vec<Option<Puzzle>>>();
                                        let next_next_puzzle = do_fill(Some(next_puzzle), (code+1)%puzzle.line_count, seen_cache, add_to_last_seen);
                                        match next_next_puzzle {
                                            Some(puzzle) => {
                                                println!("code {}: puzzle was found (solved). Propagating...", code_to_emoji(code));
                                                Some(puzzle)
                                            },
                                            None => {
                                                println!("code {}: puzzle was not found (is None). Trying remaining possibilities", code_to_emoji(code));
                                                try_every_possibility(puzzle, tail.to_vec(), code, seen_cache, last_seen_puzzle)
                                            }
                                        }
                                    } else {
                                        println!("{}.{:?}: invalid", code_to_emoji(code), head);
                                        try_every_possibility(puzzle, tail.to_vec(), code, seen_cache, last_seen_puzzle)
                                    }
                                },
                                None => {

                                    println!("code {}: no more possibilities remaining", code_to_emoji(code));

                                    println!("code {}: is assumed to be complete. Moving on.", code_to_emoji(code));
                                    let line_count = puzzle.line_count;
                                    let puzzle_hash = puzzle.hash();
                                    seen_cache.insert(puzzle_hash, true);
                                    let add_to_last_seen = last_seen_puzzle.iter().enumerate().map(|(i, p)| if i == code { Some(puzzle.clone()) } else { p.clone() }).collect::<Vec<Option<Puzzle>>>();
                                    let result = do_fill(Some(puzzle), (code+1)%line_count, seen_cache, add_to_last_seen);
                                    result
                                }
                            }
                        }

                        try_every_possibility(puzzle, possibilities, code, seen_cache, last_seen_puzzle)
                    } else {
                        if puzzle.is_complete() && puzzle.is_valid() {
                            println!("code {}: no moves left, but puzzle is valid and complete. Propagating...", code_to_emoji(code));
                            Some(puzzle)
                        } else {
                            println!("code {}: no moves left, and puzzle is invalid or incomplete. Propagating None...", code_to_emoji(code));
                            None
                        }
                    }
                },
                None => {
                    println!("{}: no moves remaining. Bailing out.", code_to_emoji(code));
                    None
                }
            }
        }
        do_fill(Some(self.clone()), 0, &mut seen_cache, vec![None; self.line_count])


    }

    pub fn emoji_visualize(&self) -> String {

        let code_to_emoji = |code: Option<usize>| match code {
            Some(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(), 
            },
            None => "⚪".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")
    }

    pub fn is_out_of_bounds(&self, coord: Coord) -> bool {
        coord.0 < 0 || coord.0 >= self.width || coord.1 < 0 || coord.1 >= self.height
    }

    pub 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
    }

    pub fn are_neighbors(&self, coord1: Coord, coord2: Coord) -> bool {
        let neighbors1 = self.get_neighbors(coord1);
        neighbors1.iter().any(|n| n.0 == coord2.0 && n.1 == coord2.1)
    }

    pub fn get_tail_ends_for_code(&self, code: Option<usize>) -> Vec<Coord> {
        self.grid
            .iter()
            .enumerate()
            .flat_map(|(y, row)|
                row
                    .iter()
                    .enumerate()
                    .flat_map(|(x, &col)|
                        if col == code && self.get_neighbors((x, y)).iter().filter(|neighbor| self.grid[neighbor.1][neighbor.0] == code).count() <= 1 {
                            vec![(x, y)]
                        } else {
                            vec![]
                        }
                    )
                    .collect::<Vec<Coord>>()
            )
            .collect()
    }

    pub fn get_tail_ends(&self) -> Vec<Vec<Coord>> {
        vec![0; self.line_count].iter().enumerate().map(|(code, _)| self.get_tail_ends_for_code(Some(code))).collect()
    }

    pub fn get_neighboring_tail_ends(&self, coord: Coord) -> Vec<Coord> {
        self.get_neighbors(coord).iter().filter(|&&neighbor| {

            let code = self.grid[neighbor.1][neighbor.0];

            let neighbor_neighbors = self.get_neighbors(neighbor);

            let neighbor_neighbors_len = neighbor_neighbors.iter().filter(|nn| self.grid[nn.1][nn.0] == code).count();

            neighbor_neighbors_len < 2
        }).map(|&neighbor| neighbor).collect()
    }

    pub fn line_length(&self, code: usize) -> usize {
        self.grid.iter().flat_map(|row| row.iter().filter(|&col| match col {
            Some(col) => *col == code,
            None => false
        })).count()
    }
}