// Copyright 2022 Damir Jelić
//
// 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 std::io::{Cursor, Write};

use crate::{EncodeError, MAX_ARRAY_LENGTH};

/// A trait for encoding values into the `matrix-pickle` binary format.
pub trait Encode {
    /// Try to encode and write a value to the given writer, returning how many bytes were written.
    fn encode(&self, writer: &mut impl Write) -> Result<usize, EncodeError>;

    /// Try to encode a value into a new `Vec`.
    fn encode_to_vec(&self) -> Result<Vec<u8>, EncodeError> {
        let buffer = Vec::new();
        let mut cursor = Cursor::new(buffer);

        self.encode(&mut cursor)?;

        Ok(cursor.into_inner())
    }
}

impl Encode for u8 {
    fn encode(&self, writer: &mut impl Write) -> Result<usize, EncodeError> {
        Ok(writer.write(&[*self])?)
    }
}

impl Encode for bool {
    fn encode(&self, writer: &mut impl Write) -> Result<usize, EncodeError> {
        (*self as u8).encode(writer)
    }
}

impl<const N: usize> Encode for [u8; N] {
    fn encode(&self, writer: &mut impl Write) -> Result<usize, EncodeError> {
        writer.write_all(self)?;

        Ok(N)
    }
}

impl Encode for u32 {
    fn encode(&self, writer: &mut impl Write) -> Result<usize, EncodeError> {
        let bytes = self.to_be_bytes();
        bytes.encode(writer)
    }
}

impl Encode for usize {
    fn encode(&self, writer: &mut impl Write) -> Result<usize, EncodeError> {
        let value = u32::try_from(*self).map_err(|_| EncodeError::OutsideU32Range(*self))?;

        value.encode(writer)
    }
}

impl<T: Encode> Encode for [T] {
    fn encode(&self, writer: &mut impl Write) -> Result<usize, EncodeError> {
        let length = self.len();

        if length > MAX_ARRAY_LENGTH {
            Err(EncodeError::ArrayTooBig(length))
        } else {
            let mut ret = length.encode(writer)?;

            for value in self {
                ret += value.encode(writer)?;
            }

            Ok(ret)
        }
    }
}
