// Copyright (c) 2021, BlockProject 3D
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//     * Neither the name of BlockProject 3D nor the names of its contributors
//       may be used to endorse or promote products derived from this software
//       without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

//! Contains utilities to work with the symbol table section.

use byteorder::{ByteOrder, LittleEndian};

use crate::{
    core::header::Struct,
    sd::Object,
    shader::{
        error::{EosContext, InvalidCodeContext, ReadError},
        Stage
    },
    table::Item
};

/// Indicates this symbol is used on the vertex stage.
pub const FLAG_VERTEX_STAGE: u16 = 0x1;

/// Indicates this symbol is used on the hull stage.
pub const FLAG_HULL_STAGE: u16 = 0x2;

/// Indicates this symbol is used on the domain stage.
pub const FLAG_DOMAIN_STAGE: u16 = 0x4;

/// Indicates this symbol is used on the geometry stage.
pub const FLAG_GEOMETRY_STAGE: u16 = 0x8;

/// Indicates this symbol is used on the pixel stage.
pub const FLAG_PIXEL_STAGE: u16 = 0x10;

/// Indicates this symbol is part of an assembly.
pub const FLAG_ASSEMBLY: u16 = 0x20;

/// Indicates this symbol is not defined in this package.
pub const FLAG_EXTERNAL: u16 = 0x40;

/// Indicates this symbol is defined in this package.
pub const FLAG_INTERNAL: u16 = 0x80;

/// Indicates this symbol has extended data.
pub const FLAG_EXTENDED_DATA: u16 = 0x100;

/// Indicates this symbol has a register number.
pub const FLAG_REGISTER: u16 = 0x200;

/// Size in bytes of a symbol structure.
pub const SIZE_SYMBOL_STRUCTURE: usize = 12;

/// The type of a symbol.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Type
{
    /// A texture symbol.
    Texture,

    /// A sampler symbol.
    Sampler,

    /// A constant buffer symbol.
    ConstantBuffer,

    /// A high performance constant symbol (represented as push constants in vulkan).
    Constant,

    /// A vertex format symbol.
    VertexFormat,

    /// A pipeline symbol.
    Pipeline
}

fn get_symbol_type_from_code(scode: u8) -> Result<Type, ReadError>
{
    match scode {
        0x0 => Ok(Type::Texture),
        0x1 => Ok(Type::Sampler),
        0x2 => Ok(Type::ConstantBuffer),
        0x3 => Ok(Type::Constant),
        0x4 => Ok(Type::VertexFormat),
        0x5 => Ok(Type::Pipeline),
        _ => Err(ReadError::InvalidCode(
            InvalidCodeContext::SymbolType,
            scode
        ))
    }
}

/// Represents the structure of a symbol.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Symbol
{
    /// The pointer to the name of the symbol.
    pub name: u32,

    /// The pointer to the BPXSD object attached to this symbol.
    pub extended_data: u32,

    /// The symbol flags (see the FLAG_ constants in the [symbol](crate::shader::symbol) module).
    pub flags: u16,

    /// The type of symbol.
    pub ty: Type,

    /// The register number for this symbol.
    pub register: u8
}

impl Struct<SIZE_SYMBOL_STRUCTURE> for Symbol
{
    type Output = Symbol;
    type Error = ReadError;

    fn new() -> Self
    {
        Symbol {
            name: 0,
            extended_data: 0xFFFFFF,
            flags: 0,
            ty: Type::Constant,
            register: 0xFF
        }
    }

    fn error_buffer_size() -> Option<Self::Error>
    {
        Some(ReadError::Eos(EosContext::SymbolTable))
    }

    fn from_bytes(buffer: [u8; SIZE_SYMBOL_STRUCTURE]) -> Result<Self::Output, Self::Error>
    {
        let name = LittleEndian::read_u32(&buffer[0..4]);
        let extended_data = LittleEndian::read_u32(&buffer[4..8]);
        let flags = LittleEndian::read_u16(&buffer[8..10]);
        let ty = get_symbol_type_from_code(buffer[10])?;
        let register = buffer[11];
        Ok(Symbol {
            name,
            extended_data,
            flags,
            ty,
            register
        })
    }

    fn to_bytes(&self) -> [u8; SIZE_SYMBOL_STRUCTURE]
    {
        let mut buf = [0; SIZE_SYMBOL_STRUCTURE];
        LittleEndian::write_u32(&mut buf[0..4], self.name);
        LittleEndian::write_u32(&mut buf[4..8], self.extended_data);
        LittleEndian::write_u16(&mut buf[8..10], self.flags);
        match self.ty {
            Type::Texture => buf[10] = 0x0,
            Type::Sampler => buf[10] = 0x1,
            Type::ConstantBuffer => buf[10] = 0x2,
            Type::Constant => buf[10] = 0x3,
            Type::VertexFormat => buf[10] = 0x4,
            Type::Pipeline => buf[10] = 0x5
        };
        buf[11] = self.register;
        buf
    }
}

impl Item for Symbol
{
    fn get_name_address(&self) -> u32
    {
        self.name
    }
}

/// The required settings to create a new symbol.
///
/// *This is intended to be generated with help of [Builder](crate::shader::symbol::Builder).*
#[derive(Clone)]
pub struct Settings
{
    /// The name of the symbol.
    pub name: String,

    /// The extended data [Object](crate::sd::Object) of the symbol.
    pub extended_data: Option<Object>,

    /// The symbol type.
    pub ty: Type,

    /// The symbol flags.
    pub flags: u16,

    /// The symbol register number.
    pub register: u8
}

/// Utility to simplify generation of [Settings](crate::shader::symbol::Settings) required when creating a new BPXS.
pub struct Builder
{
    sym: Settings
}

impl Builder
{
    /// Creates a new symbol builder.
    pub fn new<S: Into<String>>(name: S) -> Builder
    {
        Builder {
            sym: Settings {
                name: name.into(),
                extended_data: None,
                ty: Type::Constant,
                flags: 0,
                register: 0xFF
            }
        }
    }

    /// Defines the type of this symbol.
    ///
    /// # Arguments
    ///
    /// * `ty`: the symbol type.
    ///
    /// returns: &mut Builder
    pub fn ty(&mut self, ty: Type) -> &mut Self
    {
        self.sym.ty = ty;
        self
    }

    /// Defines the extended data for this symbol.
    ///
    /// *This function automatically adds the
    /// [FLAG_EXTENDED_DATA](crate::shader::symbol::FLAG_EXTENDED_DATA) flag.*
    ///
    /// # Arguments
    ///
    /// * `obj`: An [Object](crate::sd::Object) to store as extended data.
    ///
    /// returns: &mut Builder
    pub fn extended_data(&mut self, obj: Object) -> &mut Self
    {
        self.sym.extended_data = Some(obj);
        self.sym.flags |= FLAG_EXTENDED_DATA;
        self
    }

    /// Defines the register number of this symbol.
    ///
    /// *This function automatically adds the [FLAG_REGISTER](crate::shader::symbol::FLAG_REGISTER)
    /// flag.*
    ///
    /// # Arguments
    ///
    /// * `register`: the register number of this symbol.
    ///
    /// returns: &mut Builder
    pub fn register(&mut self, register: u8) -> &mut Self
    {
        self.sym.register = register;
        self.sym.flags |= FLAG_REGISTER;
        self
    }

    /// Marks this symbol as internal.
    ///
    /// *Adds the [FLAG_INTERNAL](crate::shader::symbol::FLAG_INTERNAL).*
    pub fn internal(&mut self) -> &mut Self
    {
        self.sym.flags |= FLAG_INTERNAL;
        self
    }

    /// Marks this symbol as external.
    ///
    /// *Adds the [FLAG_EXTERNAL](crate::shader::symbol::FLAG_EXTERNAL).*
    pub fn external(&mut self) -> &mut Self
    {
        self.sym.flags |= FLAG_EXTERNAL;
        self
    }

    /// Marks this symbol as being part of an assembly.
    ///
    /// *Adds the [FLAG_ASSEMBLY](crate::shader::symbol::FLAG_ASSEMBLY).*
    pub fn assembly(&mut self) -> &mut Self
    {
        self.sym.flags |= FLAG_ASSEMBLY;
        self
    }

    /// Adds the stage flag identified by `stage` to this symbol.
    ///
    /// For more information please have a look at the FLAG_*_STAGE flags defined in the
    /// [symbol](crate::shader::symbol) module.
    ///
    /// # Arguments
    ///
    /// * `stage`: the stage to add.
    ///
    /// returns: &mut Builder
    pub fn stage(&mut self, stage: Stage) -> &mut Self
    {
        match stage {
            Stage::Vertex => self.sym.flags |= FLAG_VERTEX_STAGE,
            Stage::Hull => self.sym.flags |= FLAG_HULL_STAGE,
            Stage::Domain => self.sym.flags |= FLAG_DOMAIN_STAGE,
            Stage::Geometry => self.sym.flags |= FLAG_GEOMETRY_STAGE,
            Stage::Pixel => self.sym.flags |= FLAG_PIXEL_STAGE
        }
        self
    }

    /// Returns the built settings.
    pub fn build(&self) -> Settings
    {
        self.sym.clone()
    }
}

impl From<&mut Builder> for Settings
{
    fn from(builder: &mut Builder) -> Self
    {
        builder.build()
    }
}

impl From<Builder> for Settings
{
    fn from(builder: Builder) -> Self
    {
        builder.build()
    }
}
