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

/**
 * Handler for getting the current state of the game (phase, good prices, etc...)
 */
export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise<any> => {
    const result = {
        ir_phase: K.ITEM('s32', 0),
        music_open_phase: K.ITEM('s32', 8),
        collabo_phase: K.ITEM('s32', 8),
        personal_event_phase: K.ITEM('s32', 10),
        shop_event_phase: K.ITEM('s32', 6),
        netvs_phase: K.ITEM('s32', 0),
        card_phase: K.ITEM('s32', 9),
        other_phase: K.ITEM('s32', 9),
        local_matching_enable: 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),
        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),
            collabo: K.ITEM('u8', 255),
            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]),
        },
        netvs: {
            rank_point: K.ITEM('s32', 0),
            record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]),
            rank: K.ITEM('u8', 0),
            vs_rank_old: K.ITEM('s8', 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]),
            netvs_play_cnt: K.ITEM('u8', 0),
        },
        hiscore: K.ITEM('bin', Buffer.from(scores.hiscore_array)),
        gakuen_data: {
            music_list: K.ITEM('s32', -1),
        },
        flying_saucer: {
            music_list: K.ITEM('s32', -1),
            tune_count: K.ITEM('s32', -1),
            clear_norma: K.ITEM('u32', 0),
            clear_norma_add: K.ITEM('u32', 0),
        },
        triple_journey: {
            music_list: K.ITEM('s32', -1),
            boss_damage: K.ARRAY('s32', [65534, 65534, 65534, 65534]),
            boss_stun: K.ARRAY('s32', [0, 0, 0, 0]),
            magic_gauge: K.ITEM('s32', 0),
            today_party: K.ITEM('s32', 0),
            union_magic: K.ITEM('bool', false),
            base_attack_rate: K.ITEM('float', 1.0),
            iidx_play_num: K.ITEM('s32', 0),
            reflec_play_num: K.ITEM('s32', 0),
            voltex_play_num: K.ITEM('s32', 0),
            iidx_play_flg: K.ITEM('bool', true),
            reflec_play_flg: K.ITEM('bool', true),
            voltex_play_flg: K.ITEM('bool', true),
        },
        ios: {
            continueRightAnswer: K.ITEM('s32', 30),
            totalRightAnswer: K.ITEM('s32', 30),
        },
        kac2013: {
            music_num: K.ITEM('s8', 0),
            music: K.ITEM('s16', 0),
            sheet: K.ITEM('u8', 0),
        },
        baseball_data: {
            music_list: K.ITEM('s64', BigInt(-1)),
        },
        floor_infection: [
            {
                infection_id: K.ITEM('s32', 3),
                music_list: K.ITEM('s32', -1),
            },
            {
                infection_id: K.ITEM('s32', 5),
                music_list: K.ITEM('s32', -1),
            },
            {
                infection_id: K.ITEM('s32', 7),
                music_list: K.ITEM('s32', -1),
            }
        ]
    };

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

    return player;
}

/**
 * Handler for saving profile and 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 = 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),
            hair: K.ITEM('u8', params.params.hair || 0),
            face: K.ITEM('u8', params.params.face || 0),
            body: K.ITEM('u8', params.params.body || 0),
            effect: K.ITEM('u8', params.params.effect || 0),
            object: K.ITEM('u8', params.params.object || 0),
            comment: K.ARRAY('u8', params.params.comment || [0, 0]),
            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 = 'v21';
const GAME_MAX_MUSIC_ID = 1350;

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 },
    sheet: { type: 'u8', path: 'base', pathSrc: '', default: 0 },
    tutorial: { type: 's8', path: 'base', pathSrc: '', default: 0 },
    music_open_pt: { type: 's8', 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 },
    gitadora_point: { type: 'u16', path: 'base', pathSrc: '', default: [2000, 2000, 2000], isArray: true },
    gitadora_select: { type: 'u8', path: 'base', pathSrc: '', default: 2 },

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

    point: { type: 'u16', path: 'zoo', default: [0, 0, 0, 0, 0], isArray: true },
    music_list__2: { type: 's32', path: 'zoo', default: [0, 0], isArray: true },
    today_play_flag: { type: 's8', path: 'zoo', default: [0, 0, 0, 0], isArray: true },

    hair: { type: 'u8', path: 'avatar', pathSrc: '', default: 0 },
    face: { type: 'u8', path: 'avatar', pathSrc: '', default: 0 },
    body: { type: 'u8', path: 'avatar', pathSrc: '', default: 0 },
    effect: { type: 'u8', path: 'avatar', pathSrc: '', default: 0 },
    object: { type: 'u8', path: 'avatar', pathSrc: '', default: 0 },
    comment: { type: 'u8', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true },
    get_hair: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true },
    get_face: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true },
    get_body: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true },
    get_effect: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true },
    get_object: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true },
    get_comment_over: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0, 0], isArray: true },
    get_comment_under: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0, 0], isArray: true },

    get_hair__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    get_face__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    get_body__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    get_effect__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    get_object__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    get_comment_over__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    get_comment_under__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    new_face: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    new_body: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    new_effect: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    new_object: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    new_comment_over: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
    new_comment_under: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true },
}