use crate::frame::Frame;
use crate::stream::encoding::Encoding;
use crate::stream::frame;
use crate::tag::Version;
use crate::{Error, ErrorKind};
use bitflags::bitflags;
use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
use flate2::write::ZlibEncoder;
use flate2::Compression;
use std::io;

bitflags! {
    pub struct Flags: u16 {
        const TAG_ALTER_PRESERVATION  = 0x8000;
        const FILE_ALTER_PRESERVATION = 0x4000;
        const READ_ONLY               = 0x2000;
        const COMPRESSION             = 0x0080;
        const ENCRYPTION              = 0x0040;
        const GROUPING_IDENTITY       = 0x0020;
    }
}

pub fn decode(mut reader: impl io::Read) -> crate::Result<Option<(usize, Frame)>> {
    let mut frame_header = [0; 10];
    let nread = reader.read(&mut frame_header)?;
    if nread < frame_header.len() || frame_header[0] == 0x00 {
        return Ok(None);
    }
    let id = frame::str_from_utf8(&frame_header[0..4])?;

    let content_size = BigEndian::read_u32(&frame_header[4..8]) as usize;
    let flags = Flags::from_bits_truncate(BigEndian::read_u16(&frame_header[8..10]));
    if flags.contains(Flags::ENCRYPTION) {
        return Err(Error::new(
            ErrorKind::UnsupportedFeature,
            "encryption is not supported",
        ));
    } else if flags.contains(Flags::GROUPING_IDENTITY) {
        return Err(Error::new(
            ErrorKind::UnsupportedFeature,
            "grouping identity is not supported",
        ));
    }

    let read_size = if flags.contains(Flags::COMPRESSION) {
        let _decompressed_size = reader.read_u32::<BigEndian>()?;
        content_size - 4
    } else {
        content_size
    };
    let mut content_buf = vec![0; read_size];
    reader.read_exact(&mut content_buf)?;
    let (content, encoding) = super::decode_content(
        &content_buf[..],
        Version::Id3v23,
        id,
        flags.contains(Flags::COMPRESSION),
        false,
    )?;
    let frame = Frame::with_content(id, content).set_encoding(encoding);
    Ok(Some((10 + content_size, frame)))
}

pub fn encode(mut writer: impl io::Write, frame: &Frame, flags: Flags) -> crate::Result<usize> {
    let (content_buf, comp_hint_delta, decompressed_size) = if flags.contains(Flags::COMPRESSION) {
        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
        let content_size = frame::content::encode(
            &mut encoder,
            frame.content(),
            Version::Id3v23,
            frame.encoding().unwrap_or(Encoding::UTF16),
        )?;
        let content_buf = encoder.finish()?;
        (content_buf, 4, Some(content_size))
    } else {
        let mut content_buf = Vec::new();
        frame::content::encode(
            &mut content_buf,
            frame.content(),
            Version::Id3v23,
            frame.encoding().unwrap_or(Encoding::UTF16),
        )?;
        (content_buf, 0, None)
    };

    writer.write_all({
        let id = frame.id().as_bytes();
        assert_eq!(4, id.len());
        id
    })?;
    writer.write_u32::<BigEndian>((content_buf.len() + comp_hint_delta) as u32)?;
    writer.write_u16::<BigEndian>(flags.bits())?;
    if let Some(s) = decompressed_size {
        writer.write_u32::<BigEndian>(s as u32)?;
    }
    writer.write_all(&content_buf)?;
    Ok(10 + comp_hint_delta + content_buf.len())
}
