use std::{collections::HashMap, hash::Hash, ops::Deref};

use eyre::{eyre, Context, Result};
use serde::Deserialize;

#[derive(Debug)]
struct Matrix<T> {
	width: usize,
	height: usize,
	data: Vec<T>,
}

#[derive(Debug)]
struct BiMatrix<T> {
	m: Matrix<T>,
	to_coord: HashMap<T, MatrixCoord>,
}
impl<T> Deref for BiMatrix<T> {
	type Target = Matrix<T>;

	fn deref(&self) -> &Self::Target {
		&self.m
	}
}

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
struct MatrixCoord {
	x: usize,
	y: usize,
}
impl MatrixCoord {
	fn new(x: usize, y: usize) -> Self {
		Self { x, y }
	}
}

impl<T: Copy + Clone> Matrix<T> {
	fn new(width: usize, height: usize) -> Self
	where
		T: Default,
	{
		let data = vec![T::default(); width * height];

		Self {
			width,
			height,
			data,
		}
	}

	fn from_vec(width: usize, height: usize, data: Vec<T>) -> Result<Self> {
		if width * height != data.len() {
			return Err(eyre!("Invalid data len"));
		}

		let out = Self {
			width,
			height,
			data,
		};

		Ok(out)
	}

	fn val_at_coord(&self, c: MatrixCoord) -> &T {
		self.val_at_index(self.coord_to_index(c))
	}

	fn val_at_index(&self, i: usize) -> &T {
		&self.data[i]
	}

	fn index_to_coord(&self, i: usize) -> MatrixCoord {
		let x = i % self.width;
		let y = i / self.width;

		MatrixCoord { x, y }
	}

	fn coord_to_index(&self, c: MatrixCoord) -> usize {
		c.y * self.width + c.x
	}

	fn set(&mut self, v: T, c: MatrixCoord) {
		let i = self.coord_to_index(c);
		self.data[i] = v;
	}

	fn size(&self) -> usize {
		self.width * self.height
	}
}

impl<T: Hash + Eq + Copy + Clone> BiMatrix<T> {
	fn new(width: usize, height: usize) -> Self
	where
		T: Default,
	{
		Self {
			m: Matrix::new(width, height),
			to_coord: Default::default(),
		}
	}

	fn from_vec(width: usize, height: usize, data: Vec<T>) -> Result<Self> {
		let m = Matrix::from_vec(width, height, data)?;

		let mut out = Self {
			m,
			to_coord: Default::default(),
		};

		for (i, v) in out.m.data.iter().enumerate() {
			out.to_coord
				.insert(*v, MatrixCoord::new(i % width, i / width));
		}

		Ok(out)
	}

	fn val_to_coord(&self, v: &T) -> MatrixCoord {
		*self.to_coord.get(v).unwrap()
	}

	fn val_to_index(&self, v: &T) -> usize {
		self.coord_to_index(self.val_to_coord(v))
	}

	fn set(&mut self, v: T, c: MatrixCoord) {
		self.m.set(v, c);
		self.to_coord.insert(v, c);
	}
}

type Layout = BiMatrix<char>;

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
enum Hand {
	Left,
	Right,
}

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
enum Digit {
	Pinky,
	Ring,
	Middle,
	Index,
}

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
struct Finger {
	digit: Digit,
	hand: Hand,
}

fn load_matrices(
	paths: &Paths,
) -> Result<(Layout, Matrix<Finger>, Matrix<f64>)> {
	let data = std::fs::read_to_string(&paths.matrices)?;

	let mut sections = data.split("\n\n");
	let section_layout =
		sections.next().ok_or(eyre!("Missing section: layout"))?;

	let data: Vec<Vec<_>> = section_layout
		.lines()
		.map(|l| {
			l.split_whitespace()
				.flat_map(|s| s.chars().next())
				.collect()
		})
		.collect();

	let width = data
		.iter()
		.map(|l| l.len())
		.max()
		.ok_or(eyre!("Initial layout was empty"))?;

	let height = data.len();

	let mut layout = Layout::new(width, height);
	for (y, row) in data.iter().enumerate() {
		for (x, ch) in row.iter().enumerate() {
			layout.set(*ch, MatrixCoord::new(x, y));
		}
	}

	let section_fingers =
		sections.next().ok_or(eyre!("Missing section: fingers"))?;

	let lines_fingers = section_fingers.lines();

	let fingers: Vec<Finger> = lines_fingers
		.flat_map(|l| {
			let words = l.split_whitespace();
			words
		})
		.map(|w| {
			let (l_or_r, digit) = w.split_at(1);
			let hand = match l_or_r.to_lowercase().as_str() {
				"l" => Hand::Left,
				"r" => Hand::Right,
				_ => return Err(eyre!("Invalid finger in data/matrices.txt")),
			};

			let digit = match digit.to_lowercase().as_str() {
				"pinky" => Digit::Pinky,
				"ring" => Digit::Ring,
				"middle" => Digit::Middle,
				"index" => Digit::Index,
				_ => return Err(eyre!("Invalid finger in data/matrices.txt")),
			};

			Ok(Finger { hand, digit })
		})
		.collect::<Result<Vec<_>, _>>()?;

	let m_finger = Matrix::from_vec(layout.width, layout.height, fingers)
		.wrap_err("When loading fingers from data/matrices.txt")?;

	let section_effort =
		sections.next().ok_or(eyre!("Missing section: effort"))?;
	let lines_effort = section_effort.lines();
	let efforts: Vec<f64> = lines_effort
		.flat_map(|l| l.split_whitespace())
		.map(|w| w.parse())
		.collect::<Result<Vec<_>, _>>()
		.wrap_err("When loading efforts from data/matrices.txt")?;

	let m_effort = Matrix::from_vec(layout.width, layout.height, efforts)
		.wrap_err("When loading efforts from data/matrices.txt")?;

	Ok((layout, m_finger, m_effort))
}

#[derive(Deserialize, Debug)]
struct Paths {
	matrices: String,
}

#[derive(Deserialize, Debug)]
struct Workers {
	n_workers: usize,
	prefer_numcpus: bool,
}

#[derive(Deserialize, Debug)]
struct Parameters {}

#[derive(Deserialize, Debug)]
struct Settings {
	paths: Paths,
	workers: Workers,
	parameters: Parameters,
}

fn main() -> Result<()> {
	let settings = std::fs::read_to_string("./settings.toml")?;
	let mut settings: Settings = toml::from_str(&settings)?;
	if settings.workers.prefer_numcpus {
		settings.workers.n_workers = num_cpus::get();
	}

	let (m_layout, m_fingers, m_effort) = load_matrices(&settings.paths)?;

	Ok(())
}
