use fuel_asm::Word;
use fuel_crypto::Hasher;
use fuel_tx::{Bytes32, ContractId};
use serde::{Deserialize, Serialize};
use std::hash::Hash;
use std::path::{Path, PathBuf};
use std::{io, iter, slice};
pub mod constants;
pub mod ident;
pub mod u256;
pub use ident::*;
pub mod integer_bits;
pub mod source_engine;
pub use source_engine::*;
pub mod span;
pub use span::*;
pub mod state;
pub mod style;
pub mod ast;
pub type Id = [u8; Bytes32::LEN];
pub type Contract = [u8; ContractId::LEN];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Position {
    pub line: usize,
    pub col: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Range {
    pub start: Position,
    pub end: Position,
}
impl Range {
    pub const fn is_valid(&self) -> bool {
        self.start.line < self.end.line
            || self.start.line == self.end.line && self.start.col <= self.end.col
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Instruction {
    pub pc: Word,
    pub range: Range,
    pub exit: bool,
}
impl Instruction {
    pub fn to_bytes(&self) -> [u8; 41] {
        let mut bytes = [0u8; 41];
        bytes[..8].copy_from_slice(&(self.pc).to_be_bytes());
        bytes[8..16].copy_from_slice(&(self.range.start.line as u64).to_be_bytes());
        bytes[16..24].copy_from_slice(&(self.range.start.col as u64).to_be_bytes());
        bytes[24..32].copy_from_slice(&(self.range.end.line as u64).to_be_bytes());
        bytes[32..40].copy_from_slice(&(self.range.end.col as u64).to_be_bytes());
        bytes[40] = self.exit as u8;
        bytes
    }
    pub fn bytes<'a>(iter: impl Iterator<Item = &'a Self>) -> Vec<u8> {
        iter.map(Self::to_bytes)
            .fold::<Vec<u8>, _>(vec![], |mut v, b| {
                v.extend(b);
                v
            })
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub struct ModuleId {
    id: u16,
}
impl ModuleId {
    pub fn new(id: u16) -> Self {
        Self { id }
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub struct SourceId {
    id: u32,
}
impl SourceId {
    const RESERVED: u16 = 0;
    const SOURCE_ID_BITS: u32 = 20;
    const SOURCE_ID_MASK: u32 = (1 << Self::SOURCE_ID_BITS) - 1;
    pub fn new(module_id: u16, source_id: u32) -> Self {
        SourceId {
            id: ((module_id as u32) << Self::SOURCE_ID_BITS) | source_id,
        }
    }
    pub fn reserved() -> Self {
        Self::new(Self::RESERVED, Self::RESERVED as u32)
    }
    pub fn module_id(&self) -> ModuleId {
        ModuleId::new((self.id >> Self::SOURCE_ID_BITS) as u16)
    }
    pub fn source_id(&self) -> u32 {
        self.id & Self::SOURCE_ID_MASK
    }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub struct Source {
    path: PathBuf,
}
impl<T> From<T> for Source
where
    T: Into<PathBuf>,
{
    fn from(path: T) -> Self {
        Self { path: path.into() }
    }
}
impl AsRef<PathBuf> for Source {
    fn as_ref(&self) -> &PathBuf {
        &self.path
    }
}
impl AsRef<Path> for Source {
    fn as_ref(&self) -> &Path {
        self.path.as_ref()
    }
}
impl AsMut<PathBuf> for Source {
    fn as_mut(&mut self) -> &mut PathBuf {
        &mut self.path
    }
}
impl Source {
    pub fn bytes(&self) -> io::Result<slice::Iter<'_, u8>> {
        Ok(self
            .path
            .as_path()
            .to_str()
            .ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::Other,
                    "Failed to get the string representation of the path!",
                )
            })?
            .as_bytes()
            .iter())
    }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CallFrame {
    id: Id,
    contract: Contract,
    source: Source,
    range: Range,
    program: Vec<Instruction>,
}
impl CallFrame {
    pub fn new(
        contract: ContractId,
        source: Source,
        range: Range,
        program: Vec<Instruction>,
    ) -> io::Result<Self> {
        Context::validate_source(&source)?;
        Context::validate_range(iter::once(&range).chain(program.iter().map(|p| &p.range)))?;
        let contract = Contract::from(contract);
        let id = Context::id_from_repr(
            Instruction::bytes(program.iter())
                .iter()
                .chain(contract.iter())
                .chain(source.bytes()?),
        );
        Ok(Self {
            id,
            contract,
            source,
            range,
            program,
        })
    }
    pub const fn id(&self) -> &Id {
        &self.id
    }
    pub const fn source(&self) -> &Source {
        &self.source
    }
    pub const fn range(&self) -> &Range {
        &self.range
    }
    pub fn program(&self) -> &[Instruction] {
        self.program.as_slice()
    }
    pub fn contract(&self) -> ContractId {
        self.contract.into()
    }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TransactionScript {
    id: Id,
    source: Source,
    range: Range,
    program: Vec<Instruction>,
}
impl TransactionScript {
    pub fn new(source: Source, range: Range, program: Vec<Instruction>) -> io::Result<Self> {
        Context::validate_source(&source)?;
        Context::validate_range(iter::once(&range).chain(program.iter().map(|p| &p.range)))?;
        let id = Context::id_from_repr(
            Instruction::bytes(program.iter())
                .iter()
                .chain(source.bytes()?),
        );
        Ok(Self {
            id,
            source,
            range,
            program,
        })
    }
    pub const fn id(&self) -> &Id {
        &self.id
    }
    pub const fn source(&self) -> &Source {
        &self.source
    }
    pub const fn range(&self) -> &Range {
        &self.range
    }
    pub fn program(&self) -> &[Instruction] {
        self.program.as_slice()
    }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Context {
    CallFrame(CallFrame),
    TransactionScript(TransactionScript),
}
impl From<CallFrame> for Context {
    fn from(frame: CallFrame) -> Self {
        Self::CallFrame(frame)
    }
}
impl From<TransactionScript> for Context {
    fn from(script: TransactionScript) -> Self {
        Self::TransactionScript(script)
    }
}
impl Context {
    pub fn validate_source<P>(path: P) -> io::Result<()>
    where
        P: AsRef<Path>,
    {
        if !path.as_ref().is_absolute() {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "The source path must be absolute!",
            ));
        }
        if !path.as_ref().is_file() {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "The source path must be a valid Sway source file!",
            ));
        }
        if !path.as_ref().exists() {
            return Err(io::Error::new(
                io::ErrorKind::NotFound,
                "The source path must point to an existing file!",
            ));
        }
        Ok(())
    }
    pub fn validate_range<'a>(mut range: impl Iterator<Item = &'a Range>) -> io::Result<()> {
        if !range.any(|r| !r.is_valid()) {
            Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "The provided source range is inconsistent!",
            ))
        } else {
            Ok(())
        }
    }
    pub fn id_from_repr<'a>(bytes: impl Iterator<Item = &'a u8>) -> Id {
        let bytes: Vec<u8> = bytes.copied().collect();
        *Hasher::hash(bytes.as_slice())
    }
    pub const fn id(&self) -> &Id {
        match self {
            Self::CallFrame(t) => t.id(),
            Self::TransactionScript(t) => t.id(),
        }
    }
    pub const fn source(&self) -> &Source {
        match self {
            Self::CallFrame(t) => t.source(),
            Self::TransactionScript(t) => t.source(),
        }
    }
    pub const fn range(&self) -> &Range {
        match self {
            Self::CallFrame(t) => t.range(),
            Self::TransactionScript(t) => t.range(),
        }
    }
    pub fn program(&self) -> &[Instruction] {
        match self {
            Self::CallFrame(t) => t.program(),
            Self::TransactionScript(t) => t.program(),
        }
    }
    pub fn contract(&self) -> Option<ContractId> {
        match self {
            Self::CallFrame(t) => Some(t.contract()),
            _ => None,
        }
    }
}
pub type FxBuildHasher = std::hash::BuildHasherDefault<rustc_hash::FxHasher>;
pub type FxIndexMap<K, V> = indexmap::IndexMap<K, V, FxBuildHasher>;
pub type FxIndexSet<K> = indexmap::IndexSet<K, FxBuildHasher>;