import { ExtraData } from "../models/common";
import * as utils from "./utils";

/**
 * Handler for getting the current state of the game.
 */
export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise<any> => {
    const result = {
        game_phase: K.ITEM('s32', 2),
        ir_phase: K.ITEM('s32', 0),
        event_phase: K.ITEM('s32', 5),
        netvs_phase: K.ITEM('s32', 0), // 1 to enable
        card_phase: K.ITEM('s32', 6),
        illust_phase: K.ITEM('s32', 2),
        psp_phase: K.ITEM('s32', 5),
        other_phase: K.ITEM('s32', 1),
        jubeat_phase: K.ITEM('s32', 1),
        public_phase: K.ITEM('s32', 3),
        kac_phase: K.ITEM('s32', 2),
        local_matching: K.ITEM('s32', 0),
        n_matching_sec: K.ITEM('s32', 60),
        l_matching_sec: K.ITEM('s32', 60),
        is_check_cpu: K.ITEM('s32', 0),
        week_no: K.ITEM('s32', 0),
        ng_illust: K.ARRAY('s32', Array(10).fill(0)),
        sel_ranking: K.ARRAY('s16', Array(10).fill(-1)),
        up_ranking: K.ARRAY('s16', Array(10).fill(-1)),
    };

    return send.object(result);
};

/**
 * Handler for new profile
 */
export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise<any> => {
    const refid = $(data).str('ref_id');
    if (!refid) return send.deny();

    const name = $(data).str('name');

    send.object(await getProfile(refid, name));
};

/**
 * Handler for existing profile
 */
export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise<any> => {
    const refid = $(data).str('ref_id');
    if (!refid) return send.deny();

    send.object(await getProfile(refid));
};

/**
 * Get/create the profile based on refid and format it
 * @param refid the profile refid
 * @param name if defined, create/update the profile with the given name
 */
export const getProfile = async (refid: string, name?: string) => {
    const profile = await utils.readProfile(refid);
    const rivals = await utils.readRivals(refid);

    if (name && name.length > 0) {
        profile.name = name;
        await utils.writeProfile(refid, profile);
    }

    // Get Score
    const scores = await getScores(refid);
    let clear_medal_sub = Array(GAME_MAX_MUSIC_ID).fill(0);

    let myBest = Array(20).fill(-1);
    const sortedPlayCount = new Map([...scores.playCount.entries()].sort((a, b) => b[1] - a[1]));
    let i = 0;
    for (const value of sortedPlayCount.keys()) {
        if (i >= 20) {
            break;
        }
        myBest[i] = value;
        i++;
    }

    let player: any = {
        base: {
            name: K.ITEM('str', profile.name),
            g_pm_id: K.ITEM('str', profile.friendId),
            staff: K.ITEM('s8', 0),
            is_conv: K.ITEM('s8', -1),
            my_best: K.ARRAY('s16', myBest),
            clear_medal: K.ARRAY('u16', scores.clear_medal),
            clear_medal_sub: K.ARRAY('u8', clear_medal_sub),
            active_fr_num: K.ITEM('u8', rivals.rivals.length),

            // TODO: replace with real data
            total_play_cnt: K.ITEM('s32', 100),
            today_play_cnt: K.ITEM('s16', 50),
            consecutive_days: K.ITEM('s16', 365),
            latest_music: K.ARRAY('s16', [-1, -1, -1]),
        },
        player_card: {
            // TODO: replace with real data
            best_music: K.ARRAY('s16', [-1, -1, -1]),
        },
        netvs: {
            get_ojama: K.ARRAY('s32', [0, 0]),
            rank_point: K.ITEM('s32', 0),
            play_point: K.ITEM('s32', 0),
            record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]),
            rank: K.ITEM('u8', 0),
            dialog: [
                K.ITEM('str', 'dialog#0'),
                K.ITEM('str', 'dialog#1'),
                K.ITEM('str', 'dialog#2'),
                K.ITEM('str', 'dialog#3'),
                K.ITEM('str', 'dialog#4'),
                K.ITEM('str', 'dialog#5'),
            ],
            ojama_condition: K.ARRAY('s8', Array(74).fill(0)),
            set_ojama: K.ARRAY('s8', [0, 0, 0]),
            set_recommend: K.ARRAY('s8', [0, 0, 0]),
            jewelry: K.ARRAY('s8', Array(15).fill(0)),
        },
        hiscore: K.ITEM('bin', Buffer.from(scores.hiscore_array))
    };

    // Add version specific datas
    const params = await utils.readParams(refid, version);
    utils.addExtraData(player, params, extraData);

    player.player_card.best_music = K.ARRAY('s16', myBest.slice(0, 3));

    return player;
}

/**
 * Handler for saving profile ans scores
 */
export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise<any> => {
    const refid = $(data).attr()['ref_id'];
    if (!refid) return send.deny();

    const params = await utils.readParams(refid, version);
    utils.getExtraData(data, params, extraData);
    await utils.writeParams(refid, version, params);

    const scoresData = await utils.readScores(refid, version, true);

    for (const scoreData of $(data).elements('stage')) {
        const music = scoreData.number('no', -1);
        const sheet = {
            2: 0,
            0: 1,
            1: 2,
            3: 3,
        }[scoreData.number('sheet')];
        const clear_type = {
            1: 100,
            2: 200,
            3: 300,
            5: 500,
            6: 600,
            7: 700,
            9: 800,
            10: 900,
            11: 1000,
            15: 1100,
        }[(scoreData.number('n_data') >> (sheet * 4)) & 0x000F];
        const score = scoreData.number('score', 0);

        const key = `${music}:${sheet}`;

        if (!scoresData.scores[key]) {
            scoresData.scores[key] = {
                score,
                cnt: 1,
                clear_type
            };
        } else {
            scoresData.scores[key] = {
                score: Math.max(score, scoresData.scores[key].score),
                cnt: scoresData.scores[key].cnt + 1,
                clear_type: Math.max(clear_type, scoresData.scores[key].clear_type || 0)
            };
        }
    }

    utils.writeScores(refid, version, scoresData);

    const profile = await utils.readProfile(refid);

    const result = {
        pref: K.ITEM('s8', -1),
        name: K.ITEM('str', profile.name)
    }

    send.object(result);
};

/**
 * Handler for sending rivals
 */
export const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise<any> => {
    const refid = $(data).attr()['ref_id'];
    const rivals = await utils.readRivals(refid);
    let result = {
        friend: []
    }

    for (const rival of rivals.rivals.slice(0, 2)) {
        const profile = await utils.readProfile(rival);
        const params = await utils.readParams(rival, version);

        const scores = await getScores(rival);

        result.friend.push({
            open: K.ITEM('s8', 1),
            g_pm_id: K.ITEM('str', profile.friendId),
            name: K.ITEM('str', profile.name),
            chara: K.ITEM('s16', params.params.chara || -1),
            clear_medal: K.ARRAY('u16', scores.clear_medal),
            hiscore: K.ITEM('bin', Buffer.from(scores.hiscore_array))
        });
    }

    send.object(result);
}

/**
 * Read the user scores and format them
 * @param refid ID of the user
 */
const getScores = async (refid: string) => {
    let hiscore_array = Array(Math.floor((((GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8)).fill(0);
    let clear_medal = Array(GAME_MAX_MUSIC_ID).fill(0);

    const scoresData = await utils.readScores(refid, version);
    const playCount = new Map();
    for (const key in scoresData.scores) {
        const keyData = key.split(':');
        const score = scoresData.scores[key];
        const music = parseInt(keyData[0], 10);
        const sheet = parseInt(keyData[1], 10);

        if (music > GAME_MAX_MUSIC_ID) {
            continue;
        }
        if ([0, 1, 2, 3].indexOf(sheet) == -1) {
            continue;
        }

        const medal = {
            100: 1,
            200: 2,
            300: 3,
            400: 5,
            500: 5,
            600: 6,
            700: 7,
            800: 9,
            900: 10,
            1000: 11,
            1100: 15,
        }[score.clear_type] || 0;
        clear_medal[music] = clear_medal[music] | (medal << (sheet * 4));

        const hiscore_index = (music * 4) + sheet;
        const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8);
        const hiscore_bit_pos = ((hiscore_index * 17) % 8);
        const hiscore_value = score.score << hiscore_bit_pos;
        hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF);
        hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF);
        hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF);

        playCount.set(music, (playCount.get(music) || 0) + score.cnt);
    }

    return {
        hiscore_array,
        clear_medal,
        playCount
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

const version: string = 'v20';
const GAME_MAX_MUSIC_ID = 1150;

const extraData: ExtraData = {
    mode: { type: 'u8', path: 'base', pathSrc: '', default: 0 },
    button: { type: 's8', path: 'base', pathSrc: '', default: 0 },
    last_play_flag: { type: 's8', path: 'base', pathSrc: '', default: -1 },
    medal_and_friend: { type: 'u8', path: 'base', pathSrc: '', default: 0 },
    category: { type: 's8', path: 'base', pathSrc: '', default: -1 },
    sub_category: { type: 's8', path: 'base', pathSrc: '', default: -1 },
    chara: { type: 's16', path: 'base', pathSrc: '', default: -1 },
    chara_category: { type: 's8', path: 'base', pathSrc: '', default: -1 },
    collabo: { type: 'u8', path: 'base', pathSrc: '', default: 255 },
    sheet: { type: 'u8', path: 'base', pathSrc: '', default: 0 },
    tutorial: { type: 's8', path: 'base', pathSrc: '', default: 0 },
    music_open_pt: { type: 's32', path: 'base', pathSrc: '', default: 0 },
    option: { type: 's32', path: 'base', pathSrc: '', default: 0 },
    music: { type: 's16', path: 'base', pathSrc: '', default: -1 },
    ep: { type: 'u16', path: 'base', pathSrc: '', default: 0 },
    sp_color_flg: { type: 's32', path: 'base', pathSrc: '', default: [0, 0], isArray: true },
    read_news: { type: 's32', path: 'base', pathSrc: '', default: 0 },
    consecutive_days_coupon: { type: 's16', path: 'base', pathSrc: '', default: 0 },

    title: { type: 'u8', path: 'player_card', pathSrc: '', default: [0, 1], isArray: true },
    frame: { type: 'u8', path: 'player_card', pathSrc: '', default: 0 },
    base: { type: 'u8', path: 'player_card', pathSrc: '', default: 0 },
    seal: { type: 'u8', path: 'player_card', pathSrc: '', default: [0, 0], isArray: true },
    get_title: { type: 's32', path: 'player_card', pathSrc: '', default: [0, 0, 0, 0], isArray: true },
    get_frame: { type: 's32', path: 'player_card', pathSrc: '', default: 0 },
    get_base: { type: 's32', path: 'player_card', pathSrc: '', default: 0 },
    get_seal: { type: 's32', path: 'player_card', pathSrc: '', default: [0, 0], isArray: true },

    get_title_ex: { type: 's32', path: 'player_card_ex', default: 0 },
    get_frame_ex: { type: 's32', path: 'player_card_ex', default: 0 },
    get_base_ex: { type: 's32', path: 'player_card_ex', default: 0 },
    get_seal_ex: { type: 's32', path: 'player_card_ex', default: 0 },

    sp: { type: 's32', path: 'sp_data', default: 0 },

    reflec: { type: 's8', path: 'reflec_data', default: [0, 0], isArray: true },

    genre: { type: 's8', path: 'navigate', default: 0 },
    image: { type: 's8', path: 'navigate', default: 0 },
    level: { type: 's8', path: 'navigate', default: 0 },
    ojama: { type: 's8', path: 'navigate', default: 0 },
    limit_num: { type: 's16', path: 'navigate', default: 0 },
    button__1: { type: 's8', path: 'navigate', default: 0 },
    life: { type: 's8', path: 'navigate', default: 0 },
    progress: { type: 's16', path: 'navigate', default: 0 },
}