use self::super::super::super::{NewBoardRequestForm, OldBoardRequestForm, BoardDifficulty, SudokuSolution, BoardMessage, SudokuBoard, Session, User};
use self::super::super::super::super::util::{random_username, board_includes};
use self::super::super::super::errors::{GenericErrorSeverity, GenericError};
use self::super::super::super::setup::{DatabaseConnection, ActivityCache};
use rocket::response::status::Custom;
use rocket::http::{Cookies, Status};
use rocket::request::{State, Form};
use chrono::{DateTime, Utc};
use rocket_contrib::Json;
use std::borrow::Cow;
use sudoku::Sudoku;
#[get("/new?<difficulty>")]
pub fn new(db: DatabaseConnection, ac: State<ActivityCache>, mut cookies: Cookies, difficulty: NewBoardRequestForm)
           -> Result<Custom<Json<BoardMessage>>, Custom<Json<GenericError>>> {
    let mut session = Session::get(&db, &mut cookies).map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?;
    ac.register_activity(session.id.unwrap() as usize);
    let difficulty = difficulty.difficulty
        .map_err(|e| {
            Custom(Status::BadRequest,
                   Json((e.map(Cow::Owned).unwrap_or_else(|| "specified difficulty out of domain".into()), GenericErrorSeverity::Warning).into()))
        })?;
    let mut board = SudokuBoard::new(difficulty);
    board.insert(&db).map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?;
    let skeleton = board.generate_skeleton().map_err(|e| Custom(Status::InternalServerError, Json((e.to_string(), GenericErrorSeverity::Danger).into())))?;
    session.start_game(board.id
                        .ok_or_else(|| {
                            Custom(Status::InternalServerError,
                                   Json(("missing roundtripped ID", GenericErrorSeverity::Danger).into()))
                        })?, 
                    skeleton.clone(),
                    &db)
        .map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?;
    Ok(Custom(Status::Created,
              Json(BoardMessage {
                  board_id: board.id.unwrap(), 
                  board_skeleton: skeleton.into(),
                  solved_board: None,
              })))
}
#[get("/replay?<board_id>")]
pub fn replay(db: DatabaseConnection, ac: State<ActivityCache>, mut cookies: Cookies, board_id: OldBoardRequestForm)
              -> Result<Custom<Json<BoardMessage>>, Custom<Json<GenericError>>> {
    let mut session = Session::get(&db, &mut cookies).map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?;
    ac.register_activity(session.id.unwrap() as usize);
    let board_id = board_id.board_id
        .map_err(|e| {
            Custom(Status::BadRequest,
                   Json((e.map(Cow::Owned).unwrap_or_else(|| "specified board_id nonpositive".into()), GenericErrorSeverity::Warning).into()))
        })?
        .0;
    let board = SudokuBoard::get(board_id, &db).map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?;
    let skeleton = board.generate_skeleton().map_err(|e| Custom(Status::InternalServerError, Json((e.to_string(), GenericErrorSeverity::Danger).into())))?;
    session.start_game(board.id.unwrap(), skeleton.clone(), &db) 
        .map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?;
    Ok(Custom(Status::Created,
              Json(BoardMessage {
                  board_id: board.id.unwrap(), 
                  board_skeleton: skeleton.into(),
                  solved_board: None,
              })))
}
#[post("/submit", data="<submitted_board>")]
pub fn submit(db: DatabaseConnection, ac: State<ActivityCache>, mut cookies: Cookies, submitted_board: Form<BoardMessage>)
              -> Result<Json<SudokuSolution>, Custom<Json<GenericError>>> {
    let mut session = Session::get(&db, &mut cookies).map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?;
    ac.register_activity(session.id.unwrap() as usize);
    let submitted_board = submitted_board.into_inner();
    if submitted_board.solved_board.is_none() {
        return Err(Custom(Status::BadRequest,
                          Json(("solved_board field missing in submission form", GenericErrorSeverity::Warning).into())));
    }
    if !session.game_started() {
        return Err(Custom(Status::PreconditionFailed, Json(("no game in progress", GenericErrorSeverity::Warning).into())));
    }
    if !(session.board_skeleton.as_ref().unwrap() == &submitted_board.board_skeleton &&
         board_includes(submitted_board.solved_board.as_ref().unwrap(), &submitted_board.board_skeleton) &&
         Sudoku::from_str_line(submitted_board.solved_board.as_ref().unwrap())
        .map_err(|e| Custom(Status::InternalServerError, Json((e.to_string(), GenericErrorSeverity::Danger).into())))?
        .is_solved()) {
        return Err(Custom(Status::PreconditionFailed, Json(("solution invalid", GenericErrorSeverity::Warning).into())));
    }
    let mut user = if let Some(uid) = session.user_id {
        Some(User::get_by_id(uid, &db).map_err(|e| Custom(Status::InternalServerError, Json((e.to_string(), GenericErrorSeverity::Danger).into())))?)
    } else {
        None
    };
    let mut solution = SudokuSolution::new(user.as_ref().map(|u| u.username.clone()).unwrap_or_else(random_username),
                                           submitted_board.board_skeleton,
                                           &SudokuBoard::get(submitted_board.board_id, &db)
                                               .map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?,
                                           &(Utc::now() - DateTime::from_utc(session.solve_start.unwrap(), Utc)))
        .ok_or_else(|| Custom(Status::InternalServerError, Json(("could not create solution", GenericErrorSeverity::Danger).into())))?;
    if let Some(ref mut user) = user {
        user.solve(solution.score as usize,
                   BoardDifficulty::from_numeric(solution.difficulty as u64).ok_or_else(|| {
                           Custom(Status::InternalServerError,
                                  Json(("invalid board difficulty", GenericErrorSeverity::Danger).into()))
                       })?,
                   &db)
            .map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?;
    }
    solution.insert(&db).map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?;
    session.stop_game(&db).map_err(|e| Custom(Status::InternalServerError, Json((e, GenericErrorSeverity::Danger).into())))?;
    Ok(Json(solution))
}