extern crate alloc;

use core::hash::Hash;

use alloc::collections::BTreeSet;
use alloc::rc::Rc;
use alloc::sync::Arc;

use beancount_types::Account;
use beancount_types::Directive;
use delegate::delegate;
use time::Date;

pub mod error;
pub mod runner;
pub mod utilities;

pub trait ImporterProtocol {
    type Error;

    fn account(&self, buffer: &[u8]) -> Result<Account, Self::Error>;

    fn date(&self, _buffer: &[u8]) -> Option<Result<Date, Self::Error>> {
        None
    }

    fn extract(&self, buffer: &[u8], existing: &[Directive])
        -> Result<Vec<Directive>, Self::Error>;

    fn filename(&self, _buffer: &[u8]) -> Option<Result<String, Self::Error>> {
        None
    }

    fn identify(&self, buffer: &[u8]) -> Result<bool, Self::Error>;

    fn name(&self) -> &'static str;

    #[doc(hidden)]
    fn typetag_deserialize(&self);
}

impl<I> ImporterProtocol for &I
where
    I: ImporterProtocol + ?Sized,
{
    type Error = I::Error;

    delegate! {
        to (*self) {
            fn account(&self, buffer: &[u8]) -> Result<Account, Self::Error>;
            fn date(&self, _buffer: &[u8]) -> Option<Result<Date, Self::Error>>;
            fn extract(&self, buffer: &[u8], existing: &[Directive]) -> Result<Vec<Directive>, Self::Error>;
            fn filename(&self, buffer: &[u8]) -> Option<Result<String, Self::Error>>;
            fn identify(&self, buffer: &[u8]) -> Result<bool, Self::Error>;
            fn name(&self) -> &'static str;
            fn typetag_deserialize(&self);
        }
    }
}

impl<I> ImporterProtocol for Arc<I>
where
    I: ImporterProtocol + ?Sized,
{
    type Error = I::Error;

    delegate! {
        to (**self) {
            fn account(&self, buffer: &[u8]) -> Result<Account, Self::Error>;
            fn date(&self, _buffer: &[u8]) -> Option<Result<Date, Self::Error>>;
            fn extract(&self, buffer: &[u8], existing: &[Directive]) -> Result<Vec<Directive>, Self::Error>;
            fn filename(&self, buffer: &[u8]) -> Option<Result<String, Self::Error>>;
            fn identify(&self, buffer: &[u8]) -> Result<bool, Self::Error>;
            fn name(&self) -> &'static str;
            fn typetag_deserialize(&self);
        }
    }
}

impl<I> ImporterProtocol for Box<I>
where
    I: ImporterProtocol + ?Sized,
{
    type Error = I::Error;

    delegate! {
        to (**self) {
            fn account(&self, buffer: &[u8]) -> Result<Account, Self::Error>;
            fn date(&self, _buffer: &[u8]) -> Option<Result<Date, Self::Error>>;
            fn extract(&self, buffer: &[u8], existing: &[Directive]) -> Result<Vec<Directive>, Self::Error>;
            fn filename(&self, buffer: &[u8]) -> Option<Result<String, Self::Error>>;
            fn identify(&self, buffer: &[u8]) -> Result<bool, Self::Error>;
            fn name(&self) -> &'static str;
            fn typetag_deserialize(&self);
        }
    }
}

impl<I> ImporterProtocol for Rc<I>
where
    I: ImporterProtocol + ?Sized,
{
    type Error = I::Error;

    delegate! {
        to (**self) {
            fn account(&self, buffer: &[u8]) -> Result<Account, Self::Error>;
            fn date(&self, _buffer: &[u8]) -> Option<Result<Date, Self::Error>>;
            fn extract(&self, buffer: &[u8], existing: &[Directive]) -> Result<Vec<Directive>, Self::Error>;
            fn filename(&self, buffer: &[u8]) -> Option<Result<String, Self::Error>>;
            fn identify(&self, buffer: &[u8]) -> Result<bool, Self::Error>;
            fn name(&self) -> &'static str;
            fn typetag_deserialize(&self);
        }
    }
}

#[derive(Debug)]
pub struct ImporterRegistry<E> {
    importers: BTreeSet<NamedImporter<E>>,
}

impl<E> Default for ImporterRegistry<E> {
    fn default() -> Self {
        let importers = Default::default();
        Self { importers }
    }
}

impl<E> ImporterRegistry<E> {
    pub fn register_importer<I>(&mut self, importer: I) -> &mut Self
    where
        I: ImporterProtocol<Error = E> + 'static,
    {
        let name = importer.name();
        if !self.importers.insert(NamedImporter::new(importer)) {
            tracing::warn!(importer = name, "importer has already been registered");
        }

        self
    }
}

impl<E> ImporterRegistry<E> {
    pub fn iter(&self) -> impl Iterator<Item = &dyn ImporterProtocol<Error = E>> {
        self.importers.iter().map(|importer| &*importer.0)
    }
}

struct NamedImporter<E>(Box<dyn ImporterProtocol<Error = E>>);

impl<E> NamedImporter<E> {
    fn new<I>(importer: I) -> Self
    where
        I: ImporterProtocol<Error = E> + 'static,
    {
        Self(Box::new(importer))
    }
}

impl<E> core::fmt::Debug for NamedImporter<E> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_tuple("NamedImporter")
            .field(&self.0.name())
            .finish()
    }
}

impl<E> Eq for NamedImporter<E> {}

impl<E> From<Box<dyn ImporterProtocol<Error = E>>> for NamedImporter<E> {
    fn from(value: Box<dyn ImporterProtocol<Error = E>>) -> Self {
        Self(value)
    }
}

impl<E> Hash for NamedImporter<E> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.0.name().hash(state);
    }
}

impl<E> Ord for NamedImporter<E> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.0.name().cmp(other.0.name())
    }
}

impl<E> PartialEq for NamedImporter<E> {
    fn eq(&self, other: &Self) -> bool {
        self.cmp(other).is_eq()
    }
}

impl<E> PartialOrd for NamedImporter<E> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}