// This file is part of hnefatafl-copenhagen.
//
// hnefatafl-copenhagen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// hnefatafl-copenhagen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use std::fmt;

use anyhow::Context;
use serde::{Deserialize, Serialize};

pub const DAY: i64 = 24 * 60 * 60 * 1_000;
pub const HOUR: i64 = 60 * 60 * 1_000;
pub const MINUTE: i64 = 60 * 1_000;
pub const SECOND: i64 = 1_000;

#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Time {
    pub add_seconds: i64,
    pub milliseconds_left: i64,
}

impl Time {
    #[must_use]
    pub fn time_left(&self) -> String {
        time_left(self.milliseconds_left)
    }
}

impl Default for Time {
    fn default() -> Self {
        Self {
            add_seconds: 10,
            milliseconds_left: 15 * MINUTE,
        }
    }
}

impl fmt::Display for Time {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let days = self.milliseconds_left / DAY;
        let hours = (self.milliseconds_left % DAY) / HOUR;
        let minutes = (self.milliseconds_left % HOUR) / MINUTE;
        let seconds = (self.milliseconds_left % MINUTE) / SECOND;

        let add_hours = self.add_seconds / (60 * 60);
        let add_minutes = (self.add_seconds % (60 * 60)) / 60;
        let add_seconds = self.add_seconds % 60;

        if days == 0 {
            write!(
                f,
                "{hours:02}:{minutes:02}:{seconds:02} | {add_hours:02}:{add_minutes:02}:{add_seconds:02}"
            )
        } else {
            write!(
                f,
                "{days} {hours:02}:{minutes:02}:{seconds:02} | {add_hours:02}:{add_minutes:02}:{add_seconds:02}"
            )
        }
    }
}

#[derive(Clone, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum TimeSettings {
    Timed(Time),
    UnTimed,
}

impl TimeSettings {
    #[must_use]
    pub fn time_left(&self) -> String {
        match self {
            Self::Timed(time) => time.time_left(),
            Self::UnTimed => "-".to_string(),
        }
    }
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct TimeLeft {
    pub milliseconds_left: i64,
}

impl fmt::Display for TimeLeft {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", time_left(self.milliseconds_left))
    }
}

impl From<Time> for TimeLeft {
    fn from(time: Time) -> Self {
        TimeLeft {
            milliseconds_left: time.milliseconds_left,
        }
    }
}

impl TryFrom<TimeSettings> for TimeLeft {
    type Error = anyhow::Error;

    fn try_from(time: TimeSettings) -> Result<TimeLeft, anyhow::Error> {
        match time {
            TimeSettings::Timed(time) => Ok(TimeLeft {
                milliseconds_left: time.milliseconds_left,
            }),
            TimeSettings::UnTimed => Err(anyhow::Error::msg("the time settings are un-timed")),
        }
    }
}

impl Default for TimeSettings {
    fn default() -> Self {
        Self::Timed(Time {
            ..Default::default()
        })
    }
}

impl fmt::Debug for TimeSettings {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Timed(time) => {
                write!(f, "fischer {} {}", time.milliseconds_left, time.add_seconds)
            }
            Self::UnTimed => write!(f, "un-timed _ _"),
        }
    }
}

impl fmt::Display for TimeSettings {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Timed(time) => write!(f, "{time}"),
            Self::UnTimed => write!(f, ""),
        }
    }
}

impl From<TimeSettings> for bool {
    fn from(time_settings: TimeSettings) -> Self {
        match time_settings {
            TimeSettings::Timed(_) => true,
            TimeSettings::UnTimed => false,
        }
    }
}

impl TryFrom<Vec<&str>> for TimeSettings {
    type Error = anyhow::Error;

    fn try_from(args: Vec<&str>) -> anyhow::Result<Self> {
        let err_msg = "expected: 'time_settings un-timed' or 'time_settings fischer MILLISECONDS ADD_SECONDS'";

        if Some("un-timed").as_ref() == args.get(1) {
            return Ok(Self::UnTimed);
        }

        if args.len() < 4 {
            return Err(anyhow::Error::msg(err_msg));
        }

        if "fischer" == args[1] {
            let arg_2 = args[2]
                .parse::<i64>()
                .context("time_settings: arg 2 is not an integer")?;

            let arg_3 = args[3]
                .parse::<i64>()
                .context("time_settings: arg 3 is not an integer")?;

            Ok(Self::Timed(Time {
                add_seconds: arg_3,
                milliseconds_left: arg_2,
            }))
        } else {
            Err(anyhow::Error::msg(err_msg))
        }
    }
}

fn time_left(milliseconds_left: i64) -> String {
    let days = milliseconds_left / DAY;
    let hours = (milliseconds_left % DAY) / HOUR;
    let minutes = (milliseconds_left % HOUR) / MINUTE;
    let seconds = (milliseconds_left % MINUTE) / SECOND;

    if days == 0 {
        if hours == 0 {
            if minutes == 0 {
                format!("{seconds:02}")
            } else {
                format!("{minutes:02}:{seconds:02}")
            }
        } else {
            format!("{hours:02}:{minutes:02}:{seconds:02}")
        }
    } else {
        format!("{days} {hours:02}:{minutes:02}:{seconds:02}")
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum TimeEnum {
    Classical,
    Infinity,
    Long,
    #[default]
    Rapid,
    VeryLong,
}

impl From<TimeEnum> for TimeSettings {
    fn from(time_enum: TimeEnum) -> TimeSettings {
        match time_enum {
            TimeEnum::Classical => TimeSettings::Timed(Time {
                add_seconds: 20,
                milliseconds_left: 30 * MINUTE,
            }),
            TimeEnum::Rapid => TimeSettings::Timed(Time {
                add_seconds: 10,
                milliseconds_left: 15 * MINUTE,
            }),

            TimeEnum::Long => TimeSettings::Timed(Time {
                add_seconds: 60 * 60 * 6,
                milliseconds_left: 3 * DAY,
            }),
            TimeEnum::VeryLong => TimeSettings::Timed(Time {
                add_seconds: 60 * 60 * 15,
                milliseconds_left: 12 * 15 * HOUR,
            }),
            TimeEnum::Infinity => TimeSettings::UnTimed,
        }
    }
}

impl fmt::Display for TimeEnum {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Classical => write!(f, "00:30:00 | 00:00:20"),
            Self::Infinity => write!(f, ""),
            Self::Long => write!(f, "3 00:00:00 | 6:00:00"),
            Self::Rapid => write!(f, "00:15:00 | 00:00:10 "),
            Self::VeryLong => write!(f, "7 12:00:00 | 15:00:00 "),
        }
    }
}