//! Shared models between server and client code.

pub use v1::HoldPosition;
pub use v1::HoldRole;
pub use v2::Hold;
pub use v2::Image;
pub use v2::ImageFilename;
pub use v2::ImageResolution;
pub use v2::ImageUid;
pub use v2::Method;
pub use v2::Root;
pub use v2::WallDimensions;
pub use v2::WallUid;
pub use v3::Attempt;
pub use v4::DatedAttempt;
pub use v4::Pattern;
pub use v4::Problem;
pub use v4::Transformation;
pub use v4::UserInteraction;
pub use v4::Wall;

mod semantics;

pub mod v4 {
    use super::v1;
    use super::v2;
    use super::v3;
    use chrono::DateTime;
    use chrono::Utc;
    use serde::Deserialize;
    use serde::Serialize;
    use std::collections::BTreeMap;
    use std::collections::BTreeSet;

    #[derive(Serialize, Deserialize, Debug, Clone)]
    pub struct Wall {
        pub uid: v2::WallUid,
        pub wall_dimensions: v2::WallDimensions,
        pub holds: BTreeMap<v1::HoldPosition, v2::Hold>,

        /// Canonicalized.
        pub problems: BTreeSet<Problem>,
    }

    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
    pub struct Problem {
        pub pattern: Pattern,
        pub method: v2::Method,
    }

    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
    pub struct Pattern {
        pub pattern: BTreeMap<v1::HoldPosition, v1::HoldRole>,
    }

    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Default)]
    pub struct Transformation {
        pub shift_right: u64,
        pub mirror: bool,
    }

    #[derive(Serialize, Deserialize, Debug, Clone)]
    pub struct UserInteraction {
        pub wall_uid: v2::WallUid,
        pub problem: Problem,

        /// Dates on which this problem was attempted, and how it went
        pub attempted_on: BTreeMap<chrono::DateTime<chrono::Utc>, v3::Attempt>,

        /// Is among favorite problems
        pub is_favorite: bool,
    }

    #[derive(Debug, Clone, Copy)]
    pub struct DatedAttempt {
        pub date_time: DateTime<Utc>,
        pub attempt: v3::Attempt,
    }
}

pub mod v3 {
    use super::v2;
    use derive_more::Display;
    use serde::Deserialize;
    use serde::Serialize;
    use std::collections::BTreeMap;

    /// Registers user interaction with a problem
    #[derive(Serialize, Deserialize, Debug, Clone)]
    pub struct UserInteraction {
        pub wall_uid: v2::WallUid,
        pub problem_uid: v2::ProblemUid,

        /// Dates on which this problem was attempted, and how it went
        pub attempted_on: BTreeMap<chrono::DateTime<chrono::Utc>, Attempt>,

        /// Is among favorite problems
        pub is_favorite: bool,

        /// Added to personal challenges
        pub is_saved: bool,
    }

    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Display)]
    pub enum Attempt {
        /// Tried to climb problem, but was not able to.
        Attempt,

        /// Climbed problem, but not flashed.
        Send,

        /// Flashed problem.
        Flash,
    }
}

pub mod v2 {
    use super::v1;
    use derive_more::Display;
    use serde::Deserialize;
    use serde::Serialize;
    use std::collections::BTreeMap;
    use std::collections::BTreeSet;

    #[derive(Serialize, Deserialize, Debug)]
    pub struct Root {
        pub walls: BTreeSet<WallUid>,
    }

    #[derive(Serialize, Deserialize, Debug, Clone)]
    pub struct Wall {
        pub uid: WallUid,

        pub rows: u64,
        pub cols: u64,

        pub holds: BTreeMap<v1::HoldPosition, Hold>,
        pub problems: BTreeSet<ProblemUid>,
    }

    #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
    pub struct WallDimensions {
        pub rows: u64,
        pub cols: u64,
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
    pub struct WallUid(pub uuid::Uuid);

    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
    pub struct Problem {
        pub uid: ProblemUid,
        pub name: String,
        pub set_by: String,
        pub holds: BTreeMap<v1::HoldPosition, v1::HoldRole>,
        pub method: Method,
        pub date_added: chrono::DateTime<chrono::Utc>,
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
    pub struct ProblemUid(pub uuid::Uuid);

    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Display, Copy)]
    pub enum Method {
        #[display("Feet follow hands")]
        FeetFollowHands,

        #[display("Footless plus kickboard")]
        FootlessPlusKickboard,

        #[display("Footless")]
        Footless,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct Hold {
        pub position: v1::HoldPosition,
        pub image: Option<Image>,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct Image {
        pub uid: ImageUid,
        pub resolutions: BTreeMap<ImageResolution, ImageFilename>,
    }

    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
    pub struct ImageResolution {
        pub width: u64,
        pub height: u64,
    }

    #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
    pub struct ImageFilename {
        pub filename: String,
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
    pub struct ImageUid(pub uuid::Uuid);
}

pub mod v1 {
    use serde::Deserialize;
    use serde::Serialize;
    use smart_default::SmartDefault;
    use std::collections::BTreeMap;
    use std::collections::BTreeSet;

    const STATE_VERSION: u64 = 1;

    #[derive(Serialize, Deserialize, Clone, Debug, SmartDefault)]
    pub struct PersistentState {
        /// State schema version
        #[default(STATE_VERSION)]
        pub version: u64,

        pub wall: Wall,
        pub problems: Problems,
    }

    #[derive(Serialize, Deserialize, Clone, Debug, Default)]
    pub struct Problems {
        pub problems: BTreeSet<Problem>,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct Wall {
        pub rows: u64,
        pub cols: u64,
        pub holds: BTreeMap<HoldPosition, Hold>,
    }
    impl Wall {
        pub fn new(rows: u64, cols: u64) -> Self {
            let mut holds = BTreeMap::new();
            for row in 0..rows {
                for col in 0..cols {
                    let position = HoldPosition { row, col };
                    let hold = Hold { position, image: None };
                    holds.insert(position, hold);
                }
            }
            Self { rows, cols, holds }
        }
    }
    impl Default for Wall {
        fn default() -> Self {
            Self::new(12, 12)
        }
    }

    #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
    pub struct HoldPosition {
        /// Starting from 0
        pub row: u64,

        /// Starting from 0
        pub col: u64,
    }

    #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
    pub struct Problem {
        pub holds: BTreeMap<HoldPosition, HoldRole>,
    }

    /// The role of a hold on a route
    #[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
    pub enum HoldRole {
        /// Start hold
        Start,

        /// Any hold on the route without a specific role
        Normal,

        /// Zone hold
        Zone,

        /// End hold
        End,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct Hold {
        pub position: HoldPosition,
        pub image: Option<Image>,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct Image {
        pub filename: String,
    }
}
