use std::mem::swap;
use std::rc::Rc;

use crate::classic::clvm::__type_compatibility__::{
    Bytes, BytesFromType, Stream, UnvalidatedBytesFromType,
};
use crate::classic::clvm::casts::bigint_to_bytes_clvm;
use crate::classic::clvm::syntax_error::SyntaxErr;
use crate::classic::clvm_tools::ir::r#type::{IRRepr, NEW_BIT_CONSTANTS};
use crate::util::Number;

const BIT_CHAR_LIST: [u8; 16] = *b"0123456789abcdef";

pub struct IRReader {
    stream: Stream,
    language_flags: u32,
}

// XXX Allows us to track line and column later if desired.
impl IRReader {
    fn read(&mut self, n: usize) -> Bytes {
        self.stream.read(n)
    }

    fn backup(&mut self, n: usize) {
        let cur_seek = self.stream.get_seek();
        if n > cur_seek {
            self.stream.set_seek(0);
        } else {
            self.stream.set_seek((cur_seek - n) as i64);
        }
    }

    pub fn read_expr(&mut self) -> Result<IRRepr, SyntaxErr> {
        consume_object(self, self.language_flags)
    }

    pub fn new(s: Stream, flags: u32) -> Self {
        IRReader {
            stream: s,
            language_flags: flags,
        }
    }
}

pub fn is_eol(chval: u8) -> bool {
    chval == b'\r' || chval == b'\n'
}

pub fn is_space(chval: u8) -> bool {
    chval == b' ' || chval == b'\t' || is_eol(chval)
}

pub fn consume_whitespace(s: &mut IRReader) {
    let mut in_comment = false;

    // This also deals with comments
    // eslint-disable-next-line no-constant-condition
    loop {
        let b = s.read(1);
        if b.length() == 0 {
            return;
        }

        let ch = b.at(0);
        if in_comment {
            if is_eol(ch) {
                in_comment = false;
            } else {
                continue;
            }
        }

        if ch == b';' {
            in_comment = true;
            continue;
        }

        if is_space(ch) {
            continue;
        }

        break;
    }

    s.backup(1);
}

pub fn consume_quoted(s: &mut IRReader, q: u8) -> Result<IRRepr, SyntaxErr> {
    let starting_at = s.stream.get_seek() - 1;
    let mut bs = false;
    let mut qchars = vec![q];

    loop {
        let b = s.read(1);
        if b.length() == 0 {
            return Err(SyntaxErr::new(format!(
                "unterminated string starting at {}: {}",
                starting_at,
                Bytes::new(Some(BytesFromType::Raw(qchars))).decode()
            )));
        }

        if bs {
            bs = false;
            qchars.push(b.at(0));
        } else if b.at(0) == b'\\' {
            bs = true;
        } else if b.at(0) == q {
            break;
        } else {
            qchars.push(b.at(0));
        }
    }

    // Exclude first quote that was captured.
    qchars = qchars.iter().skip(1).copied().collect();
    Ok(IRRepr::Quotes(Bytes::new(Some(BytesFromType::Raw(qchars)))))
}

pub fn is_hex(chars: &[u8]) -> bool {
    chars.len() > 2 && chars[0] == b'0' && (chars[1] == b'x' || chars[1] == b'X')
}

pub fn is_dec(chars: &[u8]) -> bool {
    let mut first = true;

    for ch in chars.iter() {
        if first {
            first = false;
            if *ch == b'-' {
                continue;
            }
        }
        if *ch > b'9' || *ch < b'0' {
            return false;
        }
    }

    true
}

fn new_bit_constants(language_flags: u32) -> bool {
    (language_flags & NEW_BIT_CONSTANTS) != 0
}

pub fn bitwise_constant(bits: u32, chars: &[u8], force_byte: bool) -> Result<Vec<u8>, SyntaxErr> {
    let radix = 1 << bits;
    let mut current_bit: u32 = 0;
    let mut bit_buffer: u16 = 0;
    let mut out_data = Vec::new();
    for ch in chars.iter().map(|c| c | 0x20).rev() {
        if let Some(found) = BIT_CHAR_LIST
            .iter()
            .enumerate()
            .take(radix)
            .filter(|(_, the_char)| **the_char == ch)
            .map(|(i, _)| i)
            .next()
        {
            bit_buffer |= (found << current_bit) as u16;
            current_bit += bits;
            if current_bit > 8 {
                current_bit -= 8;
                out_data.push((bit_buffer & 0xff) as u8);
                bit_buffer >>= 8;
            }
        } else {
            return Err(SyntaxErr::new(format!(
                "bitwise constant with {bits} bits, have char {}",
                ch as char
            )));
        }
    }

    if (chars.len() > 1 || chars[0] != b'0' || force_byte)
        && (current_bit >= bits || (current_bit > 0 && (bit_buffer & 0xff) != 0))
    {
        out_data.push((bit_buffer & 0xff) as u8);
    }

    Ok(out_data.iter().rev().copied().collect())
}

pub fn interpret_atom_value(chars: &[u8], language_flags: u32) -> Result<IRRepr, SyntaxErr> {
    // The Decimal and Hex representation of atoms in the program
    let all_int_digits = || {
        String::from_utf8(chars.to_vec())
            .ok()
            .and_then(|s| s.parse::<Number>().ok())
            .map(|n| bigint_to_bytes_clvm(&n))
    };

    if chars.is_empty() {
        Ok(IRRepr::Null)
    } else if new_bit_constants(language_flags) && !chars.is_empty() && chars[0] == b'0' {
        if chars.len() == 1 {
            // A '0' decimal constant.
            return Ok(IRRepr::Int(bigint_to_bytes_clvm(&Number::default()), true));
        }

        if chars.len() < 3 {
            // Not allowed.
            return Err(SyntaxErr::new(
                "too short numeric constant starting with '0'".to_string(),
            ));
        }
        match (chars[1], all_int_digits()) {
            (b'b', _) | (b'B', _) => Ok(IRRepr::Binary(Bytes::new(Some(BytesFromType::Raw(
                bitwise_constant(1, &chars[2..], false)?,
            ))))),
            (b'o', _) | (b'O', _) => Ok(IRRepr::Octal(Bytes::new(Some(BytesFromType::Raw(
                bitwise_constant(3, &chars[2..], false)?,
            ))))),
            (b'x', _) | (b'X', _) => Ok(IRRepr::Hex(Bytes::new(Some(BytesFromType::Raw(
                bitwise_constant(4, &chars[2..], true)?,
            ))))),
            (b'0', Some(decimal)) => Ok(IRRepr::Int(decimal, true)),
            _ => Err(SyntaxErr::new(format!(
                "malformed int or bit constant '{}'",
                String::from_utf8_lossy(chars)
            ))),
        }
    } else if is_hex(chars) {
        let mut string_bytes = if !chars.len().is_multiple_of(2) {
            // Pad an odd-length hex constant from the program text
            // with a zero in the High nibble
            Bytes::new(Some(BytesFromType::Raw(vec![b'0'])))
        } else {
            // This is the even-length hex constant case
            Bytes::new(None)
        };
        string_bytes =
            string_bytes.concat(&Bytes::new(Some(BytesFromType::Raw(chars[2..].to_vec()))));

        Bytes::new_validated(Some(UnvalidatedBytesFromType::Hex(string_bytes.decode())))
            .map(IRRepr::Hex)
    } else if let Some(n) = all_int_digits() {
        Ok(IRRepr::Int(n, true))
    } else {
        let string_bytes = Bytes::new(Some(BytesFromType::Raw(chars.to_vec())));
        Ok(IRRepr::Symbol(string_bytes.decode()))
    }
}

pub fn consume_atom(
    s: &mut IRReader,
    b: &Bytes,
    language_flags: u32,
) -> Result<Option<IRRepr>, SyntaxErr> {
    let mut result_vec = b.data().to_vec();
    loop {
        let b = s.read(1);
        if b.length() == 0 {
            if result_vec.is_empty() {
                return Ok(None);
            } else {
                return interpret_atom_value(&result_vec, language_flags).map(Some);
            }
        }

        if b.at(0) == b'(' || b.at(0) == b')' || is_space(b.at(0)) {
            s.backup(1);
            return interpret_atom_value(&result_vec, language_flags).map(Some);
        }

        result_vec.push(b.at(0));
    }
}

fn enlist_ir(vec: &mut [IRRepr], tail: IRRepr) -> IRRepr {
    let mut result = tail;
    for i_reverse in 0..vec.len() {
        let i = vec.len() - i_reverse - 1;
        let mut next_head = IRRepr::Null;
        swap(&mut vec[i], &mut next_head);
        result = IRRepr::Cons(Rc::new(next_head), Rc::new(result));
    }
    result
}

pub fn consume_cons_body(s: &mut IRReader, language_flags: u32) -> Result<IRRepr, SyntaxErr> {
    let mut result = vec![];

    loop {
        consume_whitespace(s);

        let b = s.read(1);
        if b.length() == 0 {
            return Err(SyntaxErr::new("missing )".to_string()));
        }

        if b.at(0) == b')' {
            return Ok(enlist_ir(&mut result, IRRepr::Null));
        }

        if b.at(0) == b'(' {
            let v = consume_cons_body(s, language_flags)?;
            result.push(v);
            continue;
        }

        if b.at(0) == b'.' {
            consume_whitespace(s);
            let v = consume_object(s, language_flags)?;
            consume_whitespace(s);
            let b = s.read(1);
            if b.length() == 0 || b.at(0) != b')' {
                return Err(SyntaxErr::new("missing )".to_string()));
            }
            return Ok(enlist_ir(&mut result, v));
        }

        if b.at(0) == b'\"' || b.at(0) == b'\'' {
            let v = consume_quoted(s, b.at(0))?;
            result.push(v);
            continue;
        } else if let Some(f) = consume_atom(s, &b, language_flags)? {
            result.push(f);
            continue;
        } else {
            return Err(SyntaxErr::new("missing )".to_string()));
        }
    }
}

pub fn consume_object(s: &mut IRReader, language_flags: u32) -> Result<IRRepr, SyntaxErr> {
    consume_whitespace(s);
    let b = s.read(1);

    if b.length() == 0 {
        Ok(IRRepr::Null)
    } else if b.at(0) == b'(' {
        consume_cons_body(s, language_flags)
    } else if b.at(0) == b'\"' || b.at(0) == b'\'' {
        consume_quoted(s, b.at(0))
    } else if let Some(ir) = consume_atom(s, &b, language_flags)? {
        Ok(ir)
    } else {
        Err(SyntaxErr::new("empty stream".to_string()))
    }
}

pub fn read_ir(s: &str, language_flags: u32) -> Result<IRRepr, SyntaxErr> {
    let bytes_of_string = Bytes::new(Some(BytesFromType::Raw(s.as_bytes().to_vec())));
    let stream = Stream::new(Some(bytes_of_string));
    let mut reader = IRReader::new(stream, language_flags);
    reader.read_expr()
}
