//            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
//                    Version 2, December 2004
//
// Copyleft (ↄ) meh. <meh@schizofreni.co> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
//            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
//   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
//  0. You just DO WHAT THE FUCK YOU WANT TO.

use std::char;
use std::io::{BufWriter, Write};

use crate::error;
use crate::parser::expansion::*;

/// Trait for items that can be expanded.
pub trait Expand {
	fn expand<W: Write>(
		&self,
		output: W,
		parameters: &[Parameter],
		context: &mut Context,
	) -> error::Result<()>;
}

/// An expansion parameter.
#[derive(Eq, PartialEq, Clone, Debug)]
pub enum Parameter {
	/// A number.
	Number(i32),

	/// An ASCII string.
	String(Vec<u8>),
}

impl Default for Parameter {
	fn default() -> Self {
		Parameter::Number(0)
	}
}

macro_rules! from {
	(number $ty:ty) => {
		impl From<$ty> for Parameter {
			fn from(value: $ty) -> Self {
				Parameter::Number(value as i32)
			}
		}
	};

	(string ref $ty:ty) => {
		impl<'a> From<&'a $ty> for Parameter {
			fn from(value: &'a $ty) -> Self {
				Parameter::String(value.into())
			}
		}
	};

	(string $ty:ty) => {
		impl From<$ty> for Parameter {
			fn from(value: $ty) -> Self {
				Parameter::String(value.into())
			}
		}
	};
}

from!(number bool);
from!(number u8);
from!(number i8);
from!(number u16);
from!(number i16);
from!(number u32);
from!(number i32);

from!(string String);
from!(string ref str);
from!(string Vec<u8>);
from!(string ref [u8]);

/// The expansion context.
///
/// The same context should be passed around through every expansion for the
/// same `Database`.
#[derive(Eq, PartialEq, Default, Debug)]
pub struct Context {
	pub fixed: [Parameter; 26],
	pub dynamic: [Parameter; 26],
}

/// Expand a parametrized string.
///
/// ## Examples
///
/// Write the expansion to `stdout`.
///
/// ```
/// use terminfo::{Database, capability as cap, expand};
/// use std::io;
///
/// # fn main() {
///
/// let info = Database::from_env().unwrap();
///
/// // Move the cursor to X: 20, Y: 30
/// expand!(io::stdout(), info.get::<cap::CursorAddress>().unwrap().as_ref(); 20, 30).unwrap();
///
/// # }
/// ```
///
/// Load the expansion for later usage.
///
/// ```
/// use terminfo::{Database, capability as cap, expand};
/// use std::io;
///
/// # fn main() {
///
/// let info = Database::from_env().unwrap();
///
/// // Set foreground color to red.
/// let red     = expand!(info.get::<cap::SetAForeground>().unwrap().as_ref(); 1).unwrap();
/// let on_blue = expand!(info.get::<cap::SetABackground>().unwrap().as_ref(); 4).unwrap();
///
/// # }
/// ```
#[macro_export]
macro_rules! expand {
	($value:expr) => (
		$crate::expand!($value;)
	);

	($value:expr => $context:expr) => (
		$crate::expand!($value => $context;)
	);

	($value:expr; $($item:expr),*) => (
		$crate::expand!($value => &mut ::std::default::Default::default(); $($item),*)
	);

	($value:expr => $context:expr; $($item:expr),*) => ({
		let mut output = ::std::vec::Vec::new();

		$crate::expand!(&mut output, $value => $context; $($item),*).map(|()| output)
	});

	($output:expr, $value:expr) => (
		$crate::expand!($output, $value;)
	);

	($output:expr, $value:expr => $context:expr) => (
		$crate::expand!($output, $value => $context;)
	);

	($output:expr, $value:expr; $($item:expr),*) => (
		$crate::expand!($output, $value => &mut ::std::default::Default::default(); $($item),*)
	);

	($output:expr, $value:expr => $context:expr; $($item:expr),*) => ({
		use $crate::Expand;
		$value.expand($output, &[$($item.into()),*], $context)
	})
}

impl Expand for [u8] {
	fn expand<W: Write>(
		&self,
		output: W,
		parameters: &[Parameter],
		context: &mut Context,
	) -> error::Result<()> {
		let mut output = BufWriter::new(output);
		let mut input = self;
		let mut params: [Parameter; 9] = Default::default();
		let mut stack = Vec::new();
		let mut conditional = false;
		let mut incremented = false;

		for (dest, source) in params.iter_mut().zip(parameters.iter()) {
			*dest = source.clone();
		}

		macro_rules! next {
			() => {
				match parse(input) {
					Ok((rest, item)) => {
						input = rest;
						item
					}

					Err(_) => return Err(error::Expand::Invalid.into()),
				}
			};
		}

		'main: while !input.is_empty() {
			match next!() {
				Item::Conditional(Conditional::If) => {
					conditional = true;
				}

				Item::Conditional(Conditional::End) if conditional => {
					conditional = false;
				}

				Item::Conditional(Conditional::Then) if conditional => match stack.pop() {
					Some(Parameter::Number(0)) => {
						let mut level = 0;

						while !input.is_empty() {
							match next!() {
								Item::Conditional(Conditional::End)
								| Item::Conditional(Conditional::Else)
									if level == 0 =>
								{
									continue 'main
								}

								Item::Conditional(Conditional::If) => level += 1,

								Item::Conditional(Conditional::End) => level -= 1,

								_ => (),
							}
						}

						return Err(error::Expand::Invalid.into());
					}

					Some(_) => (),

					None => return Err(error::Expand::StackUnderflow.into()),
				},

				Item::Conditional(Conditional::Else) if conditional => {
					let mut level = 0;

					while !input.is_empty() {
						match next!() {
							Item::Conditional(Conditional::End) if level == 0 => continue 'main,

							Item::Conditional(Conditional::If) => level += 1,

							Item::Conditional(Conditional::End) => level -= 1,

							_ => (),
						}
					}

					return Err(error::Expand::Invalid.into());
				}

				Item::Conditional(..) => return Err(error::Expand::Invalid.into()),

				Item::String(value) => output.write_all(value)?,

				Item::Constant(Constant::Character(ch)) => {
					stack.push(Parameter::Number(ch as i32));
				}

				Item::Constant(Constant::Integer(value)) => {
					stack.push(Parameter::Number(value));
				}

				Item::Variable(Variable::Length) => match stack.pop() {
					Some(Parameter::String(ref value)) => {
						stack.push(Parameter::Number(value.len() as i32));
					}

					Some(_) => {
						return Err(error::Expand::TypeMismatch.into());
					}

					None => {
						return Err(error::Expand::StackUnderflow.into());
					}
				},

				Item::Variable(Variable::Push(index)) => {
					stack.push(params[index as usize].clone());
				}

				Item::Variable(Variable::Set(dynamic, index)) => {
					if let Some(value) = stack.pop() {
						if dynamic {
							context.dynamic[index as usize] = value.clone();
						} else {
							context.fixed[index as usize] = value.clone();
						}
					} else {
						return Err(error::Expand::StackUnderflow.into());
					}
				}

				Item::Variable(Variable::Get(dynamic, index)) => {
					if dynamic {
						stack.push(context.dynamic[index as usize].clone());
					} else {
						stack.push(context.fixed[index as usize].clone());
					}
				}

				Item::Operation(Operation::Increment) if !incremented => {
					incremented = true;

					if let (&Parameter::Number(x), &Parameter::Number(y)) = (&params[0], &params[1])
					{
						params[0] = Parameter::Number(x + 1);
						params[1] = Parameter::Number(y + 1);
					} else {
						return Err(error::Expand::TypeMismatch.into());
					}
				}

				Item::Operation(Operation::Increment) => (),

				Item::Operation(Operation::Binary(operation)) => match (stack.pop(), stack.pop()) {
					(Some(Parameter::Number(y)), Some(Parameter::Number(x))) => {
						stack.push(Parameter::Number(match operation {
							Binary::Add => x + y,
							Binary::Subtract => x - y,
							Binary::Multiply => x * y,
							Binary::Divide => {
								if y != 0 {
									x / y
								} else {
									0
								}
							}
							Binary::Remainder => {
								if y != 0 {
									x % y
								} else {
									0
								}
							}

							Binary::AND => x & y,
							Binary::OR => x | y,
							Binary::XOR => x ^ y,

							Binary::And => (x != 0 && y != 0) as i32,
							Binary::Or => (x != 0 || y != 0) as i32,

							Binary::Equal => (x == y) as i32,
							Binary::Greater => (x > y) as i32,
							Binary::Lesser => (x < y) as i32,
						}))
					}

					(Some(_), Some(_)) => return Err(error::Expand::TypeMismatch.into()),

					_ => return Err(error::Expand::StackUnderflow.into()),
				},

				Item::Operation(Operation::Unary(operation)) => match stack.pop() {
					Some(Parameter::Number(x)) => stack.push(Parameter::Number(match operation {
						Unary::Not => (x != 0) as i32,
						Unary::NOT => !x,
					})),

					Some(_) => return Err(error::Expand::TypeMismatch.into()),

					_ => return Err(error::Expand::StackUnderflow.into()),
				},

				Item::Print(p) => {
					/// Calculate the length of a formatted number.
					fn length(value: i32, p: &Print) -> usize {
						let digits = match p.format {
							Format::Dec => (value as f32).abs().log(10.0).floor() as usize + 1,

							Format::Oct => (value as f32).abs().log(8.0).floor() as usize + 1,

							Format::Hex | Format::HEX => {
								(value as f32).abs().log(16.0).floor() as usize + 1
							}

							_ => unreachable!(),
						};

						let mut length = digits;

						// Add the minimum number of digits.
						if p.flags.precision > digits {
							length += p.flags.precision - digits;
						}

						// Add the sign if present.
						if p.format == Format::Dec && (value < 0 || p.flags.sign) {
							length += 1;
						}

						// Add the alternate representation.
						if p.flags.alternate {
							match p.format {
								Format::Hex | Format::HEX => length += 2,

								Format::Oct => length += 1,

								_ => (),
							}
						}

						length
					}

					macro_rules! w {
						($value:expr) => (
							output.write_all($value)?
						);

						($($item:tt)*) => (
							write!(output, $($item)*)?
						);
					}

					macro_rules! f {
						(by $length:expr) => (
							for _ in 0 .. p.flags.width - $length {
								output.write_all(if p.flags.space { b" " } else { b"0" })?;
							}
						);

						(before by $length:expr) => (
							if !p.flags.left && p.flags.width > $length {
								f!(by $length);
							}
						);

						(after by $length:expr) => (
							if p.flags.left && p.flags.width > $length {
								f!(by $length);
							}
						);

						(before $value:expr) => (
							f!(before by length($value, &p));
						);

						(after $value:expr) => (
							f!(after by length($value, &p));
						);
					}

					match (p.format, stack.pop()) {
						(Format::Str, Some(Parameter::String(ref value))) => {
							let mut value = &value[..];

							if p.flags.precision > 0 && p.flags.precision < value.len() {
								value = &value[..p.flags.precision];
							}

							f!(before by value.len());
							w!(value);
							f!(after by value.len());
						}

						(Format::Chr, Some(Parameter::Number(value))) => {
							w!("{}", value as u8 as char)
						}

						(Format::Uni, Some(Parameter::Number(value))) => w!(
							"{}",
							char::from_u32(value as u32).ok_or(error::Expand::TypeMismatch)?
						),

						(Format::Dec, Some(Parameter::Number(value))) => {
							f!(before value);

							if p.flags.sign && value >= 0 {
								w!(b"+");
							}

							w!("{:.1$}", value, p.flags.precision);

							f!(after value);
						}

						(Format::Oct, Some(Parameter::Number(value))) => {
							f!(before value);

							if p.flags.alternate {
								w!(b"0");
							}

							w!("{:.1$o}", value, p.flags.precision);

							f!(after value);
						}

						(Format::Hex, Some(Parameter::Number(value))) => {
							f!(before value);

							if p.flags.alternate {
								w!(b"0x");
							}

							w!("{:.1$x}", value, p.flags.precision);

							f!(after value);
						}

						(Format::HEX, Some(Parameter::Number(value))) => {
							f!(before value);

							if p.flags.alternate {
								w!(b"0X");
							}

							w!("{:.1$X}", value, p.flags.precision);

							f!(after value);
						}

						(_, Some(_)) => return Err(error::Expand::TypeMismatch.into()),

						(_, None) => return Err(error::Expand::StackUnderflow.into()),
					}
				}
			}
		}

		Ok(())
	}
}

#[cfg(test)]
mod test {
	#[test]
	fn test_basic_setabf() {
		assert_eq!(b"\\E[48;5;1m".to_vec(), expand!(b"\\E[48;5;%p1%dm"; 1).unwrap());
	}

	#[test]
	fn print() {
		assert_eq!(b"0001".to_vec(), expand!(b"%p1%4d"; 1).unwrap());

		assert_eq!(b"10".to_vec(), expand!(b"%p1%o"; 8).unwrap());
	}

	#[test]
	fn conditional() {
		assert_eq!(b"1".to_vec(), expand!(b"%?%p1%t1%e2%;"; 1).unwrap());

		assert_eq!(b"2".to_vec(), expand!(b"%?%p1%t1%e2%;"; 0).unwrap());

		assert_eq!(b"3".to_vec(), expand!(b"%?%p1%t%e%p2%t2%e%p3%t3%;"; 0, 0, 1).unwrap());
	}
}
