use crate::op::Op;
use crate::value::{Key, Value};
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>,
}

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
            | PanicIfNoMatch | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | 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 => {
                println!("{i:04}: {op}")
            }
            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();
                println!("{i:04}: {:16} {idx:05}: {value}", op.to_string());
                *i += 2;
            }
            Msg => {
                let msg_idx = self.bytecode[*i + 1];
                let msg = &self.msgs[msg_idx as usize];
                println!("{i:04}: {msg}");
                *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];
                println!("{i:04}: {:16} {next:03}", 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;
                println!("{i:04}: {:16} {len:05}", op.to_string());
                *i += 2;
            }
        }
    }

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

    // pub fn kw_from(&self, kw: &str) -> Option<Value> {
    //     self.kw_index_from(kw).map(Value::Keyword)
    // }

    // pub fn kw_index_from(&self, kw: &str) -> Option<usize> {
    //     self.keywords.iter().position(|s| *s == kw)
    // }
}
