//! The XML containers
use super::codec::Encode;
use bytes::{BufMut, BytesMut};
use std::borrow::BorrowMut;
use std::sync::Arc;

/// Provides information of the location for the schema.
#[derive(Debug, Clone, PartialEq)]
pub struct XmlSchema {
    db_name: String,
    owner: String,
    collection: String,
}

impl XmlSchema {
    pub(crate) fn new(
        db_name: impl ToString,
        owner: impl ToString,
        collection: impl ToString,
    ) -> Self {
        Self {
            db_name: db_name.to_string(),
            owner: owner.to_string(),
            collection: collection.to_string(),
        }
    }

    /// Specifies the name of the database where the schema collection is defined.
    pub fn db_name(&self) -> &str {
        &self.db_name
    }

    /// Specifies the name of the relational schema containing the schema collection.
    pub fn owner(&self) -> &str {
        &self.owner
    }

    /// Specifies the name of the XML schema collection to which the type is
    /// bound.
    pub fn collection(&self) -> &str {
        &self.collection
    }
}

/// A representation of XML data in TDS. Holds the data as a UTF-8 string and
/// and optional information about the schema.
#[derive(Debug, Clone, PartialEq)]
pub struct XmlData {
    data: String,
    schema: Option<Arc<XmlSchema>>,
}

impl XmlData {
    /// Create a new XmlData with the given string. Validation of the XML data
    /// happens in the database.
    pub fn new(data: impl ToString) -> Self {
        Self {
            data: data.to_string(),
            schema: None,
        }
    }

    pub(crate) fn set_schema(&mut self, schema: Arc<XmlSchema>) {
        self.schema = Some(schema);
    }

    /// Returns information about the schema of the XML file, if existing.
    pub fn schema(&self) -> Option<&XmlSchema> {
        self.schema.as_ref().map(|s| &**s)
    }

    /// Takes the XML string out from the struct.
    pub fn into_string(self) -> String {
        self.data
    }
}

impl ToString for XmlData {
    fn to_string(&self) -> String {
        self.data.to_string()
    }
}

impl AsRef<str> for XmlData {
    fn as_ref(&self) -> &str {
        self.data.as_ref()
    }
}

impl Encode<BytesMut> for XmlData {
    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
        dst.put_u8(0);
        dst.put_u64_le(0xfffffffffffffffe as u64);

        let mut length = 0u32;
        let len_pos = dst.len();

        // writing the length later
        dst.put_u32_le(length);

        for chr in self.data.encode_utf16() {
            length += 1;
            dst.put_u16_le(chr);
        }

        // PLP_TERMINATOR
        dst.put_u32_le(0);

        let dst: &mut [u8] = dst.borrow_mut();
        let bytes = (length * 2).to_le_bytes(); // u32, four bytes

        // writing the length
        for (i, byte) in bytes.iter().enumerate() {
            dst[len_pos + i] = *byte;
        }

        Ok(())
    }
}
