use crate::blockchain::coin::Coin;
use crate::blockchain::npc_result::NPCResult;
use crate::clvm::sexp::{AtomBuf, SExp};
use dg_xch_serialize::hash_256;
use num_bigint::BigInt;
use num_traits::{Num, Zero};
use once_cell::sync::Lazy;
use regex::Regex;
use std::io::{Error, ErrorKind};

pub const NO_NEG_DIV: u32 = 0x0001;
pub const NO_UNKNOWN_OPS: u32 = 0x0002;
pub const COND_CANON_INTS: u32 = 0x010000;
pub const NO_UNKNOWN_CONDS: u32 = 0x20000;
pub const COND_ARGS_NIL: u32 = 0x40000;
pub const STRICT_ARGS_COUNT: u32 = 0x80000;
pub const MEMPOOL_MODE: u32 =
    NO_NEG_DIV | COND_CANON_INTS | NO_UNKNOWN_CONDS | NO_UNKNOWN_OPS | COND_ARGS_NIL;
pub const INFINITE_COST: u64 = 0x7FFFFFFFFFFFFFFF;

pub fn tree_hash(sexp: &SExp) -> Vec<u8> {
    match sexp {
        SExp::Pair(pair) => {
            let mut byte_buf = Vec::new();
            byte_buf.push(2);
            byte_buf.append(&mut tree_hash(&pair.first));
            byte_buf.append(&mut tree_hash(&pair.rest));
            hash_256(&byte_buf)
        }
        SExp::Atom(atom) => {
            let mut byte_buf = Vec::new();
            byte_buf.push(1);
            byte_buf.extend(&atom.data);
            hash_256(&byte_buf)
        }
    }
}

pub fn check_cost(cost: u64, max_cost: u64) -> Result<(), Error> {
    if cost > max_cost {
        Err(Error::new(ErrorKind::InvalidData, "cost exceeded"))
    } else {
        Ok(())
    }
}

pub fn check_arg_count(args: &SExp, expected: usize, name: &str) -> Result<(), Error> {
    if arg_count(args, expected) != expected {
        Err(Error::new(
            ErrorKind::InvalidData,
            format!(
                "{} takes exactly {} argument{}",
                name,
                expected,
                if expected == 1 { "" } else { "s" }
            ),
        ))
    } else {
        Ok(())
    }
}

pub fn arg_count(args: &SExp, return_early_if_exceeds: usize) -> usize {
    let mut count = 0;
    let mut ptr = args;
    while let Ok(pair) = ptr.pair() {
        ptr = &pair.rest;
        count += 1;
        if count > return_early_if_exceeds {
            break;
        };
    }
    count
}

pub fn int_atom<'a>(args: &'a SExp, op_name: &str) -> Result<&'a [u8], Error> {
    args.atom().map(|b| b.data.as_slice()).map_err(|_| {
        Error::new(
            ErrorKind::InvalidInput,
            format!("{} requires int args", op_name),
        )
    })
}

pub fn atom<'a>(args: &'a SExp, op_name: &str) -> Result<&'a [u8], Error> {
    args.atom()
        .map(|b| b.data.as_slice())
        .map_err(|_| Error::new(ErrorKind::InvalidInput, format!("{} on list", op_name)))
}

pub fn two_ints(args: &SExp, op_name: &str) -> Result<(BigInt, usize, BigInt, usize), Error> {
    check_arg_count(args, 2, op_name)?;
    let a0 = args.first()?;
    let a1 = args.rest()?.first()?;
    let n0 = int_atom(a0, op_name)?;
    let n1 = int_atom(a1, op_name)?;
    Ok((number_from_u8(n0), n0.len(), number_from_u8(n1), n1.len()))
}

pub fn ptr_from_number(item: &BigInt) -> Result<SExp, Error> {
    let bytes: Vec<u8> = item.to_signed_bytes_be();
    let mut slice = bytes.as_slice();

    // make number minimal by removing leading zeros
    while (!slice.is_empty()) && (slice[0] == 0) {
        if slice.len() > 1 && (slice[1] & 0x80 == 0x80) {
            break;
        }
        slice = &slice[1..];
    }
    Ok(SExp::Atom(slice.to_vec().into()))
}

pub fn number_from_u8(v: &[u8]) -> BigInt {
    if v.is_empty() {
        0.into()
    } else {
        BigInt::from_signed_bytes_be(v)
    }
}

fn u32_from_u8_impl(buf: &[u8], signed: bool) -> Option<u32> {
    if buf.is_empty() {
        return Some(0);
    }
    // too many bytes for u32
    if buf.len() > 4 {
        return None;
    }
    let sign_extend = (buf[0] & 0x80) != 0;
    let mut ret: u32 = if signed && sign_extend { 0xffffffff } else { 0 };
    for b in buf {
        ret <<= 8;
        ret |= *b as u32;
    }
    Some(ret)
}

pub fn u32_from_u8(buf: &[u8]) -> Option<u32> {
    u32_from_u8_impl(buf, false)
}

pub fn i32_from_u8(buf: &[u8]) -> Option<i32> {
    u32_from_u8_impl(buf, true).map(|v| v as i32)
}

pub fn i32_atom(args: &SExp, op_name: &str) -> Result<i32, Error> {
    let buf = match args.atom() {
        Ok(a) => a,
        _ => {
            return Err(Error::new(
                ErrorKind::InvalidData,
                format!("{} requires int32 args", op_name),
            ));
        }
    };
    match i32_from_u8(&buf.data) {
        Some(v) => Ok(v),
        _ => Err(Error::new(
            ErrorKind::InvalidData,
            format!("{} requires int32 args (with no leading zeros)", op_name),
        )),
    }
}

pub fn new_substr(node: &SExp, start: usize, end: usize) -> Result<SExp, Error> {
    let atom = &node.atom()?.data;
    if start > atom.len() {
        return Err(Error::new(
            ErrorKind::InvalidData,
            format!("substr start out of bounds: {} is > {}", start, atom.len()),
        ));
    }
    if end > atom.len() {
        return Err(Error::new(
            ErrorKind::InvalidData,
            format!("substr end out of bounds: {} is > {}", end, atom.len()),
        ));
    }
    if end < start {
        return Err(Error::new(
            ErrorKind::InvalidData,
            format!("substr invalid bounds: {} is > {}", start, end),
        ));
    }
    let sub = SExp::Atom(AtomBuf {
        data: atom[start..end].to_vec(),
    });
    Ok(sub)
}

pub fn new_concat<'a>(nodes: &'a [&'a SExp]) -> Result<SExp, Error> {
    let mut buf = vec![];
    for node in nodes {
        let atom = node.atom()?;
        buf.extend(&atom.data);
    }
    let new_atom = SExp::Atom(AtomBuf { data: buf });
    Ok(new_atom)
}

pub fn encode_bigint(int: BigInt) -> Result<Vec<u8>, Error> {
    if int == BigInt::zero() {
        Ok(vec![])
    } else {
        let length = (int.bits() + 8) >> 3;
        let bytes = int_to_bytes(int, length as usize, true)?;
        let mut slice = bytes.as_slice();
        while slice.len() > 1 && slice[0] == (if (slice[1] & 0x80) != 0 { 0xFF } else { 0 }) {
            slice = &slice[1..];
        }
        Ok(slice.to_vec())
    }
}

static RE: Lazy<Regex> = Lazy::new(|| Regex::new("[01]{8}").unwrap());

pub fn int_to_bytes(value: BigInt, size: usize, signed: bool) -> Result<Vec<u8>, Error> {
    let is_neg = value < BigInt::zero();
    if is_neg && !signed {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            "Cannot convert negative int to unsigned.",
        ));
    }
    let pad_len = size * 8;
    let mut binary = format!(
        "{:>0pad_len$}",
        format!("{}", if is_neg { -value } else { value }.to_str_radix(2))
    );
    if is_neg {
        binary = format!(
            "{:>0pad_len$}",
            &(BigInt::from_str_radix(&binary, 2)
                .map_err(|_| { Error::new(ErrorKind::InvalidInput, "Failed to build big int",) })?
                .to_str_radix(2)
                .chars()
                .rev()
                .collect::<String>())
        );
    }
    let bytes = RE
        .captures_iter(&binary)
        .map(|m| -> u8 { u8::from_str_radix(m.get(0).unwrap().as_str(), 2).unwrap() })
        .collect();
    Ok(bytes)
}

pub fn additions_for_npc(npc_result: NPCResult) -> Vec<Coin> {
    let mut additions: Vec<Coin> = vec![];
    if let Some(conds) = npc_result.conds {
        for spend in conds.spends {
            for (puzzle_hash, amount, _) in spend.create_coin {
                additions.push(Coin {
                    parent_coin_info: spend.coin_id,
                    puzzle_hash,
                    amount,
                });
            }
        }
    }
    additions
}
