use num_bigint::Sign;
use std::i64;
use std::rc::Rc;
use crate::label::Label;
use crate::program::{Program, Command, Integer, BigInteger, SizedInteger, SourceLoc};
use crate::WsError;
use crate::WsErrorKind::ParseError;
#[derive(Debug, Clone)]
struct ParseState<'a> {
    line: usize,
    column: usize,
    index: usize,
    item: Option<u8>,
    buffer: &'a [u8]
}
impl<'a> Iterator for ParseState<'a> {
    type Item = u8;
    fn next(&mut self) -> Option<u8> {
        loop {
            match self.item {
                Some(b'\n') => {self.index += 1; self.line   += 1; self.column = 1;},
                Some(_)     => {self.index += 1; self.column += 1;}
                None        => ()
            }
            return match self.buffer.get(self.index) {
                Some(&new) => {
                    self.item = Some(new);
                    match new {
                        b'\n' | b'\t' | b' ' => Some(new),
                        _ => continue
                    }
                } 
                None => None
            }
        }
    }
}
impl<'a> ParseState<'a> {
    fn new(buffer: &'a [u8]) -> ParseState<'a> {
        ParseState {line: 1, column: 1, index: 0, item: None, buffer: buffer}
    }
    fn parse_arg(&mut self) -> Result<SizedInteger, WsError> {
        let mut accum = 0u64;
        let negative;
        negative = match self.next() {
            Some(b'\n') => return Ok(SizedInteger::Small(0)),
            Some(b'\t') => true,
            Some(b' ' ) => false,
            None        => return Err(WsError::new(ParseError(self.line, self.column, self.index), "Hit EOF while expecting argument")),
            _           => unreachable!()
        };
        loop {
            match self.next() {
                Some(byte) => {
                    accum = accum << 1 | match byte {
                        b'\n' => return Ok(SizedInteger::Small(if negative {
                            -(accum as Integer)
                        } else {
                            accum as Integer
                        })),
                        b'\t' => 1,
                        b' '  => 0,
                        _     => unreachable!()
                    };
                    if accum > i64::MAX as u64 {
                        break;
                    }
                }
                None => return Err(WsError::new(ParseError(self.line, self.column, self.index), "Hit EOF while parsing argument"))
            }
        }
        let mut segments: Vec<u32> = Vec::new();
        segments.push((accum >> 32) as u32);
        segments.push(accum as u32);
        let mut bit = 31;
        let mut accum = 0u32;
        while let Some(byte) = self.next() {
            accum |= match byte {
                b'\n' => {
                    segments.push(accum);
                    segments.reverse();
                    segments.push(0);
                    bit += 1; for i in 0..segments.len() - 1 {
                        segments[i] = ((segments[i] as u64 | ((segments[i + 1] as u64)<< 32)) >> bit) as u32;
                    };
                    return Ok(SizedInteger::Big(BigInteger::new(if negative {Sign::Minus} else {Sign::Plus}, segments)));
                },
                b'\t' => 1,
                b' '  => 0,
                _     => unreachable!()
            } << bit;
            if bit == 0 {
                segments.push(accum);
                bit = 32;
                accum = 0;
            }
            bit -= 1;
        }
        Err(WsError::new(ParseError(self.line, self.column, self.index), "Hit EOF while parsing argument"))
    }
    fn parse_label(&mut self) -> Result<Label, WsError> {
        let mut label = Label::new();
        while let Some(byte) = self.next() {
            match byte {
                b'\n' => return Ok(label),
                b'\t' => label.push(true),
                b' '  => label.push(false),
                _ => unreachable!()
            };
        }
        Err(WsError::new(ParseError(self.line, self.column, self.index), "Hit EOF while parsing label"))
    }
}
impl Program {
    pub fn parse(code: Vec<u8>) -> Result<Program, WsError> {
        let mut commands = Vec::<Command>::new();
        let mut sourcelocs = Vec::<SourceLoc>::new();
        {
            let mut state = ParseState::new(&code);
            let mut hash = 0;
            let mut hash_length = 0;
            let mut startline = 0usize;
            let mut startcolumn = 0usize;
            let mut startindex = 0usize;
            while let Some(byte) = state.next() {
                if hash_length == 0 {
                    startline   = state.line;
                    startcolumn = state.column;
                    startindex  = state.index;
                }
                hash = hash * 3 + match byte {
                    b' '  => 0,
                    b'\t' => 1,
                    b'\n' => 2,
                    _     => unreachable!()
                };
                hash_length += 1;
                let command = match (hash_length, hash) {
                    (2, 0)  => match state.parse_arg()? {
                        SizedInteger::Small(value) => Command::Push {value: value},
                        SizedInteger::Big(value) => Command::PushBig {value: value}
                    },
                    (3, 6)  => Command::Duplicate,
                    (3, 3)  => match state.parse_arg()? {
                        SizedInteger::Small(value) => Command::Copy {index: value as usize},
                        SizedInteger::Big(value) => return Err(WsError::new(ParseError(startline, startcolumn, startindex), format!("Copy argument too large: {}", value)))
                    },
                    (3, 7)  => Command::Swap,
                    (3, 8)  => Command::Discard,
                    (3, 5)  => match state.parse_arg()? {
                        SizedInteger::Small(value) => Command::Slide {amount: value as usize},
                        SizedInteger::Big(value) => return Err(WsError::new(ParseError(startline, startcolumn, startindex), format!("Slide argument too large: {}", value)))
                    },
                    (4, 27) => Command::Add,
                    (4, 28) => Command::Subtract,
                    (4, 29) => Command::Multiply,
                    (4, 30) => Command::Divide,
                    (4, 31) => Command::Modulo,
                    (3, 12) => Command::Set,
                    (3, 13) => Command::Get,
                    (3, 18) => Command::Label,
                    (3, 19) => Command::Call {index: 0},
                    (3, 20) => Command::Jump {index: 0},
                    (3, 21) => Command::JumpIfZero {index: 0},
                    (3, 22) => Command::JumpIfNegative {index: 0},
                    (3, 23) => Command::EndSubroutine,
                    (3, 26) => Command::EndProgram,
                    (4, 45) => Command::PrintChar,
                    (4, 46) => Command::PrintNum,
                    (4, 48) => Command::InputChar,
                    (4, 49) => Command::InputNum,
                    (3, 4) | 
                    (3, 11) | 
                    (3, 14) | 
                    (3, 17) | 
                    (3, 24) | 
                    (3, 25) | 
                    (4, _) => {
                        let mut buf = [0u8; 5];
                        for i in 0 .. hash_length {
                            buf[i] = hash % 3;
                            hash /= 3;
                        }
                        let s = buf[..hash_length].iter().rev().map(|c| if *c == 0 {
                            'S'
                        } else if *c == 1 {
                            'T'
                        } else {
                            'N'
                        }).collect::<String>();
                        return Err(WsError::new(ParseError(startline, startcolumn, startindex), format!("invalid command: {}", s)));
                    },
                    (_, _) => continue
                };
                hash = 0;
                hash_length = 0;
                let label = match command {
                    Command::Label | Command::Call {..} | Command::Jump {..} |
                    Command::JumpIfZero {..} | Command::JumpIfNegative {..} => Some(state.parse_label()?.into()),
                    _ => None
                };
                commands.push(command);
                sourcelocs.push(SourceLoc {
                    line: startline,
                    column: startcolumn,
                    span: startindex .. state.index + 1,
                    label: label
                });
            }
            if hash_length != 0 {
                return Err(WsError::new(ParseError(state.line, state.column, state.index), "Hit EOF while parsing command"));
            }
        }
        let mut program = Program {
            source: Some(code),
            commands: commands,
            locs: Some(sourcelocs),
            source_is_whitespace: true
        };
        program.compile()?;
        Ok(program)
    }
    pub fn dump(&self) -> Vec<u8> {
        let mut buffer = Vec::<u8>::new();
        use crate::program::Command::*;
        for (index, command) in self.commands.iter().enumerate() {
            let label = self.locs.as_ref().and_then(|l| l[index].label.as_ref());
            let (code, arg): (&[u8], _) = match *command {
                Push {value}           => (b"  ", Some(number_to_ws(value))),
                PushBig {ref value}    => (b"  ", Some(large_number_to_ws(value))),
                Duplicate              => (b" \n ", None),
                Copy {index}           => (b" \t ", Some(number_to_ws(index as Integer))),
                Swap                   => (b" \n\t", None),
                Discard                => (b" \n\n", None),
                Slide {amount}         => (b" \t\n", Some(number_to_ws(amount as Integer))),
                Add                    => (b"\t   ", None),
                Subtract               => (b"\t  \t", None),
                Multiply               => (b"\t  \n", None),
                Divide                 => (b"\t \t ", None),
                Modulo                 => (b"\t \t\t", None),
                Set                    => (b"\t\t ", None),
                Get                    => (b"\t\t\t", None),
                Label                  => (b"\n  ", Some(label_to_ws(label, index))),
                Call {index}           => (b"\n \t", Some(label_to_ws(label, index - 1))),
                Jump {index}           => (b"\n \n", Some(label_to_ws(label, index - 1))),
                JumpIfZero {index}     => (b"\n\t ", Some(label_to_ws(label, index - 1))),
                JumpIfNegative {index} => (b"\n\t\t", Some(label_to_ws(label, index - 1))),
                EndSubroutine          => (b"\n\t\n", None),
                EndProgram             => (b"\n\n\n", None),
                PrintChar              => (b"\t\n  ", None),
                PrintNum               => (b"\t\n \t", None),
                InputChar              => (b"\t\n\t ", None),
                InputNum               => (b"\t\n\t\t", None),
            };
            buffer.extend(code);
            if let Some(arg) = arg {
                buffer.extend(arg);
            }
        }
        buffer
    }
}
use std::mem::size_of;
fn number_to_ws(mut n: Integer) -> Vec<u8> {
    let mut res = Vec::new();
    if n < 0 {
        n = -n;
        res.push(b'\t');
    } else {
        res.push(b' ');
    }
    let n = n as usize;
    let mut i = size_of::<usize>() * 8;
    let mut force = false;
    while i != 0 {
        i -= 1;
        if (n & (1 << i)) != 0 {
            force = true;
            res.push(b'\t');
        } else if force {
            res.push(b' ');
        }
    }
    res.push(b'\n');
    res
}
fn large_number_to_ws(n: &BigInteger) -> Vec<u8> {
    let mut res = Vec::new();
    let (sign, bytes) = n.to_bytes_be();
    match sign {
        Sign::Minus  => res.push(b'\t'),
        Sign::Plus   => res.push(b' '),
        Sign::NoSign => {
            res.push(b'\n');
            return res;
        }
    }
    let mut force = false;
    for byte in bytes {
        for n in 0..8 {
            if byte & (1 << (7 - n)) != 0 {
                force = true;
                res.push(b'\t');
            } else if force {
                res.push(b' ');
            }
        }
    }
    res.push(b'\n');
    return res;
}
fn label_to_ws(label: Option<&Rc<Label>>, i: usize) -> Vec<u8> {
    let label_storage: Label;
    let label = if let Some(l) = label {
        l
    } else {
        label_storage =  i.to_string().as_bytes().into();
        &label_storage
    };
    let mut res: Vec<u8> = (&label).into_iter()
                                   .map(|i| if i {b'\t'} else {b' '})
                                   .collect();
    res.push(b'\n');
    res
}