/* Copyright 2018 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::{BinaryReader, BinaryReaderError, Result};
use ::core::fmt;
use ::core::marker;
use ::core::ops::Range;

#[cfg(feature = "component-model")]
mod component;
mod core;

#[cfg(feature = "component-model")]
pub use self::component::*;
pub use self::core::*;

/// A trait implemented for items that can be decoded directly from a
/// `BinaryReader`, or that which can be parsed from the WebAssembly binary
/// format.
///
/// Note that this is also accessible as a [`BinaryReader::read`] method.
pub trait FromReader<'a>: Sized {
    /// Attempts to read `Self` from the provided binary reader, returning an
    /// error if it is unable to do so.
    fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self>;
}

impl<'a> FromReader<'a> for bool {
    fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
        match reader.read_u8()? {
            0 => Ok(false),
            1 => Ok(true),
            _ => Err(BinaryReaderError::new(
                "invalid boolean value",
                reader.original_position() - 1,
            )),
        }
    }
}

impl<'a> FromReader<'a> for u32 {
    fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
        reader.read_var_u32()
    }
}

impl<'a> FromReader<'a> for &'a str {
    fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
        reader.read_string()
    }
}

impl<'a, T, U> FromReader<'a> for (T, U)
where
    T: FromReader<'a>,
    U: FromReader<'a>,
{
    fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
        Ok((reader.read()?, reader.read()?))
    }
}

/// A generic structure for reading a section of a WebAssembly binary which has
/// a limited number of items within it.
///
/// Many WebAssembly sections are a count of items followed by that many items.
/// This helper structure can be used to parse these sections and provides
/// an iteration-based API for reading the contents.
///
/// Note that this always implements the [`Clone`] trait to represent the
/// ability to parse the section multiple times.
pub struct SectionLimited<'a, T> {
    reader: BinaryReader<'a>,
    count: u32,
    _marker: marker::PhantomData<T>,
}

impl<'a, T> SectionLimited<'a, T> {
    /// Creates a new section reader from the provided contents.
    ///
    /// The `data` provided here is the data of the section itself that will be
    /// parsed. The `offset` argument is the byte offset, in the original wasm
    /// binary, that the section was found. The `offset` argument is used
    /// for error reporting.
    ///
    /// # Errors
    ///
    /// Returns an error if a 32-bit count couldn't be read from the `data`.
    pub fn new(mut reader: BinaryReader<'a>) -> Result<Self> {
        let count = reader.read_var_u32()?;
        Ok(SectionLimited {
            reader,
            count,
            _marker: marker::PhantomData,
        })
    }

    /// Returns the count of total items within this section.
    pub fn count(&self) -> u32 {
        self.count
    }

    /// Returns whether the original byte offset of this section.
    pub fn original_position(&self) -> usize {
        self.reader.original_position()
    }

    /// Returns the range, as byte offsets, of this section within the original
    /// wasm binary.
    pub fn range(&self) -> Range<usize> {
        self.reader.range()
    }

    /// Returns an iterator which yields not only each item in this section but
    /// additionally the offset of each item within the section.
    pub fn into_iter_with_offsets(self) -> SectionLimitedIntoIterWithOffsets<'a, T>
    where
        T: FromReader<'a>,
    {
        SectionLimitedIntoIterWithOffsets {
            iter: self.into_iter(),
        }
    }
}

impl<T> Clone for SectionLimited<'_, T> {
    fn clone(&self) -> Self {
        SectionLimited {
            reader: self.reader.clone(),
            count: self.count,
            _marker: self._marker,
        }
    }
}

impl<T> fmt::Debug for SectionLimited<'_, T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("SectionLimited")
            .field("count", &self.count)
            .field("range", &self.range())
            .finish()
    }
}

impl<'a, T> IntoIterator for SectionLimited<'a, T>
where
    T: FromReader<'a>,
{
    type Item = Result<T>;
    type IntoIter = SectionLimitedIntoIter<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        SectionLimitedIntoIter {
            remaining: self.count,
            section: self,
            end: false,
        }
    }
}

/// A consuming iterator of a [`SectionLimited`].
///
/// This is created via the [`IntoIterator`] `impl` for the [`SectionLimited`]
/// type.
pub struct SectionLimitedIntoIter<'a, T> {
    section: SectionLimited<'a, T>,
    remaining: u32,
    end: bool,
}

impl<T> SectionLimitedIntoIter<'_, T> {
    /// Returns the current byte offset of the section within this iterator.
    pub fn original_position(&self) -> usize {
        self.section.reader.original_position()
    }
}

impl<'a, T> Iterator for SectionLimitedIntoIter<'a, T>
where
    T: FromReader<'a>,
{
    type Item = Result<T>;

    fn next(&mut self) -> Option<Result<T>> {
        if self.end {
            return None;
        }
        if self.remaining == 0 {
            self.end = true;
            if self.section.reader.eof() {
                return None;
            }
            return Some(Err(BinaryReaderError::new(
                "section size mismatch: unexpected data at the end of the section",
                self.section.reader.original_position(),
            )));
        }
        let result = self.section.reader.read();
        self.end = result.is_err();
        self.remaining -= 1;
        Some(result)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = self.remaining as usize;
        (remaining, Some(remaining))
    }
}

impl<'a, T> ExactSizeIterator for SectionLimitedIntoIter<'a, T> where T: FromReader<'a> {}

/// An iterator over a limited section iterator.
pub struct SectionLimitedIntoIterWithOffsets<'a, T> {
    iter: SectionLimitedIntoIter<'a, T>,
}

impl<'a, T> Iterator for SectionLimitedIntoIterWithOffsets<'a, T>
where
    T: FromReader<'a>,
{
    type Item = Result<(usize, T)>;

    fn next(&mut self) -> Option<Self::Item> {
        let pos = self.iter.section.reader.original_position();
        Some(self.iter.next()?.map(|item| (pos, item)))
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.iter.size_hint()
    }
}

impl<'a, T> ExactSizeIterator for SectionLimitedIntoIterWithOffsets<'a, T> where T: FromReader<'a> {}

/// A trait implemented for subsections of another outer section.
///
/// This is currently only used for subsections within custom sections, such as
/// the `name` section of core wasm.
///
/// This is used in conjunction with [`Subsections`].
pub trait Subsection<'a>: Sized {
    /// Converts the section identifier provided with the section contents into
    /// a typed section
    fn from_reader(id: u8, reader: BinaryReader<'a>) -> Result<Self>;
}

/// Iterator/reader over the contents of a section which is composed of
/// subsections.
///
/// This reader is used for the core `name` section, for example. This type
/// primarily implements [`Iterator`] for advancing through the sections.
pub struct Subsections<'a, T> {
    reader: BinaryReader<'a>,
    _marker: marker::PhantomData<T>,
}

impl<'a, T> Subsections<'a, T> {
    /// Creates a new reader for the specified section contents starting at
    /// `offset` within the original wasm file.
    pub fn new(reader: BinaryReader<'a>) -> Self {
        Subsections {
            reader,
            _marker: marker::PhantomData,
        }
    }

    /// Returns whether the original byte offset of this section.
    pub fn original_position(&self) -> usize {
        self.reader.original_position()
    }

    /// Returns the range, as byte offsets, of this section within the original
    /// wasm binary.
    pub fn range(&self) -> Range<usize> {
        self.reader.range()
    }

    fn read(&mut self) -> Result<T>
    where
        T: Subsection<'a>,
    {
        let subsection_id = self.reader.read_u7()?;
        let reader = self.reader.read_reader()?;
        T::from_reader(subsection_id, reader)
    }
}

impl<T> Clone for Subsections<'_, T> {
    fn clone(&self) -> Self {
        Subsections {
            reader: self.reader.clone(),
            _marker: self._marker,
        }
    }
}

impl<T> fmt::Debug for Subsections<'_, T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Subsections")
            .field("range", &self.range())
            .finish()
    }
}

impl<'a, T> Iterator for Subsections<'a, T>
where
    T: Subsection<'a>,
{
    type Item = Result<T>;

    fn next(&mut self) -> Option<Result<T>> {
        if self.reader.eof() {
            None
        } else {
            Some(self.read())
        }
    }
}
