use super::*;
use crate::Value;
use crate::parse::{List, Table};
use crate::session::Session;
use hmath::{Ratio, BigInt};

impl Value {

    pub fn to_bin(&self, buffer: &mut Vec<u8>, session: &Session) {

        match self {
            Value::None => {
                buffer.push(META_NONE);
            },

            Value::Boolean(b) => if *b {
                buffer.push(META_TRUE);
            }

            else {
                buffer.push(META_FALSE);
            },

            Value::Number(n) => if n.is_integer() {
                let n = n.truncate_bi();
                int_to_bin(&n, buffer);
            }

            else {
                rat_to_bin(n.clone(), buffer);
            },

            Value::List(List { data }) => {

                if data.len() <= (META_LIST_CONST_LEN_END - META_LIST_CONST_LEN_BEGIN) as usize {
                    buffer.push(data.len() as u8 + META_LIST_CONST_LEN_BEGIN);
                }

                else {
                    buffer.push(META_LIST_ARB_LEN);
                    let len = BigInt::from(data.len() as i64 - 49);
                    int_to_bin(&len, buffer);
                }

                for element in data.iter() {
                    element.to_bin(buffer, session);
                }

            },

            Value::String(s) => if let Some(s) = session.get_string_from_index(s.index) {

                if s.len() <= (META_STRING_CONST_LEN_END - META_STRING_CONST_LEN_BEGIN) as usize {
                    buffer.push(s.len() as u8 + META_STRING_CONST_LEN_BEGIN);

                    for b in s.iter() {
                        buffer.push(*b);
                    }

                }

                else {
                    buffer.push(META_STRING_ARB_LEN);
                    let len = BigInt::from(s.len() as i64 - 49);
                    int_to_bin(&len, buffer);

                    for b in s.iter() {
                        buffer.push(*b);
                    }

                }

            }

            else {
                todo!()
            },

            Value::Table(Table { data }) => {

                if data.len() <= (META_TABLE_CONST_LEN_END - META_TABLE_CONST_LEN_BEGIN) as usize {
                    buffer.push(data.len() as u8 + META_TABLE_CONST_LEN_BEGIN);
                }

                else {
                    buffer.push(META_TABLE_ARB_LEN);
                    let len = BigInt::from(data.len() as i64 - 49);
                    int_to_bin(&len, buffer);
                }

                for (key, value) in data.iter() {
                    key.to_bin(buffer, session);
                    value.to_bin(buffer, session);
                }

            },

            Value::Variable(_) => todo!()
        }

    }

}

pub fn encode_timestamp(timestamp: u64, buffer: &mut Vec<u8>) {

    // metadata must come very first
    assert!(buffer.is_empty());

    buffer.push(META_FILE_METADATA_START);
    int_to_bin(&8.into(), buffer);

    buffer.push((timestamp / (1 << 56)) as u8);
    buffer.push((timestamp / (1 << 48) % 256) as u8);
    buffer.push((timestamp / (1 << 40) % 256) as u8);
    buffer.push((timestamp / (1 << 32) % 256) as u8);
    buffer.push((timestamp / (1 << 24) % 256) as u8);
    buffer.push((timestamp / (1 << 16) % 256) as u8);
    buffer.push((timestamp / (1 <<  8) % 256) as u8);
    buffer.push((timestamp % 256) as u8);
}

fn int_to_bin(n: &BigInt, buffer: &mut Vec<u8>) {

    if let Ok(i) = n.to_i64() {

        if -32 <= i && i < 128 {
            buffer.push((i + 35) as u8);
        }

        else if i < 0 {
            buffer.push(META_NUM_NEG);
            int_to_bin(&n.neg(), buffer);
        }

        else if i < 384 {
            buffer.push(META_NUM_1_BYTE);
            buffer.push((i - 128) as u8);
        }

        else if i < 65920 {
            buffer.push(META_NUM_2_BYTE);
            buffer.push(((i - 384) / 256) as u8);
            buffer.push(((i - 384) % 256) as u8);
        }

        else if i < 16843136 {
            buffer.push(META_NUM_3_BYTE);
            buffer.push(((i - 65920) / 65536) as u8);
            buffer.push(((i - 65920) / 256 % 256) as u8);
            buffer.push(((i - 65920) % 256) as u8);
        }

        else if i < 4311810432 {
            buffer.push(META_NUM_4_BYTE);
            buffer.push(((i - 16843136) / 16777216) as u8);
            buffer.push(((i - 16843136) / 65536 % 256) as u8);
            buffer.push(((i - 16843136) / 256 % 256) as u8);
            buffer.push(((i - 16843136) % 256) as u8);
        }

        else {
            let num_buff = vec![
                ((i - 4311810432) / (1 << 56)) as u8,
                ((i - 4311810432) / (1 << 48) % 256) as u8,
                ((i - 4311810432) / (1 << 40) % 256) as u8,
                ((i - 4311810432) / (1 << 32) % 256) as u8,
                ((i - 4311810432) / (1 << 24) % 256) as u8,
                ((i - 4311810432) / (1 << 16) % 256) as u8,
                ((i - 4311810432) / (1 <<  8) % 256) as u8,
                ((i - 4311810432) % 256) as u8,
            ];

            let start_index = if num_buff[0] == 0 {
                if num_buff[1] == 0 {
                    if num_buff[2] == 0 {
                        if num_buff[3] == 0 { 4 } else  { 3 }
                    } else { 2 }
                } else { 1 }
            } else { 0 };

            buffer.push(META_NUM_ARB_BYTE);
            int_to_bin(&((8 - start_index) as i32 - 37).into(), buffer);

            for num in &num_buff[start_index..] {
                buffer.push(*num);
            }

        }

    }

    else if n.is_neg() {
        buffer.push(META_NUM_NEG);
        int_to_bin(&n.neg(), buffer);
    }

    else {
        buffer.push(META_NUM_ARB_BYTE);
        let mut n = n.clone().sub_bi(&4311810432i64.into());
        let mut num_buff = Vec::with_capacity(12);

        while !n.is_zero() {
            let rem: u32 = n.slice_right(1).try_into().unwrap();
            n.shift_right_mut(1);

            num_buff.push((rem % 256) as u8);
            num_buff.push((rem / 256 % 256) as u8);
            num_buff.push((rem / 65536 % 256) as u8);
            num_buff.push((rem / 16777216) as u8);
        }

        num_buff = num_buff.into_iter().rev().collect();

        while num_buff[num_buff.len() - 1] == 0 {
            num_buff.pop().unwrap();
        }

        int_to_bin(&(num_buff.len() as i32 - 37).into(), buffer);

        for byte in num_buff.into_iter() {
            buffer.push(byte);
        }

    }
}

fn rat_to_bin(mut n: Ratio, buffer: &mut Vec<u8>) {
    n.mul_i32_mut(10000);

    if n.is_integer() {
        buffer.push(META_NUM_DENOM_10000);
        int_to_bin(&n.truncate_bi(), buffer);
        return;
    }

    n.mul_i32_mut(10000);

    if n.is_integer() {
        buffer.push(META_NUM_DENOM_100000000);
        int_to_bin(&n.truncate_bi(), buffer);
        return;
    }

    n.div_i32_mut(100_000_000);

    buffer.push(META_NUM_DENOM_ARB);
    int_to_bin(&n.get_denom(), buffer);
    int_to_bin(&n.get_numer(), buffer);
}