use crate::js::*;
use crate::op::Op;
use crate::value::{Key, Value};
use chumsky::prelude::SimpleSpan;
use imbl::HashMap;
use num_traits::FromPrimitive;
use regex::Regex;

#[derive(Clone, Debug)]
pub struct StrPattern {
    pub words: Vec<&'static str>,
    pub re: Regex,
}

#[derive(Clone, Debug)]
pub struct Chunk {
    pub constants: Vec<Value>,
    pub bytecode: Vec<u8>,
    pub keywords: Vec<&'static str>,
    pub string_patterns: Vec<StrPattern>,
    pub env: HashMap<Key, Value>,
    pub msgs: Vec<String>,
    pub spans: Vec<SimpleSpan>,
    pub src: &'static str,
    pub input: &'static str,
}

impl std::fmt::Display for Chunk {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "Chunk.")
    }
}

impl Chunk {
    pub fn dissasemble_instr(&self, i: &mut usize) {
        let op = Op::from_u8(self.bytecode[*i]).unwrap();
        use Op::*;
        match op {
            Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
            | ResetMatch | GetKey | PanicWhenFallthrough | PanicNoMatch | PanicNoFnMatch
            | PanicNoLetMatch | TypeOf | Duplicate | Decrement | ToInt | Noop | LoadTuple
            | LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At
            | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return
            | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict
            | AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage
            | NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee | Spawn => {
                console_log!("{} | {i:04}: {op}", self.spans[*i])
            }
            Constant | MatchConstant => {
                let high = self.bytecode[*i + 1];
                let low = self.bytecode[*i + 2];
                let idx = ((high as usize) << 8) + low as usize;
                let value = &self.constants[idx].show();
                console_log!(
                    "{} | {i:04}: {:16} {idx:05}: {value}",
                    self.spans[*i],
                    op.to_string()
                );
                *i += 2;
            }
            Msg => {
                let msg_idx = self.bytecode[*i + 1];
                let msg = &self.msgs[msg_idx as usize];
                console_log!("{} | {i:04}: {msg}", self.spans[*i]);
                *i += 1;
            }
            PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList
            | MatchSplattedList | LoadSplattedList | MatchDict | MatchSplattedDict
            | DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN
            | Call | GetUpvalue | Partial | MatchString | PushStringMatches | TailCall | LoadN => {
                let next = self.bytecode[*i + 1];
                console_log!(
                    "{} | {i:04}: {:16} {next:03}",
                    self.spans[*i],
                    op.to_string()
                );
                *i += 1;
            }
            Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch | JumpIfMatch | JumpBack
            | JumpIfZero => {
                let high = self.bytecode[*i + 1];
                let low = self.bytecode[*i + 2];
                let len = ((high as u16) << 8) + low as u16;
                console_log!(
                    "{} | {i:04}: {:16} {len:05}",
                    self.spans[*i],
                    op.to_string()
                );
                *i += 2;
            }
        }
    }

    pub fn dissasemble(&self) {
        console_log!("IDX | CODE           | INFO");
        let mut i = 0;
        while i < self.bytecode.len() {
            self.dissasemble_instr(&mut i);
            i += 1;
        }
    }
}
