pub mod directory;

use chrono::{DateTime, Local, NaiveDate, NaiveTime};
use std::fmt::Display;
use std::io;
use std::io::Write;
use std::time::{Duration, SystemTime};

pub trait WriteFactory {
    type Writer: Write;

    fn new_writer(&self, name: impl Display) -> io::Result<Self::Writer>;
}

pub enum RollingType {
    Duration(Duration, SystemTime),
    Daily(NaiveTime, Option<NaiveDate>),
    Size(usize, usize)
}

impl RollingType {
    pub fn daily_at(time: NaiveTime) -> RollingType {
        RollingType::Daily(time, None)
    }

    pub fn every(duration: Duration, starting: SystemTime) -> RollingType {
        RollingType::Duration(duration, starting + duration)
    }

    pub fn after_written(n: usize) -> RollingType {
        RollingType::Size(n, n)
    }
}

pub struct Rolling<WF: WriteFactory> {
    write_factory: WF,
    underlying: WF::Writer,
    rolling_type: RollingType
}

impl<WF: WriteFactory> Rolling<WF> {
    pub fn new(write_factory: WF, rolling_type: RollingType) -> io::Result<Self> {
        let underlying = write_factory.new_writer(Local::now().to_rfc3339())?;
        Ok(Self {
            write_factory,
            underlying,
            rolling_type,
        })
    }

    fn switch_writer(&mut self, now: DateTime<Local>) -> io::Result<()> {
        self.underlying.flush()?;
        self.underlying = self.write_factory.new_writer(now.to_rfc3339())?;
        Ok(())
    }
}

impl<WF: WriteFactory> Write for Rolling<WF> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match self.rolling_type {
            RollingType::Duration(duration, ref mut next) => {
                let now = SystemTime::now();
                if now >= *next {
                    *next += duration;
                    self.switch_writer(now.into())?;
                }
            },
            RollingType::Daily(time, ref mut last_date) => {
                let now = Local::now();
                let current_date = now.date_naive();
                if last_date.is_none_or(|date| date < current_date) && time >= now.time() {
                    *last_date = Some(current_date);
                    self.switch_writer(now)?;
                }
            }
            RollingType::Size(size, ref mut remaining) => {
                let written = self.underlying.write(&buf[0..*remaining])?;
                if *remaining - written <= 0 {
                    *remaining = size;
                    self.switch_writer(Local::now())?;
                }
                return Ok(written);
            }
        }
        self.underlying.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> { self.underlying.flush() }
}