use chrono::{Duration, FixedOffset, offset::LocalResult, TimeZone as ChronoTimeZone, prelude::*};

trait DateTimeGetter {
    fn get_datetime(&self) -> Option<DateTime<FixedOffset>> {
        if self.get_month() == 0 || self.get_day() == 0 {
            None
        }

        else {
            match FixedOffset::east_opt(self.get_timezone_sec())?.with_ymd_and_hms(self.get_year(), self.get_month(), self.get_day(), self.get_hour(), self.get_minute(), self.get_second()) {
                LocalResult::Single(date_time) => date_time.with_nanosecond(self.get_milli_second()*1000*1000),
                _                              => None,   
            }
        }
    }

    fn get_year(&self)         -> i32;
    fn get_month(&self)        -> u32;
    fn get_day(&self)          -> u32;

    fn get_hour(&self)         -> u32;
    fn get_minute(&self)       -> u32;
    fn get_second(&self)       -> u32;
    fn get_milli_second(&self) -> u32;
    fn get_timezone_sec(&self) -> i32;
}

#[repr(packed(1))]
pub struct Date {
        year:      [u8; 4],
        month:     [u8; 2],
        day:       [u8; 2],
        hour:      [u8; 2],
        minute:    [u8; 2],
        second:    [u8; 2],
        ten_ms:    [u8; 2],
    pub time_zone: TimeZone
}

#[repr(packed(1))]
pub struct SmallDate {
        year:      u8,
        month:     u8,
        day:       u8,
        hour:      u8,
        minute:    u8,
        second:    u8,
    pub time_zone: TimeZone
}

#[repr(packed(1))]
pub struct TimeZone {
    //Can be between -48 (GMT-12) and 52 (GMT+13)
    value: i8
}

impl std::fmt::Display for dyn DateTimeGetter {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.get_datetime() {
            Some(datetime) => write!(f, "{}", datetime),
            None           => write!(f, "<Empty>"),
        }
    }
}

impl Date {
    pub const SECS_PER_HOUR:i32 = 3600;

    pub fn new() -> Date {
        Date{
            year:      ['0' as u8;4], 
            month:     ['0' as u8;2],
            day:       ['0' as u8;2],
            hour:      ['0' as u8;2],
            minute:    ['0' as u8;2],
            second:    ['0' as u8;2],
            ten_ms:    ['0' as u8;2],
            time_zone: TimeZone::new()
        }
    }

    pub fn now() -> Date {
        let     utc  = Local::now();
        let mut date = Date::new();
        
        date.set_year(utc.year() as u32);
        date.set_month(utc.month());
        date.set_day(utc.day());

        date.set_hour(utc.hour());
        date.set_minute(utc.minute());
        date.set_second(utc.second());
        date.set_milliseconds((utc.timestamp_millis()%1000) as u32);
        date.time_zone.set_from_date_time(&utc);

        date
    }

    pub fn set_year(&mut self, year: u32) {
        self.year = Self::set_value(year);
    }

    pub fn set_month(&mut self, month: u32) {
        self.month = Self::set_value(month);
    }

    pub fn set_day(&mut self, day: u32) {
        self.day = Self::set_value(day);
    }
    
    pub fn set_hour(&mut self, hour: u32) {
        self.hour = Self::set_value(hour);
    }

    pub fn set_minute(&mut self, minute: u32) {
        self.minute = Self::set_value(minute);
    }
    
    pub fn set_second(&mut self, second: u32) {
        self.second = Self::set_value(second);
    }

    pub fn set_milliseconds(&mut self, milliseconds: u32) {
        self.ten_ms = Self::set_value(milliseconds/10);
    }

    fn set_value<const SIZE:usize>(mut number: u32) -> [u8;SIZE] {
        let mut value = ['0' as u8;SIZE];

        for i in 0..SIZE {
            value[SIZE - 1 - i] = ('0' as u32 + (number%10)) as u8;
            number /= 10;
        }
        value
    }

    fn get_value<const SIZE:usize>(data: &[u8; SIZE]) -> u32 {
        let mut number = 0u32;

        for i in (SIZE - 1)..=0 {
            number += (data[i] - '0' as u8) as u32;
            number *= 10;
        }
        number
    }
}

impl std::fmt::Display for Date {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        (self as &dyn DateTimeGetter).fmt(f)
    }   
}

impl DateTimeGetter for Date {
    fn get_year(&self) -> i32 {
        Self::get_value(&self.year) as i32
    }

    fn get_month(&self) -> u32 {
        Self::get_value(&self.month)
    }

    fn get_day(&self) -> u32 {
        Self::get_value(&self.day)
    }

    fn get_hour(&self) -> u32 {
        Self::get_value(&self.hour)
    }

    fn get_minute(&self) -> u32 {
        Self::get_value(&self.minute)
    }

    fn get_second(&self) -> u32 {
        Self::get_value(&self.second)
    }

    fn get_milli_second(&self) -> u32 {
        Self::get_value(&self.ten_ms)*10
    }

    fn get_timezone_sec(&self) -> i32 {
        self.time_zone.get_gmt_offset()*15*60
    }
}

impl SmallDate {
    pub fn now() -> SmallDate {
        let     utc  = Local::now();
        let mut date = SmallDate::default();
        
        date.set_year(utc.year() as u32);
        date.set_month(utc.month());
        date.set_day(utc.day());

        date.set_hour(utc.hour());
        date.set_minute(utc.minute());
        date.set_second(utc.second());
        date.time_zone.set_from_date_time(&utc);

        date
    }

    pub fn set_year(&mut self, year: u32) {
        self.year = (year - 1900) as u8;
    }

    pub fn set_month(&mut self, month: u32) {
        self.month = month as u8;
    }

    pub fn set_day(&mut self, day: u32) {
        self.day = day as u8;
    }

    pub fn set_hour(&mut self, hour: u32) {
        self.hour = hour as u8;
    }

    pub fn set_minute(&mut self, minute: u32) {
        self.minute = minute as u8;
    }

    pub fn set_second(&mut self, second: u32) {
        self.second = second as u8;
    }
}

impl std::fmt::Display for SmallDate {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        (self as &dyn DateTimeGetter).fmt(f)
    }   
}

impl std::default::Default for SmallDate {
    fn default() -> SmallDate {
        SmallDate{year: 0u8, month: 0u8, day: 0u8, hour: 0u8, minute: 0u8, second: 0u8, time_zone: TimeZone::new()}
    }
}

impl DateTimeGetter for SmallDate {
    fn get_year(&self) -> i32 {
        (self.year as i32) + 1900
    }

    fn get_month(&self) -> u32 {
        self.month as u32
    }

    fn get_day(&self) -> u32 {
        self.day as u32
    }

    fn get_hour(&self) -> u32 {
        self.hour as u32
    }

    fn get_minute(&self) -> u32 {
        self.minute as u32
    }

    fn get_second(&self) -> u32 {
        self.second as u32
    }

    fn get_milli_second(&self) -> u32 {
        0
    }

    fn get_timezone_sec(&self) -> i32 {
        self.time_zone.get_gmt_offset()*15*60
    }
}

impl TimeZone {
    pub fn new() -> TimeZone {
        TimeZone{value: 0}
    }

    pub fn set_from_date_time<Tz: chrono::TimeZone>(&mut self, time: &chrono::DateTime::<Tz>) {
        const SECS_PER_MINUTE:i32      = 60;
        const MIN_PER_QUARTER_HOUR:i32 = 15;

        self.value = (time.offset().fix().local_minus_utc()/SECS_PER_MINUTE/MIN_PER_QUARTER_HOUR) as i8;
    }

    pub fn get_gmt_offset(&self) -> i32 {
        self.value as i32
    }

    pub fn get_hour_correction(&self) -> Duration {
        Duration::hours((self.value/4) as i64)
    }
}