use std::fmt::Display;

use bstr::{BStr, BString};

use crate::parse::{Event, EventRef};

impl Event {
    /// Shift all backing-buffer spans in this event forward by `offset` bytes.
    pub(crate) fn rebase(&mut self, offset: usize) -> Result<(), crate::parse::span::Error> {
        match self {
            Event::Comment(comment) => comment.text.rebase(offset),
            Event::SectionHeader(header) => header.rebase(offset),
            Event::SectionValueName(name) => name.rebase(offset),
            Event::Value(bytes)
            | Event::Newline(bytes)
            | Event::ValueNotDone(bytes)
            | Event::ValueDone(bytes)
            | Event::Whitespace(bytes) => bytes.rebase(offset),
            Event::KeyValueSeparator => Ok(()),
        }
    }

    pub(crate) fn copy_to_backing_in(
        &self,
        source: &[u8],
        target: &mut Vec<u8>,
    ) -> Result<Event, crate::parse::span::Error> {
        Ok(match self {
            Event::Comment(comment) => Event::Comment(comment.copy_to_backing_in(source, target)?),
            Event::SectionHeader(header) => Event::SectionHeader(header.copy_to_backing_in(source, target)?),
            Event::SectionValueName(name) => Event::SectionValueName(name.copy_to_backing_in(source, target)?),
            Event::Value(bytes) => Event::Value(bytes.copy_to_backing_in(source, target)?),
            Event::Newline(bytes) => Event::Newline(bytes.copy_to_backing_in(source, target)?),
            Event::ValueNotDone(bytes) => Event::ValueNotDone(bytes.copy_to_backing_in(source, target)?),
            Event::ValueDone(bytes) => Event::ValueDone(bytes.copy_to_backing_in(source, target)?),
            Event::Whitespace(bytes) => Event::Whitespace(bytes.copy_to_backing_in(source, target)?),
            Event::KeyValueSeparator => Event::KeyValueSeparator,
        })
    }

    /// Resolve this event against `backing` without allocating.
    pub(crate) fn as_ref_in<'a>(&'a self, backing: &'a [u8]) -> EventRef<'a> {
        match self {
            Event::Comment(comment) => EventRef::Comment {
                tag: comment.tag,
                text: comment.text.as_bstr_in(backing),
            },
            Event::SectionHeader(header) => EventRef::SectionHeader {
                name: header.name.as_bstr_in(backing),
                separator: header.separator.as_ref().map(|separator| separator.as_bstr_in(backing)),
                subsection_name: header
                    .subsection_name
                    .as_ref()
                    .map(|subsection_name| subsection_name.value_in(backing)),
            },
            Event::SectionValueName(name) => EventRef::SectionValueName(name.as_bstr_in(backing)),
            Event::Value(bytes) => EventRef::Value(bytes.as_bstr_in(backing)),
            Event::Newline(bytes) => EventRef::Newline(bytes.as_bstr_in(backing)),
            Event::ValueNotDone(bytes) => EventRef::ValueNotDone(bytes.as_bstr_in(backing)),
            Event::ValueDone(bytes) => EventRef::ValueDone(bytes.as_bstr_in(backing)),
            Event::Whitespace(bytes) => EventRef::Whitespace(bytes.as_bstr_in(backing)),
            Event::KeyValueSeparator => EventRef::KeyValueSeparator,
        }
    }

    /// Return the event's principal byte payload resolved against `backing`, without allocating.
    ///
    /// This is lossy for events whose serialized representation consists of multiple pieces:
    /// section headers omit their brackets, separator, and subsection; comments omit their marker;
    /// and continued values omit their trailing backslash. Use [`Self::write_to_in()`] when the
    /// complete serialized event is required.
    pub(crate) fn to_bstr_lossy_in<'a>(&'a self, backing: &'a [u8]) -> &'a BStr {
        match self {
            Self::ValueNotDone(e) | Self::Whitespace(e) | Self::Newline(e) | Self::Value(e) | Self::ValueDone(e) => {
                e.as_bstr_in(backing)
            }
            Self::KeyValueSeparator => "=".into(),
            Self::SectionValueName(k) => k.as_bstr_in(backing),
            Self::SectionHeader(h) => h.name.as_bstr_in(backing),
            Self::Comment(c) => c.text.as_bstr_in(backing),
        }
    }

    pub(crate) fn write_to_in(&self, backing: &[u8], out: &mut dyn std::io::Write) -> std::io::Result<()> {
        match self {
            Self::ValueNotDone(e) => {
                out.write_all(e.as_slice_in(backing))?;
                out.write_all(br"\")
            }
            Self::Whitespace(e) | Self::Newline(e) | Self::Value(e) | Self::ValueDone(e) => {
                out.write_all(e.as_slice_in(backing))
            }
            Self::KeyValueSeparator => out.write_all(b"="),
            Self::SectionValueName(k) => out.write_all(k.as_slice_in(backing)),
            Self::SectionHeader(h) => h.write_to_in(backing, out),
            Self::Comment(c) => c.write_to_in(backing, out),
        }
    }
}

impl EventRef<'_> {
    /// Turn ourselves into the text we represent, lossy.
    ///
    /// Note that this mirrors `Event::to_bstr_lossy_in()`.
    pub fn to_bstr_lossy(&self) -> &BStr {
        match self {
            EventRef::ValueNotDone(bytes)
            | EventRef::Whitespace(bytes)
            | EventRef::Newline(bytes)
            | EventRef::Value(bytes)
            | EventRef::ValueDone(bytes) => bytes,
            EventRef::KeyValueSeparator => "=".into(),
            EventRef::SectionValueName(name) => name,
            EventRef::SectionHeader { name, .. } => name,
            EventRef::Comment { text, .. } => text,
        }
    }

    /// Stream ourselves to the given `out`, reproducing this event mostly losslessly.
    ///
    /// Quoted subsection names are the exception: [`EventRef::SectionHeader`] contains the decoded
    /// subsection name, so this method escapes it again and may normalize its original escape
    /// spelling. The resulting header has the same subsection name, but may not be byte-for-byte
    /// identical to the parsed input.
    pub fn write_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> {
        match self {
            EventRef::ValueNotDone(bytes) => {
                out.write_all(bytes)?;
                out.write_all(br"\")
            }
            EventRef::Whitespace(bytes)
            | EventRef::Newline(bytes)
            | EventRef::Value(bytes)
            | EventRef::ValueDone(bytes) => out.write_all(bytes),
            EventRef::KeyValueSeparator => out.write_all(b"="),
            EventRef::SectionValueName(name) => out.write_all(name),
            EventRef::SectionHeader {
                name,
                separator,
                subsection_name,
            } => {
                out.write_all(b"[")?;
                out.write_all(name)?;
                if let (Some(separator), Some(subsection_name)) = (separator, subsection_name) {
                    out.write_all(separator)?;
                    if *separator == b"." {
                        out.write_all(subsection_name)?;
                    } else {
                        out.write_all(b"\"")?;
                        crate::parse::section::header::write_escaped_subsection(subsection_name, &mut *out)?;
                        out.write_all(b"\"")?;
                    }
                }
                out.write_all(b"]")
            }
            EventRef::Comment { tag, text } => {
                out.write_all(&[*tag])?;
                out.write_all(text)
            }
        }
    }
}

impl Display for EventRef<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&BString::from(self), f)
    }
}

impl From<&EventRef<'_>> for BString {
    fn from(event: &EventRef<'_>) -> Self {
        let mut buf = Vec::new();
        event.write_to(&mut buf).expect("io error impossible");
        buf.into()
    }
}
