use crate::{
    config::TxStrategy,
    error::PERes,
    id::{RecRef, SegmentId},
    journal::JournalEntry,
    util::io::{InfallibleWrite, InfallibleWriteVarInt, ReadVarInt},
};
#[cfg(feature = "experimental_inspect")]
use std::fmt::Debug;
use std::{io::Read, str};

#[derive(Default)]
#[cfg_attr(feature = "experimental_inspect", derive(Debug))]
pub struct Start {}

impl JournalEntry for Start {
    fn get_type(&self) -> u8 {
        1
    }

    fn write(&self, _: &mut dyn InfallibleWrite) -> PERes<()> {
        Ok(())
    }

    fn read_new(_: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        Ok(Start::default())
    }
}

#[derive(Clone, Default)]
#[cfg_attr(feature = "experimental_inspect", derive(Debug))]
pub struct NewSegmentPage {
    pub segment: SegmentId,
    pub page: u64,
    pub previous: u64,
}

impl NewSegmentPage {
    pub fn new(segment: SegmentId, page: u64, previous: u64) -> NewSegmentPage {
        NewSegmentPage {
            segment,
            page,
            previous,
        }
    }
}

impl JournalEntry for NewSegmentPage {
    fn get_type(&self) -> u8 {
        13
    }

    fn write(&self, buffer: &mut dyn InfallibleWrite) -> PERes<()> {
        self.segment.write_varint(buffer);
        buffer.write_varint_u64(self.page);
        buffer.write_varint_u64(self.previous);
        Ok(())
    }

    fn read_new(buffer: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        let segment = SegmentId::read_varint(buffer)?;
        let page = buffer.read_varint_u64()?;
        let previous = buffer.read_varint_u64()?;
        Ok(NewSegmentPage {
            segment,
            page,
            previous,
        })
    }
}

#[derive(Clone, Default)]
pub struct InsertRecord {
    pub segment: SegmentId,
    pub recref: RecRef,
    pub record_page: u64,
}

#[cfg(feature = "experimental_inspect")]
impl Debug for InsertRecord {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("InsertRecord")
            .field("segment", &self.segment)
            .field("recref", &format!("{}", &self.recref))
            .field("record_page", &self.record_page)
            .finish()
    }
}

impl InsertRecord {
    pub fn new(segment: SegmentId, rec_ref: &RecRef, record: u64) -> InsertRecord {
        InsertRecord {
            segment,
            recref: *rec_ref,
            record_page: record,
        }
    }
}

impl JournalEntry for InsertRecord {
    fn get_type(&self) -> u8 {
        2
    }

    fn write(&self, buffer: &mut dyn InfallibleWrite) -> PERes<()> {
        self.segment.write_varint(buffer);
        buffer.write_varint_u64(self.recref.page);
        buffer.write_varint_u32(self.recref.pos);
        buffer.write_varint_u64(self.record_page);
        Ok(())
    }

    fn read_new(buffer: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        let segment = SegmentId::read_varint(buffer)?;
        let recref_page = buffer.read_varint_u64()?;
        let recref_pos = buffer.read_varint_u32()?;
        let record_page = buffer.read_varint_u64()?;
        Ok(InsertRecord {
            segment,
            recref: RecRef::new(recref_page, recref_pos),
            record_page,
        })
    }
}

#[derive(Clone, Default)]
pub struct UpdateRecord {
    pub segment: SegmentId,
    pub recref: RecRef,
    pub record_page: u64,
    pub version: u16,
}

#[cfg(feature = "experimental_inspect")]
impl Debug for UpdateRecord {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("UpdateRecord")
            .field("segment", &self.segment)
            .field("recref", &format!("{}", self.recref))
            .field("record_page", &self.record_page)
            .finish()
    }
}
impl UpdateRecord {
    pub fn new(segment: SegmentId, rec_ref: &RecRef, record: u64, version: u16) -> UpdateRecord {
        UpdateRecord {
            segment,
            recref: *rec_ref,
            record_page: record,
            version,
        }
    }
}

impl JournalEntry for UpdateRecord {
    fn get_type(&self) -> u8 {
        5
    }

    fn write(&self, buffer: &mut dyn InfallibleWrite) -> PERes<()> {
        self.segment.write_varint(buffer);
        buffer.write_varint_u64(self.recref.page);
        buffer.write_varint_u32(self.recref.pos);
        buffer.write_varint_u64(self.record_page);
        buffer.write_varint_u16(self.version);
        Ok(())
    }

    fn read_new(buffer: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        let segment = SegmentId::read_varint(buffer)?;
        let recref_page = buffer.read_varint_u64()?;
        let recref_pos = buffer.read_varint_u32()?;
        let record_page = buffer.read_varint_u64()?;
        let version = buffer.read_varint_u16()?;
        Ok(UpdateRecord {
            segment,
            recref: RecRef::new(recref_page, recref_pos),
            record_page,
            version,
        })
    }
}

#[derive(Clone, Default)]
pub struct ReadRecord {
    pub segment: SegmentId,
    pub recref: RecRef,
    pub version: u16,
}

#[cfg(feature = "experimental_inspect")]
impl Debug for ReadRecord {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ReadRecord")
            .field("segment", &self.segment)
            .field("recref", &format!("{}", self.recref))
            .field("version", &self.version)
            .finish()
    }
}

impl ReadRecord {
    pub fn new(segment: SegmentId, recref: &RecRef, version: u16) -> ReadRecord {
        ReadRecord {
            segment,
            recref: *recref,
            version,
        }
    }
}

impl JournalEntry for ReadRecord {
    fn get_type(&self) -> u8 {
        10
    }

    fn write(&self, buffer: &mut dyn InfallibleWrite) -> PERes<()> {
        self.segment.write_varint(buffer);
        buffer.write_varint_u64(self.recref.page);
        buffer.write_varint_u32(self.recref.pos);
        buffer.write_varint_u16(self.version);
        Ok(())
    }

    fn read_new(buffer: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        let segment = SegmentId::read_varint(buffer)?;
        let recref_page = buffer.read_varint_u64()?;
        let recref_pos = buffer.read_varint_u32()?;
        let version = buffer.read_varint_u16()?;
        Ok(ReadRecord {
            segment,
            recref: RecRef::new(recref_page, recref_pos),
            version,
        })
    }
}

#[derive(Clone, Default)]
pub struct DeleteRecord {
    pub segment: SegmentId,
    pub recref: RecRef,
    pub version: u16,
}

#[cfg(feature = "experimental_inspect")]
impl Debug for DeleteRecord {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DeleteRecord")
            .field("segment", &self.segment)
            .field("recref", &format!("{}", self.recref))
            .field("version", &self.version)
            .finish()
    }
}

impl DeleteRecord {
    pub fn new(segment: SegmentId, rec_ref: &RecRef, version: u16) -> DeleteRecord {
        DeleteRecord {
            segment,
            recref: *rec_ref,
            version,
        }
    }
}

impl JournalEntry for DeleteRecord {
    fn get_type(&self) -> u8 {
        6
    }

    fn write(&self, buffer: &mut dyn InfallibleWrite) -> PERes<()> {
        self.segment.write_varint(buffer);
        buffer.write_varint_u64(self.recref.page);
        buffer.write_varint_u32(self.recref.pos);
        buffer.write_varint_u16(self.version);
        Ok(())
    }

    fn read_new(buffer: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        let segment = SegmentId::read_varint(buffer)?;
        let recref_page = buffer.read_varint_u64()?;
        let recref_pos = buffer.read_varint_u32()?;
        let version = buffer.read_varint_u16()?;
        Ok(DeleteRecord {
            segment,
            recref: RecRef::new(recref_page, recref_pos),
            version,
        })
    }
}

#[derive(Clone, Default)]
#[cfg_attr(feature = "experimental_inspect", derive(Debug))]
pub struct CreateSegment {
    pub name: String,
    pub segment_id: SegmentId,
    pub first_page: u64,
}

impl CreateSegment {
    pub fn new(name: &str, segment_id: SegmentId, first_page: u64) -> CreateSegment {
        CreateSegment {
            name: name.into(),
            segment_id,
            first_page,
        }
    }
}

impl JournalEntry for CreateSegment {
    fn get_type(&self) -> u8 {
        8
    }

    fn write(&self, buffer: &mut dyn InfallibleWrite) -> PERes<()> {
        self.segment_id.write_varint(buffer);
        buffer.write_varint_u64(self.first_page);
        buffer.write_varint_u16(self.name.len() as u16);
        buffer.write_all(self.name.as_bytes());
        Ok(())
    }

    fn read_new(buffer: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        let segment_id = SegmentId::read_varint(buffer)?;
        let first_page = buffer.read_varint_u64()?;
        let string_size = buffer.read_varint_u16()?;
        let mut slice: Vec<u8> = vec![0; string_size as usize];
        buffer.read_exact(&mut slice[0..string_size as usize])?;
        let name = str::from_utf8(&slice[0..string_size as usize])?.into();
        Ok(CreateSegment {
            name,
            segment_id,
            first_page,
        })
    }
}

#[derive(Clone, Default)]
#[cfg_attr(feature = "experimental_inspect", derive(Debug))]
pub struct DropSegment {
    pub name: String,
    pub segment_id: SegmentId,
}

impl DropSegment {
    pub fn new(name: &str, segment_id: SegmentId) -> DropSegment {
        DropSegment {
            name: name.into(),
            segment_id,
        }
    }
}
impl JournalEntry for DropSegment {
    fn get_type(&self) -> u8 {
        9
    }

    fn write(&self, buffer: &mut dyn InfallibleWrite) -> PERes<()> {
        self.segment_id.write_varint(buffer);
        buffer.write_varint_u16(self.name.len() as u16);
        buffer.write_all(self.name.as_bytes());
        Ok(())
    }

    fn read_new(buffer: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        let segment_id = SegmentId::read_varint(buffer)?;
        let string_size = buffer.read_varint_u16()?;
        let mut slice: Vec<u8> = vec![0; string_size as usize];
        buffer.read_exact(&mut slice[0..string_size as usize])?;
        let name = str::from_utf8(&slice[0..string_size as usize])?.into();
        Ok(DropSegment { name, segment_id })
    }
}

#[derive(Clone, PartialEq, Debug, PartialOrd, Ord, Eq, Default)]
pub struct FreedPage {
    pub page: u64,
}

impl FreedPage {
    pub fn new(page: u64) -> FreedPage {
        FreedPage { page }
    }
}

impl JournalEntry for FreedPage {
    fn get_type(&self) -> u8 {
        12
    }

    fn write(&self, buffer: &mut dyn InfallibleWrite) -> PERes<()> {
        buffer.write_varint_u64(self.page);
        Ok(())
    }

    fn read_new(buffer: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        let page = buffer.read_varint_u64()?;
        Ok(FreedPage { page })
    }
}

#[derive(Default)]
#[cfg_attr(feature = "experimental_inspect", derive(Debug))]
pub struct PrepareCommit {}

impl PrepareCommit {
    pub fn new() -> PrepareCommit {
        PrepareCommit {}
    }
}

impl JournalEntry for PrepareCommit {
    fn get_type(&self) -> u8 {
        3
    }

    fn write(&self, _: &mut dyn InfallibleWrite) -> PERes<()> {
        Ok(())
    }

    fn read_new(_: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        Ok(PrepareCommit {})
    }
}

#[derive(Default)]
#[cfg_attr(feature = "experimental_inspect", derive(Debug))]
pub struct Commit {}

impl Commit {
    pub fn new() -> Commit {
        Commit {}
    }
}

impl JournalEntry for Commit {
    fn get_type(&self) -> u8 {
        4
    }

    fn write(&self, _: &mut dyn InfallibleWrite) -> PERes<()> {
        Ok(())
    }

    fn read_new(_: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        Ok(Commit {})
    }
}

#[derive(Default)]
#[cfg_attr(feature = "experimental_inspect", derive(Debug))]
pub struct Cleanup {}

impl Cleanup {
    pub fn new() -> Cleanup {
        Cleanup {}
    }
}

impl JournalEntry for Cleanup {
    fn get_type(&self) -> u8 {
        14
    }

    fn write(&self, _: &mut dyn InfallibleWrite) -> PERes<()> {
        Ok(())
    }

    fn read_new(_: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        Ok(Cleanup {})
    }
}

#[derive(Default)]
#[cfg_attr(feature = "experimental_inspect", derive(Debug))]
pub struct Rollback {}

impl Rollback {
    pub fn new() -> Rollback {
        Rollback {}
    }
}

impl JournalEntry for Rollback {
    fn get_type(&self) -> u8 {
        7
    }

    fn write(&self, _: &mut dyn InfallibleWrite) -> PERes<()> {
        Ok(())
    }

    fn read_new(_: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        Ok(Rollback {})
    }
}

#[derive(Default)]
#[cfg_attr(feature = "experimental_inspect", derive(Debug))]
pub struct Metadata {
    pub strategy: TxStrategy,
    pub meta_id: Vec<u8>,
}

impl Metadata {
    pub fn new(strategy: &TxStrategy, meta_id: Vec<u8>) -> Metadata {
        Metadata {
            strategy: strategy.clone(),
            meta_id,
        }
    }
}

impl JournalEntry for Metadata {
    fn get_type(&self) -> u8 {
        11
    }

    fn write(&self, write: &mut dyn InfallibleWrite) -> PERes<()> {
        write.write_varint_u8(self.strategy.value());
        let len = self.meta_id.len();
        write.write_varint_u16(len as u16);
        write.write_all(&self.meta_id);
        Ok(())
    }

    fn read_new(buffer: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        let strategy = TxStrategy::from_value(buffer.read_varint_u8()?);
        let len = buffer.read_varint_u16()?;
        let mut slice: Vec<u8> = vec![0; len as usize];
        buffer.read_exact(&mut slice[0..len as usize])?;
        let meta_id = slice;
        Ok(Metadata { strategy, meta_id })
    }
}

#[derive(Clone, PartialEq, PartialOrd, Ord, Eq, Default)]
pub struct RollbackPage {
    pub segment: SegmentId,
    pub recref: RecRef,
    pub record_page: u64,
}

#[cfg(feature = "experimental_inspect")]
impl Debug for RollbackPage {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("RollbackPage")
            .field("segment", &self.segment)
            .field("recref", &format!("{}", self.recref))
            .field("record_page", &self.record_page)
            .finish()
    }
}
impl RollbackPage {
    pub fn new(segment: SegmentId, rec_ref: &RecRef, record: u64) -> Self {
        Self {
            segment,
            recref: *rec_ref,
            record_page: record,
        }
    }
}

impl JournalEntry for RollbackPage {
    fn get_type(&self) -> u8 {
        15
    }

    fn write(&self, buffer: &mut dyn InfallibleWrite) -> PERes<()> {
        self.segment.write_varint(buffer);
        buffer.write_varint_u64(self.recref.page);
        buffer.write_varint_u32(self.recref.pos);
        buffer.write_varint_u64(self.record_page);
        Ok(())
    }

    fn read_new(buffer: &mut dyn Read) -> PERes<Self>
    where
        Self: Sized,
    {
        let segment = SegmentId::read_varint(buffer)?;
        let recref_page = buffer.read_varint_u64()?;
        let recref_pos = buffer.read_varint_u32()?;
        let record_page = buffer.read_varint_u64()?;
        Ok(RollbackPage {
            segment,
            recref: RecRef::new(recref_page, recref_pos),
            record_page,
        })
    }
}
