Documentation
use std::cmp::Ordering;
use std::str::FromStr;

use ansi_term::Color::{Green, Red, Yellow};
use serde::Deserialize;
use serde_repr::{Deserialize_repr, Serialize_repr};

use DifficultyType::*;

use crate::{Either, LeetUpError};

#[derive(Debug)]
pub struct Problem {
    pub id: usize,
    pub slug: String,
    pub lang: String,
    pub link: String,
    pub typed_code: Option<String>,
}

#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Debug)]
#[repr(u8)]
pub enum DifficultyType {
    Easy = 1,
    Medium,
    Hard,
}

impl FromStr for DifficultyType {
    type Err = LeetUpError;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        let easy = Easy.to_string();
        let medium = Medium.to_string();
        let hard = Hard.to_string();
        match s {
            x if x == easy => Ok(Easy),
            x if x == medium => Ok(Medium),
            x if x == hard => Ok(Hard),
            _ => Err(LeetUpError::UnexpectedCommand),
        }
    }
}

impl ToString for DifficultyType {
    fn to_string(&self) -> String {
        match self {
            Easy => "Easy".into(),
            Medium => "Medium".into(),
            Hard => "Hard".into(),
        }
    }
}

#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub enum Difficulty {
    Cardinal { level: DifficultyType },
    String(String),
}

impl<'a> From<&'_ Difficulty> for DifficultyType {
    fn from(difficulty: &Difficulty) -> Self {
        match difficulty {
            Difficulty::Cardinal { level } => level.clone(),
            Difficulty::String(s) => DifficultyType::from_str(s).unwrap(),
        }
    }
}

impl ToString for Difficulty {
    fn to_string(&self) -> String {
        let level: DifficultyType = self.into();
        match level {
            Easy => Green.paint(Easy.to_string()).to_string(),
            Medium => Yellow.paint(Medium.to_string()).to_string(),
            Hard => Red.paint(Hard.to_string()).to_string(),
        }
    }
}

pub type ProblemInfoSeq = Vec<Box<dyn ProblemInfo + Send + 'static>>;

pub trait ProblemInfo {
    fn question_id(&self) -> usize;
    fn question_title(&self) -> &str;
    fn difficulty(&self) -> &Difficulty;
    fn is_favorite(&self) -> Option<bool>;
    fn is_paid_only(&self) -> bool;
    fn status(&self) -> Option<&str>;
}

impl PartialEq<Self> for dyn ProblemInfo + '_ + Send {
    fn eq(&self, other: &Self) -> bool {
        self.question_id().eq(&other.question_id())
    }
}

impl Eq for dyn ProblemInfo + '_ + Send {}

impl PartialOrd for dyn ProblemInfo + '_ + Send {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for dyn ProblemInfo + '_ + Send {
    fn cmp(&self, other: &Self) -> Ordering {
        self.question_id().cmp(&other.question_id())
    }
}

impl PartialEq for dyn ProblemInfo {
    fn eq(&self, other: &Self) -> bool {
        self.question_id().eq(&other.question_id())
    }
}

impl Eq for dyn ProblemInfo {}

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

impl Ord for dyn ProblemInfo {
    fn cmp(&self, other: &Self) -> Ordering {
        self.question_id().cmp(&other.question_id())
    }
}

#[derive(Deserialize, Debug)]
pub struct Stat {
    pub question_id: usize,

    #[serde(rename = "question__article__live")]
    pub question_article_live: Option<bool>,

    #[serde(rename = "question__article__slug")]
    pub question_article_slug: Option<String>,

    #[serde(rename = "question__title")]
    pub question_title: String,

    #[serde(rename = "question__title_slug")]
    pub question_title_slug: String,

    #[serde(rename = "question__hide")]
    pub question_hide: bool,

    pub total_acs: usize,
    pub total_submitted: usize,
    pub frontend_question_id: usize,
    pub is_new_question: bool,
}

#[derive(Deserialize, Debug)]
pub struct StatStatusPair {
    pub stat: Stat,
    pub status: Option<String>,
    pub difficulty: Difficulty,
    pub paid_only: bool,
    pub is_favor: bool,
    pub frequency: f64,
    pub progress: f64,
}

#[derive(Deserialize, Debug)]
pub struct TopicTagQuestion {
    pub status: Option<String>,
    pub difficulty: Difficulty,
    pub title: String,

    #[serde(rename = "isPaidOnly")]
    pub is_paid_only: bool,

    #[serde(rename = "titleSlug")]
    pub title_slug: String,

    #[serde(rename = "questionFrontendId")]
    pub question_frontend_id: String,
}

#[derive(Deserialize, Debug)]
pub struct ListResponse {
    pub user_name: String,
    pub num_solved: usize,
    pub num_total: usize,
    pub ac_easy: usize,
    pub ac_medium: usize,
    pub ac_hard: usize,
    pub stat_status_pairs: Vec<StatStatusPair>,
    pub frequency_high: usize,
    pub frequency_mid: usize,
    pub category_slug: String,
}

#[derive(Deserialize, Debug)]
pub struct CodeDefinition {
    pub value: String,
    pub text: String,

    #[serde(rename = "defaultCode")]
    pub default_code: String,
}

#[derive(Deserialize, Debug)]
pub struct SubmissionResponse {
    pub state: Option<String>,
    pub input: Option<Either>,
    pub input_formatted: Option<Either>,
    pub code_output: Option<Either>,
    pub std_output: Option<Either>,
    pub last_test_case: Option<Either>,
    pub correct_answer: Option<bool>,
    pub code_answer: Option<Either>,
    pub expected_output: Option<Either>,
    pub expected_code_output: Option<Either>,
    pub expected_answer: Option<Either>,
    pub expected_code_answer: Option<Either>,
    pub compare_result: Option<String>,
    pub compile_error: Option<String>,
    pub full_compile_error: Option<String>,
    pub lang: String,
    pub memory: Option<u32>,
    pub memory_percentile: Option<f32>,
    pub pretty_lang: String,
    pub run_success: bool,
    pub runtime_percentile: Option<f32>,
    pub expected_status_code: Option<u32>,
    pub status_memory: String,
    pub status_msg: String,
    pub status_runtime: String,
    pub submission_id: String,
    pub total_correct: Option<u32>,
    pub total_testcases: Option<u32>,
}

pub trait ExecutionErrorResponse {
    fn has_compile_error(&self) -> bool;

    fn has_runtime_error(&self) -> bool;

    fn has_error(&self) -> bool;

    fn is_error(&self) -> bool {
        self.has_compile_error() || self.has_runtime_error() || self.has_error()
    }
}

impl ExecutionErrorResponse for SubmissionResponse {
    fn has_compile_error(&self) -> bool {
        self.compile_error.is_some() || self.full_compile_error.is_some()
    }

    fn has_runtime_error(&self) -> bool {
        let tle = "time limit exceeded";
        let msg = self.status_msg.to_lowercase();

        msg.eq(tle) || msg.contains("error")
    }

    fn has_error(&self) -> bool {
        self.total_correct.lt(&self.total_testcases)
    }
}

impl ProblemInfo for StatStatusPair {
    fn question_id(&self) -> usize {
        self.stat.frontend_question_id
    }

    fn question_title(&self) -> &str {
        self.stat.question_title.as_str()
    }

    fn difficulty(&self) -> &Difficulty {
        &self.difficulty
    }

    fn is_favorite(&self) -> Option<bool> {
        Some(self.is_favor)
    }

    fn is_paid_only(&self) -> bool {
        self.paid_only
    }

    fn status(&self) -> Option<&str> {
        self.status.as_ref().map(String::as_ref)
    }
}

impl ProblemInfo for TopicTagQuestion {
    fn question_id(&self) -> usize {
        self.question_frontend_id
            .parse()
            .expect("Expected question_frontend_id")
    }

    fn question_title(&self) -> &str {
        self.title.as_str()
    }

    fn difficulty(&self) -> &Difficulty {
        &self.difficulty
    }

    fn is_favorite(&self) -> Option<bool> {
        None
    }

    fn is_paid_only(&self) -> bool {
        self.is_paid_only
    }

    fn status(&self) -> Option<&str> {
        self.status.as_ref().map(String::as_ref)
    }
}