use std::borrow::Cow;

use crate::{CustomSection, Encode, Section};

/// The "core" custom section for coredumps, as described in the
/// [tool-conventions
/// repository](https://github.com/WebAssembly/tool-conventions/blob/main/Coredump.md).
///
/// There are four sections that comprise a core dump:
///     - "core", which contains the name of the core dump
///     - "coremodules", a listing of modules
///     - "coreinstances", a listing of module instances
///     - "corestack", a listing of frames for a specific thread
///
/// # Example of how these could be constructed and encoded into a module:
///
/// ```
/// use wasm_encoder::{
///     CoreDumpInstancesSection, CoreDumpModulesSection, CoreDumpSection, CoreDumpStackSection,
///     CoreDumpValue, Module,
/// };
/// let core = CoreDumpSection::new("MyModule.wasm");
///
/// let mut modules = CoreDumpModulesSection::new();
/// modules.module("my_module");
///
/// let mut instances = CoreDumpInstancesSection::new();
/// let module_idx = 0;
/// let memories = vec![1];
/// let globals = vec![2];
/// instances.instance(module_idx, memories, globals);
///
/// let mut thread = CoreDumpStackSection::new("main");
/// let instance_index = 0;
/// let func_index = 42;
/// let code_offset = 0x1234;
/// let locals = vec![CoreDumpValue::I32(1)];
/// let stack = vec![CoreDumpValue::I32(2)];
/// thread.frame(instance_index, func_index, code_offset, locals, stack);
///
/// let mut module = Module::new();
/// module.section(&core);
/// module.section(&modules);
/// module.section(&instances);
/// module.section(&thread);
/// ```
#[derive(Clone, Debug, Default)]
pub struct CoreDumpSection {
    name: String,
}

impl CoreDumpSection {
    /// Create a new core dump section encoder
    pub fn new(name: impl Into<String>) -> Self {
        let name = name.into();
        CoreDumpSection { name }
    }

    /// View the encoded section as a CustomSection.
    fn as_custom<'a>(&'a self) -> CustomSection<'a> {
        let mut data = vec![0];
        self.name.encode(&mut data);
        CustomSection {
            name: "core".into(),
            data: Cow::Owned(data),
        }
    }
}

impl Encode for CoreDumpSection {
    fn encode(&self, sink: &mut Vec<u8>) {
        self.as_custom().encode(sink);
    }
}

impl Section for CoreDumpSection {
    fn id(&self) -> u8 {
        crate::core::SectionId::Custom as u8
    }
}

/// The "coremodules" custom section for coredumps which lists the names of the
/// modules
///
/// # Example
///
/// ```
/// use wasm_encoder::{CoreDumpModulesSection, Module};
/// let mut modules_section = CoreDumpModulesSection::new();
/// modules_section.module("my_module");
/// let mut module = Module::new();
/// module.section(&modules_section);
/// ```
#[derive(Debug)]
pub struct CoreDumpModulesSection {
    num_added: u32,
    bytes: Vec<u8>,
}

impl CoreDumpModulesSection {
    /// Create a new core dump modules section encoder.
    pub fn new() -> Self {
        CoreDumpModulesSection {
            bytes: vec![],
            num_added: 0,
        }
    }

    /// View the encoded section as a CustomSection.
    pub fn as_custom(&self) -> CustomSection<'_> {
        let mut data = vec![];
        self.num_added.encode(&mut data);
        data.extend(self.bytes.iter().copied());
        CustomSection {
            name: "coremodules".into(),
            data: Cow::Owned(data),
        }
    }

    /// Encode a module name into the section's bytes.
    pub fn module(&mut self, module_name: impl AsRef<str>) -> &mut Self {
        self.bytes.push(0x0);
        module_name.as_ref().encode(&mut self.bytes);
        self.num_added += 1;
        self
    }

    /// The number of modules that are encoded in the section.
    pub fn len(&self) -> u32 {
        self.num_added
    }
}

impl Encode for CoreDumpModulesSection {
    fn encode(&self, sink: &mut Vec<u8>) {
        self.as_custom().encode(sink);
    }
}

impl Section for CoreDumpModulesSection {
    fn id(&self) -> u8 {
        crate::core::SectionId::Custom as u8
    }
}

/// The "coreinstances" section for the core dump
#[derive(Debug)]
pub struct CoreDumpInstancesSection {
    num_added: u32,
    bytes: Vec<u8>,
}

impl CoreDumpInstancesSection {
    /// Create a new core dump instances section encoder.
    pub fn new() -> Self {
        CoreDumpInstancesSection {
            bytes: vec![],
            num_added: 0,
        }
    }

    /// View the encoded section as a CustomSection.
    pub fn as_custom(&self) -> CustomSection<'_> {
        let mut data = vec![];
        self.num_added.encode(&mut data);
        data.extend(self.bytes.iter().copied());
        CustomSection {
            name: "coreinstances".into(),
            data: Cow::Owned(data),
        }
    }

    /// Encode an instance into the section's bytes.
    pub fn instance<M, G>(&mut self, module_index: u32, memories: M, globals: G) -> &mut Self
    where
        M: IntoIterator<Item = u32>,
        <M as IntoIterator>::IntoIter: ExactSizeIterator,
        G: IntoIterator<Item = u32>,
        <G as IntoIterator>::IntoIter: ExactSizeIterator,
    {
        self.bytes.push(0x0);
        module_index.encode(&mut self.bytes);
        crate::encode_vec(memories, &mut self.bytes);
        crate::encode_vec(globals, &mut self.bytes);
        self.num_added += 1;
        self
    }

    /// The number of modules that are encoded in the section.
    pub fn len(&self) -> u32 {
        self.num_added
    }
}

impl Encode for CoreDumpInstancesSection {
    fn encode(&self, sink: &mut Vec<u8>) {
        self.as_custom().encode(sink);
    }
}

impl Section for CoreDumpInstancesSection {
    fn id(&self) -> u8 {
        crate::core::SectionId::Custom as u8
    }
}

/// A "corestack" custom section as described in the [tool-conventions
/// repository](https://github.com/WebAssembly/tool-conventions/blob/main/Coredump.md)
///
/// # Example
///
/// ```
/// use wasm_encoder::{CoreDumpStackSection, Module, CoreDumpValue};
/// let mut thread = CoreDumpStackSection::new("main");
///
/// let instance_index = 0;
/// let func_index = 42;
/// let code_offset = 0x1234;
/// let locals = vec![CoreDumpValue::I32(1)];
/// let stack = vec![CoreDumpValue::I32(2)];
/// thread.frame(instance_index, func_index, code_offset, locals, stack);
///
/// let mut module = Module::new();
/// module.section(&thread);
/// ```
#[derive(Clone, Debug, Default)]
pub struct CoreDumpStackSection {
    frame_bytes: Vec<u8>,
    count: u32,
    name: String,
}

impl CoreDumpStackSection {
    /// Create a new core dump stack section encoder.
    pub fn new(name: impl Into<String>) -> Self {
        let name = name.into();
        CoreDumpStackSection {
            frame_bytes: Vec::new(),
            count: 0,
            name,
        }
    }

    /// Add a stack frame to this coredump stack section.
    pub fn frame<L, S>(
        &mut self,
        instanceidx: u32,
        funcidx: u32,
        codeoffset: u32,
        locals: L,
        stack: S,
    ) -> &mut Self
    where
        L: IntoIterator<Item = CoreDumpValue>,
        <L as IntoIterator>::IntoIter: ExactSizeIterator,
        S: IntoIterator<Item = CoreDumpValue>,
        <S as IntoIterator>::IntoIter: ExactSizeIterator,
    {
        self.count += 1;
        self.frame_bytes.push(0);
        instanceidx.encode(&mut self.frame_bytes);
        funcidx.encode(&mut self.frame_bytes);
        codeoffset.encode(&mut self.frame_bytes);
        crate::encode_vec(locals, &mut self.frame_bytes);
        crate::encode_vec(stack, &mut self.frame_bytes);
        self
    }

    /// View the encoded section as a CustomSection.
    pub fn as_custom<'a>(&'a self) -> CustomSection<'a> {
        let mut data = vec![0];
        self.name.encode(&mut data);
        self.count.encode(&mut data);
        data.extend(&self.frame_bytes);
        CustomSection {
            name: "corestack".into(),
            data: Cow::Owned(data),
        }
    }
}

impl Encode for CoreDumpStackSection {
    fn encode(&self, sink: &mut Vec<u8>) {
        self.as_custom().encode(sink);
    }
}

impl Section for CoreDumpStackSection {
    fn id(&self) -> u8 {
        crate::core::SectionId::Custom as u8
    }
}

/// Local and stack values are encoded using one byte for the type (similar to
/// Wasm's Number Types) followed by bytes representing the actual value
/// See the tool-conventions repo for more details.
#[derive(Clone, Debug)]
pub enum CoreDumpValue {
    /// a missing value (usually missing because it was optimized out)
    Missing,
    /// An i32 value
    I32(i32),
    /// An i64 value
    I64(i64),
    /// An f32 value
    F32(f32),
    /// An f64 value
    F64(f64),
}

impl Encode for CoreDumpValue {
    fn encode(&self, sink: &mut Vec<u8>) {
        match self {
            CoreDumpValue::Missing => sink.push(0x01),
            CoreDumpValue::I32(x) => {
                sink.push(0x7F);
                x.encode(sink);
            }
            CoreDumpValue::I64(x) => {
                sink.push(0x7E);
                x.encode(sink);
            }
            CoreDumpValue::F32(x) => {
                sink.push(0x7D);
                x.encode(sink);
            }
            CoreDumpValue::F64(x) => {
                sink.push(0x7C);
                x.encode(sink);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Module;
    use wasmparser::{KnownCustom, Parser, Payload};

    // Create new core dump section and test whether it is properly encoded and
    // parsed back out by wasmparser
    #[test]
    fn test_roundtrip_core() {
        let core = CoreDumpSection::new("test.wasm");
        let mut module = Module::new();
        module.section(&core);

        let wasm_bytes = module.finish();

        let mut parser = Parser::new(0).parse_all(&wasm_bytes);
        match parser.next() {
            Some(Ok(Payload::Version { .. })) => {}
            _ => panic!(""),
        }

        let payload = parser
            .next()
            .expect("parser is not empty")
            .expect("element is a payload");
        match payload {
            Payload::CustomSection(section) => {
                assert_eq!(section.name(), "core");
                let core = match section.as_known() {
                    KnownCustom::CoreDump(s) => s,
                    _ => panic!("not coredump"),
                };
                assert_eq!(core.name, "test.wasm");
            }
            _ => panic!("unexpected payload"),
        }
    }

    #[test]
    fn test_roundtrip_coremodules() {
        let mut coremodules = CoreDumpModulesSection::new();
        coremodules.module("test_module");

        let mut module = crate::Module::new();
        module.section(&coremodules);

        let wasm_bytes = module.finish();

        let mut parser = Parser::new(0).parse_all(&wasm_bytes);
        match parser.next() {
            Some(Ok(Payload::Version { .. })) => {}
            _ => panic!(""),
        }

        let payload = parser
            .next()
            .expect("parser is not empty")
            .expect("element is a payload");
        match payload {
            Payload::CustomSection(section) => {
                assert_eq!(section.name(), "coremodules");
                let modules = match section.as_known() {
                    KnownCustom::CoreDumpModules(s) => s,
                    _ => panic!("not coremodules"),
                };
                assert_eq!(modules.modules[0], "test_module");
            }
            _ => panic!("unexpected payload"),
        }
    }

    #[test]
    fn test_roundtrip_coreinstances() {
        let mut coreinstances = CoreDumpInstancesSection::new();
        let module_index = 0;
        let memories = vec![42];
        let globals = vec![17];
        coreinstances.instance(module_index, memories, globals);

        let mut module = Module::new();
        module.section(&coreinstances);
        let wasm_bytes = module.finish();

        let mut parser = Parser::new(0).parse_all(&wasm_bytes);
        match parser.next() {
            Some(Ok(Payload::Version { .. })) => {}
            _ => panic!(""),
        }

        let payload = parser
            .next()
            .expect("parser is not empty")
            .expect("element is a payload");
        match payload {
            Payload::CustomSection(section) => {
                assert_eq!(section.name(), "coreinstances");
                let coreinstances = match section.as_known() {
                    KnownCustom::CoreDumpInstances(s) => s,
                    _ => panic!("not coreinstances"),
                };
                assert_eq!(coreinstances.instances.len(), 1);
                let instance = coreinstances
                    .instances
                    .first()
                    .expect("instance is encoded");
                assert_eq!(instance.module_index, 0);
                assert_eq!(instance.memories.len(), 1);
                assert_eq!(instance.globals.len(), 1);
            }
            _ => panic!("unexpected payload"),
        }
    }

    // Create new corestack section and test whether it is properly encoded and
    // parsed back out by wasmparser
    #[test]
    fn test_roundtrip_corestack() {
        let mut corestack = CoreDumpStackSection::new("main");
        corestack.frame(
            0,
            12,
            0,
            vec![CoreDumpValue::I32(10)],
            vec![CoreDumpValue::I32(42)],
        );
        let mut module = Module::new();
        module.section(&corestack);
        let wasm_bytes = module.finish();

        let mut parser = Parser::new(0).parse_all(&wasm_bytes);
        match parser.next() {
            Some(Ok(Payload::Version { .. })) => {}
            _ => panic!(""),
        }

        let payload = parser
            .next()
            .expect("parser is not empty")
            .expect("element is a payload");
        match payload {
            Payload::CustomSection(section) => {
                assert_eq!(section.name(), "corestack");
                let corestack = match section.as_known() {
                    KnownCustom::CoreDumpStack(s) => s,
                    _ => panic!("not a corestack section"),
                };
                assert_eq!(corestack.name, "main");
                assert_eq!(corestack.frames.len(), 1);
                let frame = corestack
                    .frames
                    .first()
                    .expect("frame is encoded in corestack");
                assert_eq!(frame.instanceidx, 0);
                assert_eq!(frame.funcidx, 12);
                assert_eq!(frame.codeoffset, 0);
                assert_eq!(frame.locals.len(), 1);
                match frame.locals.first().expect("frame contains a local") {
                    &wasmparser::CoreDumpValue::I32(val) => assert_eq!(val, 10),
                    _ => panic!("unexpected local value"),
                }
                assert_eq!(frame.stack.len(), 1);
                match frame.stack.first().expect("stack contains a value") {
                    &wasmparser::CoreDumpValue::I32(val) => assert_eq!(val, 42),
                    _ => panic!("unexpected stack value"),
                }
            }
            _ => panic!("unexpected payload"),
        }
    }

    #[test]
    fn test_encode_coredump_section() {
        let core = CoreDumpSection::new("test");

        let mut encoded = vec![];
        core.encode(&mut encoded);

        #[rustfmt::skip]
        assert_eq!(encoded, vec![
            // section length
            11,
            // name length
            4,
            // section name (core)
            b'c',b'o',b'r',b'e',
            // process-info (0, data length, data)
            0, 4, b't', b'e', b's', b't',
        ]);
    }

    #[test]
    fn test_encode_coremodules_section() {
        let mut modules = CoreDumpModulesSection::new();
        modules.module("mod1");
        modules.module("mod2");

        let mut encoded = vec![];
        modules.encode(&mut encoded);

        #[rustfmt::skip]
        assert_eq!(encoded, vec![
            // section length
            25,
            // name length
            11,
            // section name (coremodules)
            b'c',b'o',b'r',b'e',b'm',b'o',b'd',b'u',b'l',b'e',b's',
            // module count
            2,
            // 0x0, name-length, module name (mod1)
            0x0, 4, b'm',b'o',b'd',b'1',
            // 0x0, name-length, module name (mod2)
            0x0, 4, b'm',b'o',b'd',b'2'
        ]);
    }

    #[test]
    fn test_encode_coreinstances_section() {
        let mut instances = CoreDumpInstancesSection::new();
        instances.instance(0, vec![42], vec![17]);

        let mut encoded = vec![];
        instances.encode(&mut encoded);

        #[rustfmt::skip]
        assert_eq!(encoded, vec![
            // section length
            21,
            // name length
            13,
            // section name (coreinstances)
            b'c',b'o',b'r',b'e',b'i',b'n',b's',b't',b'a',b'n',b'c',b'e',b's',
            // instance count
            1,
            // 0x0, module_idx
            0x0, 0,
            // memories count, memories
            1, 42,
            // globals count, globals
            1, 17
        ]);
    }

    #[test]
    fn test_encode_corestack_section() {
        let mut thread = CoreDumpStackSection::new("main");
        thread.frame(
            0,
            42,
            51,
            vec![CoreDumpValue::I32(1)],
            vec![CoreDumpValue::I32(2)],
        );

        let mut encoded = vec![];
        thread.encode(&mut encoded);

        #[rustfmt::skip]
        assert_eq!(
            encoded,
            vec![
                // section length
                27,
                // length of name.
                9,
                // section name (corestack)
                b'c',b'o',b'r',b'e',b's',b't',b'a',b'c',b'k',
                // 0x0, thread name length
                0, 4,
                // thread name (main)
                b'm',b'a',b'i',b'n',
                // frame count
                1,
                // 0x0, instanceidx, funcidx, codeoffset
                0, 0, 42, 51,
                // local count
                1,
                // local value type
                0x7F,
                // local value
                1,
                // stack count
                1,
                // stack value type
                0x7F,
                // stack value
                2

            ]
        );
    }
}
