/*  medal                                                                                                            *\
 *  Copyright (C) 2022  Bundesweite Informatikwettbewerbe, Robert Czechowski                                                            *
 *                                                                                                                   *
 *  This program 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.                                                                                  *
 *                                                                                                                   *
 *  This program 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   *
\*  <http://www.gnu.org/licenses/>.                                                                                  */

extern crate time;

use self::time::{Duration, Timespec};

#[derive(Clone, Debug)]
pub struct SessionUser {
    pub id: i32,
    pub session_token: Option<String>, // delete this to log out
    pub csrf_token: String,
    pub last_login: Option<Timespec>,
    pub last_activity: Option<Timespec>,
    pub account_created: Option<Timespec>,

    pub username: Option<String>,
    pub password: Option<String>,
    pub salt: Option<String>,
    pub logincode: Option<String>,
    pub email: Option<String>,
    pub email_unconfirmed: Option<String>,
    pub email_confirmationcode: Option<String>,

    pub firstname: Option<String>,
    pub lastname: Option<String>,
    pub street: Option<String>,
    pub zip: Option<String>,
    pub city: Option<String>,
    pub nation: Option<String>,
    pub grade: i32,
    pub sex: Option<i32>,
    pub anonymous: bool,

    pub is_admin: Option<bool>,
    pub is_teacher: bool,
    pub managed_by: Option<i32>,
    pub school_name: Option<String>,

    pub oauth_foreign_id: Option<String>,
    pub oauth_provider: Option<String>,
}

pub enum Sex {
    #[allow(dead_code)]
    NotStated = 0,
    Male      = 1,
    Female    = 2,
    Diverse   = 3,
    #[allow(dead_code)]
    Other     = 4,
}

// Short version for display
#[derive(Clone, Default)]
pub struct UserInfo {
    pub id: i32,
    pub username: Option<String>,
    pub logincode: Option<String>,
    pub firstname: Option<String>,
    pub lastname: Option<String>,
    pub grade: i32,
    pub annotation: Option<String>,
}

#[derive(Clone, Debug)]
pub struct Group {
    pub id: Option<i32>,
    pub name: String,
    pub groupcode: String,
    pub tag: String,
    pub admins: Vec<i32>,
    pub members: Vec<SessionUser>,
}

#[derive(Debug)]
pub struct Contest {
    pub id: Option<i32>,
    pub location: String,
    pub filename: String,
    pub name: String,
    pub duration: i32,
    pub public: bool,
    pub start: Option<Timespec>,
    pub end: Option<Timespec>,
    pub review_start: Option<Timespec>,
    pub review_end: Option<Timespec>,
    pub min_grade: Option<i32>,
    pub max_grade: Option<i32>,
    pub max_teamsize: Option<i32>,
    pub positionalnumber: Option<i32>,
    pub requires_login: Option<bool>,
    pub requires_contest: Option<String>,
    pub protected: bool,
    pub secret: Option<String>,
    pub message: Option<String>,
    pub image: Option<String>,
    pub language: Option<String>,
    pub category: Option<String>,
    pub standalone_task: Option<bool>,
    pub tags: Vec<String>,
    pub taskgroups: Vec<Taskgroup>,
}

#[derive(Debug)]
pub struct Taskgroup {
    pub id: Option<i32>,
    pub contest: i32,
    pub name: String,
    pub active: bool,
    pub positionalnumber: Option<i32>,
    pub tasks: Vec<Task>,
}

#[derive(Debug)]
pub struct Task {
    pub id: Option<i32>,
    pub taskgroup: i32,
    pub location: String,
    pub language: Option<String>,
    pub stars: i32,
}

pub struct Submission {
    pub id: Option<i32>,
    pub user: i32,
    pub task: i32,
    pub grade: i32,
    pub validated: bool,
    pub nonvalidated_grade: i32,
    pub needs_validation: bool,
    pub subtask_identifier: Option<String>,
    pub value: String,
    pub date: Timespec,
}

#[derive(Clone, Copy, Default, Debug)]
pub struct Grade {
    pub taskgroup: i32,
    pub user: i32,
    pub grade: Option<i32>,
    pub validated: bool,
}

pub struct Participation {
    pub contest: i32,
    pub user: i32,
    pub start: Timespec,
    pub team: Option<i32>,
}

pub trait HasId {
    fn get_id(&self) -> Option<i32>;
    fn set_id(&mut self, id: i32);
}
impl HasId for Submission {
    fn get_id(&self) -> Option<i32> { self.id }
    fn set_id(&mut self, id: i32) { self.id = Some(id); }
}
impl HasId for Task {
    fn get_id(&self) -> Option<i32> { self.id }
    fn set_id(&mut self, id: i32) { self.id = Some(id); }
}
impl HasId for Taskgroup {
    fn get_id(&self) -> Option<i32> { self.id }
    fn set_id(&mut self, id: i32) { self.id = Some(id); }
}
impl HasId for Contest {
    fn get_id(&self) -> Option<i32> { self.id }
    fn set_id(&mut self, id: i32) { self.id = Some(id); }
}
impl HasId for Group {
    fn get_id(&self) -> Option<i32> { self.id }
    fn set_id(&mut self, id: i32) { self.id = Some(id); }
}

impl SessionUser {
    pub fn minimal(id: i32, session_token: String, csrf_token: String) -> Self {
        SessionUser { id,
                      session_token: Some(session_token),
                      csrf_token,
                      last_login: None,
                      last_activity: None,
                      account_created: Some(time::get_time()),
                      // müssen die überhaupt außerhalb der datenbankabstraktion sichtbar sein?
                      username: None,
                      password: None,
                      salt: None,
                      logincode: None,
                      email: None,
                      email_unconfirmed: None,
                      email_confirmationcode: None,

                      firstname: None,
                      lastname: None,
                      street: None,
                      zip: None,
                      city: None,
                      nation: None,
                      grade: 0,
                      sex: None,
                      anonymous: false,

                      is_admin: Some(false),
                      is_teacher: false,
                      managed_by: None,
                      school_name: None,

                      oauth_foreign_id: None,
                      oauth_provider: None }
    }

    pub fn group_user_stub() -> Self {
        SessionUser { id: 0,
                      session_token: None,
                      csrf_token: "".to_string(),
                      last_login: None,
                      last_activity: None,
                      account_created: Some(time::get_time()),

                      username: None,
                      password: None,
                      salt: None,
                      logincode: None,
                      email: None,
                      email_unconfirmed: None,
                      email_confirmationcode: None,

                      firstname: None,
                      lastname: None,
                      street: None,
                      zip: None,
                      city: None,
                      nation: None,
                      grade: 0,
                      sex: None,
                      anonymous: false,

                      is_admin: None,
                      is_teacher: false,
                      managed_by: None,
                      school_name: None,

                      oauth_foreign_id: None,
                      oauth_provider: None }
    }

    pub fn is_alive(&self) -> bool {
        let duration = Duration::hours(9); // TODO: hardcoded value, should be moved into constant or sth
        let now = time::get_time();
        if let Some(last_activity) = self.last_activity {
            now - last_activity < duration
        } else {
            false
        }
    }

    pub fn is_logged_in(&self) -> bool {
        (self.password.is_some() || self.logincode.is_some() || self.oauth_foreign_id.is_some()) && self.is_alive()
    }

    pub fn is_teacher(&self) -> bool { self.is_teacher }

    pub fn is_admin(&self) -> bool { self.is_admin == Some(true) }

    pub fn ensure_alive(self) -> Option<Self> {
        if self.is_alive() {
            Some(self)
        } else {
            None
        }
    }

    pub fn ensure_logged_in(self) -> Option<Self> {
        if self.is_logged_in() {
            Some(self)
        } else {
            None
        }
    }

    pub fn ensure_teacher(self) -> Option<Self> {
        if self.is_logged_in() && self.is_teacher() {
            Some(self)
        } else {
            None
        }
    }

    pub fn ensure_admin(self) -> Option<Self> {
        if self.is_logged_in() && self.is_admin() {
            Some(self)
        } else {
            None
        }
    }

    pub fn ensure_teacher_or_admin(self) -> Option<Self> {
        if self.is_logged_in() && (self.is_admin() || self.is_teacher()) {
            Some(self)
        } else {
            None
        }
    }
}

impl Taskgroup {
    pub fn new(name: String, positionalnumber: Option<i32>) -> Self {
        Taskgroup { id: None, contest: 0, name, active: true, positionalnumber, tasks: Vec::new() }
    }
}

impl Task {
    pub fn new(mut location: String, mut language: Option<String>, stars: i32) -> Self {
        if language.is_none() {
            (language, location) = match location.chars().next() {
                Some('B') => (Some("blockly".to_string()), (&location[1..]).to_string()),
                Some('P') => (Some("python".to_string()), (&location[1..]).to_string()),
                _ => (None, location),
            };
        }

        Task { id: None, taskgroup: 0, location, language, stars }
    }
}

pub trait OptionSession {
    fn ensure_alive(self) -> Self;
    fn ensure_logged_in(self) -> Self;
    #[allow(dead_code)]
    fn ensure_teacher(self) -> Self;
    #[allow(dead_code)]
    fn ensure_admin(self) -> Self;
}

impl OptionSession for Option<SessionUser> {
    fn ensure_alive(self) -> Self { self?.ensure_alive() }
    fn ensure_logged_in(self) -> Self { self?.ensure_logged_in() }
    fn ensure_teacher(self) -> Self { self?.ensure_teacher() }
    fn ensure_admin(self) -> Self { self?.ensure_admin() }
}