use std::fmt;

use crate::{
    error::MachineError,
    vm::{machine::Machine, value::NumberValue},
};

#[derive(Debug, PartialEq, thiserror::Error)]
#[error("unrecognized instruction: {0}")]
pub struct InstructionDecodeError(u8);

primitive_enum! {
    #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
    pub enum Instruction: u8 [InstructionDecodeError] {
        // Stack manipulation
        PushNil = 0,
        PushInteger = 1,
        PushTrue = 2,
        PushFalse = 3,
        PushConstant = 4,
        CloseUpvalue = 5,
        Drop = 6,
        SetTemp = 7,
        GetTemp = 8,
        // Binding manupulation
        GetLocal = 10,
        SetLocal = 11,
        GetUpvalue = 12,
        SetUpvalue = 13,
        GetGlobal = 14,
        SetGlobal = 15,
        DeclareGlobal = 16,
        DeclareMacro = 17,
        // Arithmetic
        Add = 20,
        Sub = 21,
        Mul = 22,
        Div = 23,
        Mod = 24,
        Gt = 25,
        Lt = 26,
        Eq = 27,
        Ge = 28,
        Le = 29,
        Ne = 30,
        Not = 31,
        Negate = 32,
        And = 33,
        Or = 34,
        // Branching
        Branch = 40,
        Jump = 41,
        // Functions and closures
        Call = 50,
        Return = 51,
        MakeClosure = 52,
    }
}

macro_rules! impl_fixed_unsigned {
    ($vis:vis $ident:ident : $repr:ty, $bits:literal) => {
        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
        #[repr(transparent)]
        $vis struct $ident($repr);

        impl $ident {
            pub const BITS: usize = $bits;

            pub fn to_bytes(&self) -> [u8; $bits / 8] {
                self.0.to_le_bytes()
            }
        }

        impl TryFrom<usize> for $ident {
            type Error = FixedConvertError<usize, $bits>;

            fn try_from(value: usize) -> Result<Self, Self::Error> {
                if let Ok(value) = <$repr>::try_from(value) {
                    Ok(Self(value))
                } else {
                    Err(FixedConvertError(value))
                }
            }
        }

        impl From<$repr> for $ident {
            fn from(value: $repr) -> $ident {
                $ident(value)
            }
        }

        impl From<$ident> for usize {
            fn from(value: $ident) -> usize {
                value.0.into()
            }
        }

        impl From<$ident> for $repr {
            fn from(value: $ident) -> $repr {
                value.0
            }
        }
    };
}
macro_rules! impl_fixed {
    ($vis:vis $ident:ident: u8) => {
        impl_fixed_unsigned!($vis $ident : u8, 8);

        impl ReadEncoded for $ident {
            fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
                let b0 = stream.fetch_byte()?;
                Ok(Self(b0))
            }
        }
    };
    ($vis:vis $ident:ident: u16) => {
        impl_fixed_unsigned!($vis $ident : u16, 16);

        impl ReadEncoded for $ident {
            fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
                let b0 = stream.fetch_byte()?;
                let b1 = stream.fetch_byte()?;
                Ok(Self(u16::from_le_bytes([b0, b1])))
            }
        }
    };
}

#[derive(Debug, thiserror::Error)]
#[error("cannot fit {0} into {N} bits")]
pub struct FixedConvertError<T: fmt::Display, const N: usize>(T);

impl_fixed!(pub LocalId: u8);
impl_fixed!(pub ConstantId: u16);
impl_fixed!(pub ArgumentCount: u8);

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct ImmediateInteger(i16);

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct BranchOffset(i16);

impl ImmediateInteger {
    pub fn sign_extend_i64(self) -> i64 {
        self.0 as i64
    }
    pub fn to_bytes(&self) -> [u8; 2] {
        self.0.to_le_bytes()
    }
}

impl From<i16> for ImmediateInteger {
    fn from(value: i16) -> Self {
        Self(value)
    }
}

impl ReadEncoded for ImmediateInteger {
    fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
        let b0 = stream.fetch_byte()?;
        let b1 = stream.fetch_byte()?;
        Ok(Self(i16::from_le_bytes([b0, b1])))
    }
}

impl TryFrom<NumberValue> for ImmediateInteger {
    type Error = FixedConvertError<NumberValue, 16>;

    fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
        match value {
            NumberValue::Int(value) if let Ok(value) = value.try_into() => Ok(Self(value)),
            _ => Err(FixedConvertError(value)),
        }
    }
}
impl TryFrom<i64> for ImmediateInteger {
    type Error = FixedConvertError<i64, 16>;

    fn try_from(value: i64) -> Result<Self, Self::Error> {
        if let Ok(value) = value.try_into() {
            Ok(Self(value))
        } else {
            Err(FixedConvertError(value))
        }
    }
}

impl BranchOffset {
    pub fn sign_extend_isize(self) -> isize {
        self.0 as isize
    }
    pub fn to_bytes(&self) -> [u8; 2] {
        self.0.to_le_bytes()
    }
}

impl TryFrom<isize> for BranchOffset {
    type Error = FixedConvertError<isize, 16>;

    fn try_from(value: isize) -> Result<Self, Self::Error> {
        if let Ok(value) = value.try_into() {
            Ok(Self(value))
        } else {
            Err(FixedConvertError(value))
        }
    }
}

impl From<i16> for BranchOffset {
    fn from(value: i16) -> Self {
        Self(value)
    }
}

impl ReadEncoded for BranchOffset {
    fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
        let b0 = stream.fetch_byte()?;
        let b1 = stream.fetch_byte()?;
        Ok(Self(i16::from_le_bytes([b0, b1])))
    }
}

pub trait ReadEncoded: Sized {
    fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError>;
}

impl fmt::Display for Instruction {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = match self {
            Self::PushNil => "PUSH_NIL",
            Self::PushInteger => "PUSH_INTEGER",
            Self::PushTrue => "PUSH_TRUE",
            Self::PushFalse => "PUSH_FALSE",
            Self::PushConstant => "PUSH_CONSTANT",
            Self::CloseUpvalue => "CLOSE_UPVALUE",
            Self::Drop => "DROP",
            Self::SetTemp => "SET_TEMP",
            Self::GetTemp => "GET_TEMP",
            Self::GetLocal => "GET_LOCAL",
            Self::SetLocal => "SET_LOCAL",
            Self::GetUpvalue => "GET_UPVALUE",
            Self::SetUpvalue => "SET_UPVALUE",
            Self::GetGlobal => "GET_GLOBAL",
            Self::SetGlobal => "SET_GLOBAL",
            Self::DeclareGlobal => "DECLARE_GLOBAL",
            Self::DeclareMacro => "DECLARE_MACRO",
            Self::Add => "ADD",
            Self::Sub => "SUB",
            Self::Mul => "MUL",
            Self::Div => "DIV",
            Self::Mod => "MOD",
            Self::Gt => "GT",
            Self::Lt => "LT",
            Self::Eq => "EQ",
            Self::Ge => "GE",
            Self::Le => "LE",
            Self::Ne => "NE",
            Self::Not => "NOT",
            Self::Negate => "NEGATE",
            Self::And => "AND",
            Self::Or => "OR",
            Self::Branch => "BRANCH",
            Self::Jump => "JUMP",
            Self::Call => "CALL",
            Self::Return => "RETURN",
            Self::MakeClosure => "MAKE_CLOSURE",
        };
        f.write_str(name)
    }
}
