use diesel::prelude::*;
use diesel::SqliteConnection;
use std::cmp::max;
use std::str::FromStr;
use uuid::Uuid;

pub static START_CHAR_CODE: u8 = 32;
pub static END_CHAR_CODE: u8 = 126;
pub static FIRST_RANK: char = '/';

pub fn get_previous_rank(next_rank: String) -> String {
    let count = next_rank.chars().count() - 1;

    for (i, char) in next_rank.chars().rev().into_iter().enumerate() {
        let char_code = char as u8;

        if char_code > START_CHAR_CODE + 1 {
            return format!("{}{}", next_rank[..count - i].to_string(), (char_code - 1) as char);
        }
    }

    format!(
        "{}{}{}",
        next_rank[..count - 1].to_string(),
        START_CHAR_CODE as char,
        END_CHAR_CODE as char
    )
}

pub fn get_next_rank(previous_rank: String) -> String {
    let count = previous_rank.chars().count() - 1;

    for (i, char) in previous_rank.chars().rev().into_iter().enumerate() {
        let char_code = char as u8;

        if char_code < END_CHAR_CODE {
            return format!("{}{}", previous_rank[..count - i].to_string(), (char_code + 1) as char);
        }
    }

    format!("{}{}", previous_rank, (START_CHAR_CODE + 1) as char)
}

pub fn get_average(first: u8, second: u8) -> u8 {
    (first + second) / 2
}

pub fn get_middle_rank(first: String, second: String) -> String {
    let mut flag = false;
    let mut rank = "".to_string();

    let first_count = first.chars().count();
    let second_count = second.chars().count();

    let max_length = max(first_count, second_count);

    for i in 0..max_length {
        let lower = if i < first_count {
            first.chars().nth(i).unwrap() as u8
        } else {
            START_CHAR_CODE
        };

        let upper = if i < second_count && !flag {
            second.chars().nth(i).unwrap() as u8
        } else {
            END_CHAR_CODE
        };

        if lower == upper {
            rank.push(lower as char);
        } else if upper - lower > 1 {
            rank.push(get_average(lower, upper) as char);
            flag = false;
            break;
        } else {
            rank.push(lower as char);
            flag = true;
        }
    }

    if flag {
        rank.push(get_average(START_CHAR_CODE, END_CHAR_CODE) as char)
    }

    rank
}

pub trait SaveFileRanking {
    fn generate_ranking(&self, previous: Option<String>, next: Option<String>) -> String;
}

impl SaveFileRanking for SqliteConnection {
    fn generate_ranking(&self, previous: Option<String>, next: Option<String>) -> String {
        use crate::schema::blocks::dsl::*;

        return if previous.is_none() && next.is_none() {
            // first rank
            FIRST_RANK.to_string()
        } else if previous.is_none() {
            // rank at the start
            let next_id = next.unwrap();
            let next_uuid = Uuid::from_str(next_id.as_str()).unwrap().as_bytes().to_vec();

            let next_rank = blocks
                .select(rank)
                .filter(id.eq(next_uuid))
                .first(self)
                .expect(format!("Could not find the block with the id {}", next_id).as_str());

            get_previous_rank(next_rank)
        } else if next.is_none() {
            // rank at the end
            let previous_id = previous.unwrap();
            let previous_uuid = Uuid::from_str(previous_id.as_str()).unwrap().as_bytes().to_vec();

            let previous_rank = blocks
                .select(rank)
                .filter(id.eq(previous_uuid))
                .first(self)
                .expect(format!("Could not find the block with the id {}", previous_id).as_str());

            get_next_rank(previous_rank)
        } else {
            // rank between two ranks
            let previous_id = previous.unwrap();
            let previous_uuid = Uuid::from_str(previous_id.as_str()).unwrap().as_bytes().to_vec();
            let next_id = next.unwrap();
            let next_uuid = Uuid::from_str(next_id.as_str()).unwrap().as_bytes().to_vec();

            let previous_rank = blocks
                .select(rank)
                .filter(id.eq(previous_uuid))
                .first(self)
                .expect(format!("Could not find the block with the id {}", previous_id).as_str());
            let next_rank = blocks
                .select(rank)
                .filter(id.eq(next_uuid))
                .first(self)
                .expect(format!("Could not find the block with the id {}", next_id).as_str());

            get_middle_rank(previous_rank, next_rank)
        };
    }
}
