//! # Base Request Handler
//!
//! Base handling code for all user requests


use std::collections::HashSet;
use std::error::Error;
use std::str::from_utf8;

use exif::In;
use image::{GenericImageView, ImageFormat};
use serde::Serialize;

use crate::api_data::http_error::HttpError;
use crate::constants::admin::AdminRole;
use crate::constants::PASSWORD_MIN_LENGTH;
use crate::data::admin::AdminID;
use crate::data::comment::Comment;
use crate::data::config::conf;
use crate::data::conversation::{ConversationMember, ConvID};
use crate::data::custom_emoji::CustomEmoji;
use crate::data::error::{ExecError, Res, ResultBoxError};
use crate::data::group::GroupAccessLevel;
use crate::data::group_id::GroupID;
use crate::data::post::{Post, PostAccessLevel};
use crate::data::user::UserID;
use crate::data::user_token::UserAccessToken;
use crate::helpers::{account_helper, admin_account_helper, admin_roles_helper, comments_helper, conversations_helper, custom_emojies_helper, friends_helper, groups_helper, posts_helper, user_helper, virtual_directory_helper};
use crate::helpers::virtual_directory_helper::VirtualDirType;
use crate::routes::RequestResult;
use crate::utils::mp3_utils::is_valid_mp3;
use crate::utils::mp4_utils::is_valid_mp4;
use crate::utils::pdf_utils::is_valid_pdf;
use crate::utils::string_utils::{check_emoji_code, check_html_color, check_url, remove_html_nodes};
use crate::utils::user_data_utils::{generate_new_user_data_file_name, prepare_file_creation, user_data_path};
use crate::utils::virtual_directories_utils;
use crate::utils::zip_utils::is_valid_zip;

#[derive(Serialize)]
struct SuccessMessage {
    success: String
}


pub struct PostFile {
    pub name: String,
    pub buff: actix_web::web::Bytes,
}

/// Single request body  value
pub enum RequestValue {
    String(String),
    File(PostFile),
}


pub trait BaseRequestHandler {
    /// Get a parameter of the request
    fn post_parameter_opt(&self, name: &str) -> Option<&RequestValue>;

    /// Set response
    fn set_response<T: Serialize>(&mut self, data: T) -> RequestResult;

    /// Set an error
    fn set_error(&mut self, error: HttpError);

    /// Get remote IP address
    fn remote_ip(&self) -> String;

    /// Current user access token
    fn user_access_token(&self) -> Option<&UserAccessToken>;

    /// Current user ID
    fn user_id_opt_ref(&self) -> Option<&UserID> {
        self.user_access_token().map(|u| &u.user_id)
    }

    /// Get an admin ID, if available
    fn admin_id_opt(&self) -> Option<AdminID>;

    /// Success message
    fn success(&mut self, message: &str) -> RequestResult {
        self.set_response(SuccessMessage {
            success: message.to_string()
        })
    }

    /// Success without message
    fn ok(&mut self) -> RequestResult {
        self.set_response(SuccessMessage {
            success: "OK.".to_string()
        })
    }

    /// Internal error response (500)
    fn internal_error(&mut self, error: Box<dyn Error>) -> RequestResult {
        self.set_error(HttpError::internal_error("Internal server error."));
        Err(error)
    }

    /// Bad request (400)
    fn bad_request(&mut self, message: String) -> RequestResult {
        self.set_error(HttpError::bad_request(&message));
        Err(Box::new(ExecError::new(&message)))
    }

    /// Forbidden (401)
    ///
    /// I did not the HTTP official specs when I made this choice of using Unauthorized instead
    /// of Forbidden. Today it would be really complicated to come back...
    fn forbidden(&mut self, message: String) -> RequestResult {
        self.set_error(HttpError::forbidden(&message));
        Err(Box::new(ExecError::new(&message)))
    }

    /// Not found (404)
    fn not_found(&mut self, message: String) -> RequestResult {
        self.set_error(HttpError::not_found(&message));
        Err(Box::new(ExecError::new(&message)))
    }

    /// Conflict (409)
    fn conflict(&mut self, message: String) -> RequestResult {
        self.set_error(HttpError::new(409, &message));
        Err(Box::new(ExecError::new(&message)))
    }

    /// Too many requests (429)
    fn too_many_requests(&mut self, message: &str) -> RequestResult {
        self.set_error(HttpError::new(429, message));
        Err(Box::new(ExecError::new(message)))
    }

    /// If result is not OK, return a bad request
    fn ok_or_bad_request<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
        match res {
            Ok(e) => Ok(e),
            Err(err) => {
                println!("Error leading to bad request: {}", err);
                self.bad_request(msg.to_string())?;
                unreachable!()
            }
        }
    }

    /// If result is not OK, return a bad request
    fn ok_or_forbidden<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
        match res {
            Ok(e) => Ok(e),
            Err(err) => {
                println!("Error leading to access forbidden: {}", err);
                self.forbidden(msg.to_string())?;
                unreachable!()
            }
        }
    }

    /// If result is not OK, return a 404 not found error
    fn ok_or_not_found<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
        match res {
            Ok(e) => Ok(e),
            Err(err) => {
                println!("Error leading to 404 not found: {}", err);
                self.not_found(msg.to_string())?;
                unreachable!()
            }
        }
    }

    /// Unwrap an option, returning an error if none is returned
    fn some_or_internal_error<E>(&mut self, opt: Option<E>, msg: &str) -> Res<E> {
        match opt {
            None => {
                self.internal_error(ExecError::boxed_new(msg))?;
                unreachable!()
            }
            Some(e) => Ok(e)
        }
    }

    /// Unwrap an option, returning an error if none is returned
    fn some_or_bad_request<E>(&mut self, opt: Option<E>, msg: &str) -> Res<E> {
        match opt {
            None => {
                self.bad_request(msg.to_string())?;
                unreachable!()
            }
            Some(e) => Ok(e)
        }
    }


    /// Get a user ID, if available
    fn user_id_opt(&self) -> Option<UserID> {
        self.user_id_opt_ref().map(|u| u.clone())
    }

    /// Get user ID. This function assess that a user ID is available to continue
    fn user_id(&self) -> ResultBoxError<UserID> {
        if let Some(id) = self.user_id_opt() {
            return Ok(id.clone());
        }

        Err(ExecError::boxed_new("Could not get required user ID!"))
    }

    /// Get current user ID, return invalid user id value if there is none
    fn user_id_or_invalid(&self) -> UserID {
        self.user_id_opt().unwrap_or(UserID::invalid())
    }

    /// Get user ID as a reference
    fn user_id_ref(&self) -> ResultBoxError<&UserID> {
        self.user_id_opt_ref().ok_or(ExecError::boxed_new("Could not get required user ID!"))
    }


    /// Get current admin ID, returning an error in case of error
    fn admin_id(&self) -> Res<AdminID> {
        self.admin_id_opt().ok_or(ExecError::boxed_new("Could not get required admin ID!"))
    }


    /// Check if a POST parameter was present in the request or not
    fn has_post_parameter(&self, name: &str) -> bool {
        self.post_parameter_opt(name).is_some()
    }

    /// Get a post parameter
    fn post_parameter(&mut self, name: &str) -> ResultBoxError<&RequestValue> {
        if !self.has_post_parameter(name) {
            self.bad_request(format!("POST parameter '{}' not found in request!", name))?;
        }

        Ok(self.post_parameter_opt(name).unwrap())
    }

    /// Get a post string
    fn post_string(&mut self, name: &str) -> ResultBoxError<String> {
        self.post_string_opt(name, 1, true)
    }

    /// Get a post string with a given name. If the value is not found, attempt to get the value
    /// with another name
    ///
    /// This function is useful to upgrade system
    fn post_string_with_fallback(&mut self, name: &str, fallback: &str) -> Res<String> {
        if self.has_post_parameter(name) {
            self.post_string(name)
        } else {
            self.post_string(fallback)
        }
    }

    /// Get a post string, specifying minimum length
    fn post_string_opt(&mut self, name: &str, min_length: usize, required: bool)
                       -> ResultBoxError<String> {
        let param = self.post_parameter(name)?;

        match (&param, required) {
            (RequestValue::String(s), _) => {
                if s.len() >= min_length {
                    Ok(s.to_string())
                } else {
                    Err(self.bad_request(format!("'{}' is too short!", name)).unwrap_err())
                }
            }

            (_, false) => Ok(String::new()),

            (_, true) =>
                Err(self.bad_request(format!("'{}' is not a string!", name)).unwrap_err()),
        }
    }

    /// Get a string included in the request. If none found, or if string is empty, returns [None]
    fn post_string_optional(&mut self, name: &str) -> Option<String> {
        match self.post_parameter_opt(name) {
            Some(RequestValue::String(s)) => {
                if s.is_empty() {
                    None
                } else { Some(s.to_string()) }
            }
            _ => None
        }
    }

    /// Check out whether a file was included in the request or not
    fn has_file(&self, name: &str) -> bool {
        self.post_parameter_opt(name)
            .map(|f| matches!(f, RequestValue::File(_)))
            .unwrap_or(false)
    }

    /// Get a file included in the request
    fn post_file(&mut self, name: &str) -> ResultBoxError<&PostFile> {
        if self.has_file(name) {
            if let RequestValue::File(f) = self.post_parameter(name)? {
                return Ok(f);
            }
        } else {
            self.bad_request(format!("File {} not included in request!", name))?;
        }

        unreachable!();
    }

    /// Get the mime type of a file included in the request
    fn post_file_type(&mut self, name: &str) -> Res<String> {
        let file = self.post_file(name)?;

        let filetype = mime_guess::from_path(&file.name)
            .first()
            .map(|m| format!("{}/{}", m.type_(), m.subtype()));

        if let None = filetype {
            self.bad_request(format!("Could not determine file type in '{}' !", name))?;
            unreachable!();
        }

        Ok(filetype.unwrap())
    }

    /// Get the extension of a file included in the request
    fn post_file_ext(&mut self, name: &str, default: &str) -> Res<String> {
        let suffix = self.post_file_type(name)?
            .parse::<mime_guess::mime::Mime>()?
            .suffix()
            .map(|s| s.as_str())
            .unwrap_or(default)
            .to_string();

        Ok(suffix)
    }

    /// Save an image in user data directory
    fn save_post_image(&mut self, name: &str, folder: &str, max_w: u32, max_h: u32) -> ResultBoxError<String> {

        // Load image
        let file = self.post_file(name)?;
        let mut image = image::load_from_memory(file.buff.as_ref())?;

        if image.width() > max_w || image.height() > max_h {
            image = image.resize(max_w, max_h, image::imageops::FilterType::Nearest);
        }

        // Read EXIF information in case of JPEG image, if possible
        if let Ok(ImageFormat::Jpeg) = image::guess_format(file.buff.as_ref()) {
            let mut reader = std::io::BufReader::new(file.buff.as_ref());

            if let Ok(exif_attr) = exif::get_exif_attr_from_jpeg(&mut reader) {
                let exif_reader = exif::Reader::new();
                let exif = exif_reader.read_raw(exif_attr)?;

                if let Some(v) = exif.get_field(exif::Tag::Orientation, In::PRIMARY) {
                    match v.value.get_uint(0) {
                        Some(1) => { /* row 0 is top and column 0 is left */ }
                        //Some(2) => println!("row 0 at top and column 0 at right"),
                        Some(3) => {
                            /* row 0 at bottom and column 0 at right */
                            image = image.rotate180()
                        }
                        //Some(4) => println!("row 0 at bottom and column 0 at left"),
                        //Some(5) => println!("row 0 at left and column 0 at top"),
                        Some(6) => {
                            /* row 0 is right and column 0 is top */
                            image = image.rotate90();
                        }
                        //Some(7) => println!("row 0 at right and column 0 at bottom"),
                        Some(8) => {
                            /* row 0 is left and column 0 is bottom */
                            image = image.rotate270();
                        }
                        v => println!("Unhandled EXIF Orientation: {:?}", v),
                    };
                }
            }
        }


        // Determine image destination
        let target_user_data_folder = prepare_file_creation(&self.user_id()?, folder)?;
        let target_file_path = generate_new_user_data_file_name(target_user_data_folder.as_path(), "png")?;
        let target_sys_path = user_data_path(target_file_path.as_path());


        // Save image
        image.save_with_format(target_sys_path, ImageFormat::Png)?;

        Ok(target_file_path.to_string_lossy().to_string())
    }

    /// Save a file included in the request
    fn save_post_file(&mut self, name: &str, folder: &str, ext: &str) -> Res<String> {
        let file = self.post_file(name)?;

        // Avoid memory warnings
        let copied_buff = file.buff.clone();

        // Determine pdf file destination
        let target_user_data_folder = prepare_file_creation(self.user_id_ref()?, folder)?;
        let target_file_path = generate_new_user_data_file_name(target_user_data_folder.as_path(), ext)?;
        let target_sys_path = user_data_path(target_file_path.as_path());

        std::fs::write(target_sys_path, &copied_buff.as_ref())?;

        Ok(target_file_path.to_string_lossy().to_string())
    }

    /// Save a pdf included in the request
    fn save_post_pdf(&mut self, name: &str, folder: &str) -> Res<String> {
        let file = self.post_file(name)?;

        if !is_valid_pdf(&file.buff)? {
            self.bad_request(format!("Invalid PDF specified in {} !", name))?;
            unreachable!();
        }

        self.save_post_file(name, folder, "pdf")
    }

    /// Save a mp3 file included in the request
    fn save_post_mp3(&mut self, name: &str, folder: &str) -> Res<String> {
        let file = self.post_file(name)?;

        if !is_valid_mp3(&file.buff) {
            self.bad_request(format!("Invalid MP3 file specified in {} !", name))?;
            unreachable!();
        }

        self.save_post_file(name, folder, "mp3")
    }

    /// Save a mp4 file included in the request
    fn save_post_mp4(&mut self, name: &str, folder: &str) -> Res<String> {
        let file = self.post_file(name)?;

        if !is_valid_mp4(&file.buff) {
            self.bad_request(format!("Invalid MP4 file specified in {} !", name))?;
            unreachable!();
        }

        self.save_post_file(name, folder, "mp4")
    }

    /// Save a zip file included in the request
    fn save_post_zip(&mut self, name: &str, folder: &str) -> Res<String> {
        let file = self.post_file(name)?;

        if !is_valid_zip(&file.buff) {
            self.bad_request(format!("Invalid ZIP archive file specified in {} !", name))?;
            unreachable!();
        }

        self.save_post_file(name, folder, "zip")
    }

    /// Save an office document included in the request
    fn save_post_office_doc(&mut self, name: &str, folder: &str) -> Res<String> {
        let file = self.post_file(name)?;

        if !is_valid_zip(&file.buff) {
            self.bad_request(format!("Invalid ZIP archive file specified in {} !", name))?;
            unreachable!();
        }

        let mime = self.post_file_type(name)?;

        if !mime.starts_with("application/") {
            self.bad_request(format!("Invalid file name in {} !", name))?;
            unreachable!();
        }

        let ext = &self.post_file_ext(name, "zip")?;
        self.save_post_file(name, folder, ext)
    }

    /// Save a simple text document included in the request
    fn save_post_txt_doc(&mut self, name: &str, folder: &str) -> Res<String> {
        let file = self.post_file(name)?;

        if from_utf8(&file.buff).is_err() {
            self.bad_request(format!("File in {} is not UTF-8!", name))?;
            unreachable!();
        }

        let ext = &self.post_file_ext(name, "txt")?;
        self.save_post_file(name, folder, ext)
    }

    /// Get an integer included in the POST request
    fn post_i64(&mut self, name: &str) -> ResultBoxError<i64> {
        Ok(self.post_string(name)?.parse::<i64>()?)
    }

    /// Get an optional number in the request. If none found, return a default value
    fn post_u64_opt(&mut self, name: &str, default: u64) -> ResultBoxError<u64> {
        if self.has_post_parameter(name) {
            Ok(self.post_string(name)?.parse::<u64>()?)
        } else {
            Ok(default)
        }
    }

    fn post_u64(&mut self, name: &str) -> ResultBoxError<u64> {
        Ok(self.post_string(name)?.parse::<u64>()?)
    }

    fn post_u32(&mut self, name: &str) -> Res<u32> {
        Ok(self.post_u64(name)? as u32)
    }

    fn post_u16(&mut self, name: &str) -> Res<u16> {
        Ok(self.post_u64(name)? as u16)
    }

    fn post_positive_u64_opt(&mut self, name: &str) -> Res<Option<u64>> {
        match self.post_u64_opt(name, 0)? {
            0 => Ok(None),
            val => Ok(Some(val))
        }
    }

    /// Get a boolean included in a POST request
    fn post_bool(&mut self, name: &str) -> ResultBoxError<bool> {
        Ok(self.post_string(name)?.eq("true"))
    }

    /// Get an optional boolean included in post request
    fn post_bool_opt(&mut self, name: &str, default: bool) -> bool {
        self.post_bool(name).unwrap_or(default)
    }


    /// Get an email included in the request
    fn post_email(&mut self, name: &str) -> ResultBoxError<String> {
        let mail = self.post_string(name)?;

        if !mailchecker::is_valid(&mail) {
            self.bad_request("Invalid email address!".to_string())?;
        }

        Ok(mail)
    }

    /// Get a list of integers included in the request
    fn post_numbers_list(&mut self, name: &str, min_len: usize) -> ResultBoxError<Vec<u64>> {
        let param = self.post_string_opt(name, min_len, min_len != 0)?;
        let mut list = vec![];

        for split in param.split::<&str>(",") {
            if split.is_empty() {
                continue;
            }

            list.push(split.parse::<u64>()?);
        }

        if list.len() < min_len {
            self.bad_request(format!("Not enough entries in '{}'!", name))?;
        }

        Ok(list)
    }

    /// Get the response to a key register credential included in the request
    fn post_register_public_key_credential(&mut self, name: &str) -> Res<webauthn_rs::proto::RegisterPublicKeyCredential> {
        let str = self.post_string(name)?;

        Ok(serde_json::from_str(&str)?)
    }

    /// Get the response to a key authentication included in the request
    fn post_auth_public_key_credential(&mut self, name: &str) -> Res<webauthn_rs::proto::PublicKeyCredential> {
        let str = self.post_string(name)?;

        Ok(serde_json::from_str(&str)?)
    }

    /// Get the ID of a user included in a POST request
    fn post_user_id(&mut self, name: &str) -> ResultBoxError<UserID> {
        let user_id = UserID::new(self.post_u64(name)?);

        if user_id.id() < 1 {
            self.bad_request(format!("Invalid user specified in '{}'!", name))?;
        }

        if !user_helper::exists(&user_id)? {
            self.not_found(format!("User with ID {} not found!", user_id.id()))?;
        }

        Ok(user_id)
    }

    /// Get the ID of an admin included in a POST request
    fn post_admin_id(&mut self, name: &str) -> Res<AdminID> {
        let admin_id = AdminID::new(self.post_u64(name)?);

        if admin_id.id() < 1 {
            self.bad_request(format!("Invalid admin specified in '{}'!", name))?;
        }

        if !admin_account_helper::exists(admin_id)? {
            self.not_found(format!("Admin with ID {} not found!", admin_id.id()))?;
        }

        Ok(admin_id)
    }

    /// Check if an admin has a given role or not
    fn check_admin_has_role(&mut self, role: AdminRole) -> Res {
        if !admin_roles_helper::has_role(self.admin_id()?, role)? {
            self.forbidden("You have not the permission to do this!".to_string())?;
        }

        Ok(())
    }

    /// Get a list of users ID included in the request
    fn post_users_id(&mut self, name: &str) -> ResultBoxError<HashSet<UserID>> {
        let users = self.post_numbers_list(name, 1)?
            .iter()
            .map(|u| UserID::new(u.clone()))
            .collect::<HashSet<UserID>>();

        for user in &users {
            if !user_helper::exists(user)? {
                self.not_found(format!("User {} not found!", user.id()))?;
            }
        }

        Ok(users)
    }

    /// Get the ID of a friend included in a POST request
    ///
    /// *Note :* This function does not check whether the user exists or not before checking if the
    /// two users are friend because as it is not possible to be friend with a non existent person
    /// A single check is enough
    fn post_friend_id(&mut self, name: &str) -> ResultBoxError<UserID> {
        let friend_id = UserID::new(self.post_u64(name)?);

        if !friends_helper::are_friend(&friend_id, self.user_id_ref()?)? {
            self.forbidden("You are not friend with this person!".to_string())?;
        }

        Ok(friend_id)
    }

    /// Get a virtual directory included in a POST request
    fn post_virtual_directory(&mut self, name: &str) -> ResultBoxError<String> {
        let dir = self.post_string(name)?;

        if !virtual_directories_utils::check_virtual_directory(&dir) {
            self.bad_request(format!("Invalid virtual directory specified in '{}' !", name))?;
        }

        Ok(dir)
    }

    /// Get a string included in the request, with HTML codes removed
    fn post_string_without_html(&mut self, name: &str, min_length: usize, required: bool) -> ResultBoxError<String> {
        Ok(remove_html_nodes(self.post_string_opt(name, min_length, required)?.as_str()))
    }

    /// Get an optional string included in the request, with HTML codes removed
    fn post_string_without_html_opt(&mut self, name: &str, min_length: usize) -> ResultBoxError<Option<String>> {
        if !self.has_post_parameter(name) {
            Ok(None)
        } else {
            Ok(Some(remove_html_nodes(self.post_string_opt(name, min_length, true)?.as_str())))
        }
    }

    /// Get & return the ID of the conversation included in the POST request
    fn post_conv(&mut self, name: &str) -> ResultBoxError<ConversationMember> {
        let conv_id = ConvID::new(self.post_u64(name)?);
        let membership = self.ok_or_forbidden(
            conversations_helper::get_user_membership(&self.user_id()?, conv_id),
            &format!("You do not belong to conversation {} !", conv_id.id()),
        )?;

        Ok(membership)
    }

    /// Get information about a conversation included in the request. The user must be an admin
    /// of the target conversation
    fn post_conv_admin(&mut self, name: &str) -> ResultBoxError<ConversationMember> {
        let conv = self.post_conv(name)?;

        if !conv.is_admin {
            self.forbidden("You are not an administrator of this conversation!".to_string())?;
        }

        Ok(conv)
    }

    /// Get the ID
    fn post_group_id(&mut self, name: &str) -> ResultBoxError<GroupID> {
        let group_id = GroupID::new(self.post_u64(name)?);

        if !groups_helper::exists(&group_id)? {
            self.not_found("Specified group not found!".to_string())?;
        }

        Ok(group_id)
    }

    /// Get the ID of a group included in a request with a check for access level of current user
    fn post_group_id_with_access(&mut self, name: &str, min_level: GroupAccessLevel) -> ResultBoxError<GroupID> {
        let group_id = self.post_group_id(name)?;
        let access_level = groups_helper::get_access_level(&group_id, self.user_id_opt())?;

        if access_level == GroupAccessLevel::NO_ACCESS {
            self.not_found("Specified group not found!".to_string())?;
        }

        if access_level < min_level {
            self.forbidden("You do not have enough rights to perform what you intend to do on this group!".to_string())?;
        }

        Ok(group_id)
    }

    /// Get an URL included in the request
    fn post_url_opt(&mut self, name: &str, required: bool) -> ResultBoxError<Option<String>> {
        let url = self.post_string_opt(name, 0, required)?;

        if url.is_empty() && !required {
            Ok(None)
        } else {
            if !check_url(&url) {
                self.bad_request(format!("Invalid url specified in {} !", name))?;
            }

            Ok(Some(url))
        }
    }

    /// Get an optional virtual directory included in the request
    fn post_checked_virtual_directory_opt(&mut self, name: &str, target_id: u64, target_type: VirtualDirType) -> ResultBoxError<Option<String>> {
        let dir = self.post_string_opt(name, 0, false)?;

        if dir.is_empty() {
            return Ok(None);
        }

        if !virtual_directory_helper::check_availability(&dir, target_id, target_type)? {
            self.forbidden("Requested virtual directory is not available!".to_string())?;
        }

        Ok(Some(dir))
    }

    /// Get information about a post whose ID was specified in the request
    fn post_post_with_access(&mut self, name: &str, min_level: PostAccessLevel) -> ResultBoxError<Post> {
        let post_id = self.post_u64(name)?;
        let post = self.ok_or_not_found(
            posts_helper::get_single(post_id),
            "Requested post not found!",
        )?;

        if posts_helper::get_access_level(&post, &self.user_id_opt())? < min_level {
            self.forbidden("You are not allowed to access this post information!".to_string())?;
        }

        Ok(post)
    }

    /// Get information about a comment whose ID is specified in the request
    fn post_comment_with_access(&mut self, name: &str) -> ResultBoxError<Comment> {
        let comment_id = self.post_u64(name)?;
        let comment = self.ok_or_not_found(
            comments_helper::get_single(comment_id),
            "Specified comment not found!",
        )?;

        if comment.user_id != self.user_id_or_invalid() {
            let post = posts_helper::get_single(comment.post_id)?;
            if posts_helper::get_access_level(&post, &self.user_id_opt())? == PostAccessLevel::NO_ACCESS {
                self.forbidden("You are not allowed to access this post information !".to_string())?;
            }
        }

        Ok(comment)
    }

    /// Get information about a comment specified in the request for which user has full access
    fn post_comment_with_full_access(&mut self, name: &str) -> ResultBoxError<Comment> {
        let comment = self.post_comment_with_access(name)?;

        if comment.user_id != self.user_id()? {
            self.forbidden("You are not the owner of this comment!".to_string())?;
        }

        Ok(comment)
    }

    /// Get a content of a post and sanitize it
    fn post_content(&mut self, name: &str, min_len: usize, required: bool) -> ResultBoxError<String> {
        let content = self.post_string_opt(name, min_len, required)?;

        if content.contains("data:image") {
            self.forbidden("Please do not include inline images!".to_string())?;
        }

        if min_len > 0 && required && content.trim().len() < min_len {
            self.forbidden(format!("The content inside {} was rejected!", name))?;
        }

        Ok(remove_html_nodes(&content))
    }

    /// Check the password of the current user
    fn need_user_password(&mut self, field: &str) -> ResultBoxError {
        let password = self.post_string_opt(field, PASSWORD_MIN_LENGTH, true)?;

        if !account_helper::check_user_password(self.user_id_ref()?, &password)? {
            self.forbidden("Invalid password!".to_string())?;
        }

        Ok(())
    }

    /// Get an emoji shortcut included in a POST request
    fn post_emoji_shortcut(&mut self, field: &str) -> ResultBoxError<String> {
        let emoji_shortcut = self.post_string(field)?;

        if !check_emoji_code(&emoji_shortcut) {
            self.bad_request("Invalid emoji shortcut code!".to_string())?;
        }

        Ok(emoji_shortcut)
    }

    /// Get information about an emoji included in a POST request
    fn post_emoji_id(&mut self, field: &str) -> ResultBoxError<CustomEmoji> {
        let emoji_id = self.post_u64(field)?;

        let info = self.ok_or_not_found(
            custom_emojies_helper::get_single(emoji_id),
            "Requested emoji not found!",
        )?;

        if &info.user_id != self.user_id_ref()? {
            self.forbidden("You do not own this emoji!".to_string())?;
        }

        Ok(info)
    }

    /// Get a color included in the request
    fn post_color_opt(&mut self, field: &str) -> Res<Option<String>> {
        let color = self.post_string_optional(field);

        match color {
            None => Ok(None),
            Some(color) => {
                if !check_html_color(&color) {
                    self.bad_request(format!("Invalid color specified in '{}' !", field))?;
                }

                Ok(Some(color))
            }
        }
    }

    /// Get a membership to a Forez group
    fn post_forez_group(&mut self, name: &str) -> Res<GroupID> {
        let group = self.post_group_id_with_access(name, GroupAccessLevel::MEMBER_ACCESS)?;

        if !conf().forez_groups.contains(&group) {
            self.bad_request(format!("The group {} is not a Forez group!", group.id()))?;
        }

        Ok(group)
    }
}