use super::*;
use crate::{Value, SJFLError, Session};
use crate::err::Span;
use crate::parse::{Table, List, InternedString};
use hmath::{Ratio, BigInt};

impl Value {

    // cursor points to the start of the next value after it finishes parsing
    pub fn from_bin(buffer: &[u8], cursor: &mut usize, session: &mut Session) -> Result<Value, SJFLError> {

        if buffer.len() <= *cursor {
            return Err(SJFLError::UnexpectedEof(Span::binary()));
        }

        let c = buffer[*cursor];

        if c <= META_FALSE {
            *cursor += 1;

            if c == 0 {
                Ok(Value::None)
            }

            else {
                Ok(Value::Boolean(c == 1))
            }

        }

        else if c <= META_CONST_NUM_END {
            *cursor += 1;
            Ok(Value::Number(Ratio::from_i32(c as i32 - 35)))
        }

        else if c <= META_NUM_NEG {
            *cursor += 1;

            if c == META_NUM_NEG {

                if let Value::Number(mut n) = Value::from_bin(buffer, cursor, session)? {
                    n.neg_mut();
                    Ok(Value::Number(n))
                }

                else {
                    Err(SJFLError::CorruptedBinarySJFL("expected number, but found another type!".to_string()))
                }

            }

            else {
                let length = if c < META_NUM_ARB_BYTE {
                    c as usize - (META_NUM_1_BYTE as usize - 1)
                }

                else if let Ok(n) = try_parse_integer(buffer, cursor, session, 37)?.try_into() {
                    n
                }

                else {
                    return Err(SJFLError::CorruptedBinarySJFL("expected unsigned int, but another type is found".to_string()));
                };

                let offset = if c == META_NUM_1_BYTE {
                    128
                } else if c == META_NUM_2_BYTE {
                    384
                } else if c == META_NUM_3_BYTE {
                    65920
                } else if c == META_NUM_4_BYTE {
                    16843136
                } else {
                    4311810432
                };

                let n = parse_integer(buffer, cursor, length, offset)?;
                Ok(Value::Number(Ratio::from_bi(n)))
            }

        }

        else if c <= META_NUM_DENOM_ARB {
            *cursor += 1;
            let denom = try_parse_integer(buffer, cursor, session, 0)?;

            let numer = if c == META_NUM_DENOM_10000 {
                1_0000.into()
            }

            else if c == META_NUM_DENOM_100000000 {
                1_0000_0000.into()
            }

            else {
                try_parse_integer(buffer, cursor, session, 0)?
            };

            Ok(Value::Number(Ratio::from_denom_and_numer(denom, numer)))
        }

        else if c <= META_LIST_ARB_LEN {
            *cursor += 1;
            let length = if c < META_LIST_ARB_LEN {
                c as usize - META_LIST_CONST_LEN_BEGIN as usize
            }

            else if let Ok(n) = try_parse_integer(buffer, cursor, session, 49)?.try_into() {
                n
            }

            else {
                return Err(SJFLError::CorruptedBinarySJFL("expected unsigned int, but another type is found".to_string()));
            };

            let mut data = Vec::with_capacity(length);

            for _ in 0..length {
                let e = Value::from_bin(buffer, cursor, session)?;
                data.push(Box::new(e));
            }

            Ok(Value::List(List { data }))
        }

        else if c <= META_STRING_ARB_LEN {
            *cursor += 1;
            let length = if c < META_STRING_ARB_LEN {
                c as usize - META_STRING_CONST_LEN_BEGIN as usize
            }

            else if let Ok(n) = try_parse_integer(buffer, cursor, session, 49)?.try_into() {
                n
            }

            else {
                return Err(SJFLError::CorruptedBinarySJFL("expected unsigned int, but another type is found".to_string()));
            };

            let string = if *cursor + length <= buffer.len() {
                buffer[*cursor..(*cursor + length)].to_vec()
            }

            else {
                return Err(SJFLError::UnexpectedEof(Span::binary()));
            };

            *cursor += length;
            Ok(Value::String(InternedString { index: session.intern_string(&string) }))
        }

        else if c <= META_TABLE_ARB_LEN {
            *cursor += 1;
            let length = if c < META_TABLE_ARB_LEN {
                c as usize - META_TABLE_CONST_LEN_BEGIN as usize
            }

            else if let Ok(n) = try_parse_integer(buffer, cursor, session, 49)?.try_into() {
                n
            }

            else {
                return Err(SJFLError::CorruptedBinarySJFL("expected unsigned int, but another type is found".to_string()));
            };

            let mut data = Vec::with_capacity(length);

            for _ in 0..length {
                let k = Value::from_bin(buffer, cursor, session)?;
                let v = Value::from_bin(buffer, cursor, session)?;
                data.push((Box::new(k), Box::new(v)));
            }

            Ok(Value::Table(Table { data }))
        }

        else if c == META_FILE_METADATA_START {
            *cursor += 1;

            if let Ok(metdata_length) = <BigInt as TryInto<usize>>::try_into(try_parse_integer(buffer, cursor, session, 0)?) {
                *cursor += metdata_length;

                Value::from_bin(buffer, cursor, session)
            }

            else {
                Err(SJFLError::CorruptedBinarySJFL("expected unsigned int, but another type is found".to_string()))
            }

        }

        else {
            todo!()
        }
    }

}

// cursor points to the start of the data, not a meta-char
fn parse_integer(buffer: &[u8], cursor: &mut usize, length: usize, offset: i64) -> Result<BigInt, SJFLError> {
    let mut result = BigInt::zero();

    for _ in 0..length {

        if buffer.len() <= *cursor {
            return Err(SJFLError::UnexpectedEof(Span::binary()));
        }

        result.mul_i32_mut(256);
        result.add_i32_mut(buffer[*cursor] as i32);
        *cursor += 1;
    }

    result.add_bi_mut(&BigInt::from_i64(offset));

    Ok(result)
}

// cursor points to a meta-char, not the data
fn try_parse_integer(buffer: &[u8], cursor: &mut usize, session: &mut Session, offset: i32) -> Result<BigInt, SJFLError> {

    if let Value::Number(n) = Value::from_bin(buffer, cursor, session)? {

        if n.is_integer() {
            Ok(<Ratio as Into<BigInt>>::into(n).add_i32(offset))
        }

        else {
            Err(SJFLError::CorruptedBinarySJFL(format!("expected an integer, but got {n}")))
        }

    }

    else {
        Err(SJFLError::CorruptedBinarySJFL(format!("expected a number, but another type is found")))
    }

}

// it assumes that file-metadata comes very first
pub fn decode_timestamp(buffer: &[u8], session: &mut Session) -> Result<u64, SJFLError> {

    // skipping META_FILE_METADATA_START
    let mut cursor = 1;

    // it's always 8 (for now)
    let _ = try_parse_integer(buffer, &mut cursor, session, 0)?;

    if cursor + 8 >= buffer.len() {
        Err(SJFLError::UnexpectedEof(Span::binary()))
    }

    else {
        Ok(((buffer[cursor] as u64) << 56)
        + ((buffer[cursor + 1] as u64) << 48)
        + ((buffer[cursor + 2] as u64) << 40)
        + ((buffer[cursor + 3] as u64) << 32)
        + ((buffer[cursor + 4] as u64) << 24)
        + ((buffer[cursor + 5] as u64) << 16)
        + ((buffer[cursor + 6] as u64) <<  8)
        + (buffer[cursor + 7] as u64))
    }

}