import { Profile } from '../models/profile';
import { Scores } from '../models/scores';
import { NosVersionHelper } from '../utils';
import { permitted_list, forte_permitted_list } from './common';

// export const event_list = {
//   event: {
//     '@attr': {
//       index: 1,
//     },
//     'status': K.ITEM('s8', 1),
//     'start_time': K.ITEM('u64', BigInt(0)),
//     'end_time': K.ITEM('u64', BigInt(0)),
//   },
// };

const getEventInfo = (version: NosVersionHelper) => {
  const event: any[] = [];
  const event_num = version.getEventMaxIndex()
  for (let i = 1; i <= event_num; ++i) {
    event.push({
      type: K.ITEM('s32', 4),
      index: K.ITEM('s32', i),
      status: K.ITEM('s8', 1),
      start_time: K.ITEM('u64', BigInt(0)),
      end_time: K.ITEM('u64', BigInt(0)),
      param1: K.ITEM('u64', BigInt(0)),
      param2: K.ITEM('u64', BigInt(0)),
    });
  }
  return event;
};

const getPlayerData = async (refid: string, info: EamuseInfo, name?: string) => {
  const p = await readProfile(refid);
  const version = new NosVersionHelper(info)

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

  const param: any[] = [];
  for (const t in p.params) {
    const para = p.params[t];
    param.push(K.ATTR({ type: t }, {
      count: K.ITEM('s32', para.length),
      params_array: K.ARRAY('s32', para),
    }));
  }

  const brooch: any[] = [];
  for (const b in p.brooches) {
    if (parseInt(b, 10) > version.getBroochMaxIndex()) continue;
    const bData = p.brooches[b];
    brooch.push(K.ATTR({ index: b }, {
      watch_count: K.ITEM('s32', bData.watch),
      level: K.ITEM('s8', bData.level),
      invested_steps: K.ITEM('s32', bData.steps),
      is_new_brooch: K.ITEM('bool', bData.new),
    }));
  }

  // Unlock brooches
  for (let i = 101; i <= 124; ++i) {
    if (i > version.getBroochMaxIndex()) continue;
    brooch.push(K.ATTR({ index: `${i}` }, {
      'watch_count': K.ITEM('s32', 0),
      'level': K.ITEM('s8', 1),
      'invested_steps': K.ITEM('s32', 0),
      'is_new_brooch': K.ITEM('bool', 0),
    }));
  }

  // Forte
  const stairs: any[] = [];
  for (const s in (p.cat_stairs || defaultProfile.cat_stairs)) {
    const stair = (p.cat_stairs || defaultProfile.cat_stairs)[s];

    stairs.push(K.ATTR({ index: s }, {
      total_steps: K.ITEM("s32", stair.total),
      chapter_index: K.ITEM("s32", stair.index),
      chapter_steps: K.ITEM("s32", stair.steps),
      chapter_goal: K.ITEM("s32", stair.goal)
    }));
  }

  // >= Op2
  const kentei_record: any[] = [];
  for (const k in p.kentei) {
    const kentei = p.kentei[k];

    kentei_record.push({
      stage_prog: K.ITEM('s8', kentei.stage),
      kentei_index: K.ITEM('s32', parseInt(k, 10)),
      score: K.ARRAY('s32', kentei.score),
      clear_rate: K.ITEM('s32', kentei.rate),
      clear_flag: K.ITEM('u32', kentei.flag),
      play_count: K.ITEM('s32', kentei.count),
    });
  }

  // >= Op2
  const island_progress: any[] = [];
  for (const i in p.islands) {
    const island = p.islands[i];

    const container: any[] = [];
    for (const c in island.containers) {
      const cont = island.containers[c];

      const reward: any[] = [];
      for (const r in cont.rewards) {
        const rew = cont.rewards[r];

        reward.push({
          reward_index: K.ITEM('s32', parseInt(r, 10)),
          point: K.ITEM('s32', rew),
        });
      }

      container.push({
        container_no: K.ITEM('s32', parseInt(c, 10)),
        movie_prog: K.ITEM('s8', cont.prog),
        reward,
      });
    }

    island_progress.push(K.ATTR({ index: i }, {
      lookUnlockWin: K.ITEM('s32', island.look),
      select_container: K.ITEM('s32', island.select),
      travelledTime: K.ITEM('u32', island.time),
      container,
    }));
  }

  const correct_permitted_list = !version.isFirstOrForte() ? permitted_list : forte_permitted_list
  const music_list = [
    K.ARRAY('s32', p.musicList.type_0, { sheet_type: '0' }),
    K.ARRAY('s32', p.musicList.type_1, { sheet_type: '1' }),
    K.ARRAY('s32', p.musicList.type_2, { sheet_type: '2' }),
    K.ARRAY('s32', p.musicList.type_3, { sheet_type: '3' }),
  ];
  const music_list2 = [
    K.ARRAY('s32', p.musicList2.type_0, { sheet_type: '0' }),
    K.ARRAY('s32', p.musicList2.type_1, { sheet_type: '1' }),
    K.ARRAY('s32', p.musicList2.type_2, { sheet_type: '2' }),
    K.ARRAY('s32', p.musicList2.type_3, { sheet_type: '3' }),
  ];

  if(version.isFirstOrForte()) {
    music_list.pop();
    music_list2.pop();
  }

  return {
    name: K.ITEM('str', p.name),
    play_count: K.ITEM('s32', p.playCount),
    today_play_count: K.ITEM('s32', p.todayPlayCount),
    permitted_list: correct_permitted_list,
    event_info_list: { event: getEventInfo(version) }, // Op2
    event_control_list: { event: getEventInfo(version) }, // Forte
    music_list: {
      flag: music_list,
    },
    free_for_play_music_list: {
      flag: music_list2,
    },
    last: {
      music_index: K.ITEM('s32', version.numericHandler('music_index', p.music, 0)),
      sheet_type: K.ITEM('s8', version.numericHandler('sheet_type', p.sheet, 0)),
      brooch_index: K.ITEM('s32', version.numericHandler('brooch_index', p.brooch, 0)),
      hi_speed_level: K.ITEM('s32', p.hispeed),
      beat_guide: K.ITEM('s8', p.beatGuide),
      headphone_volume: K.ITEM('s8', p.headphone),
      judge_bar_pos: K.ITEM('s32', p.judgeBar),
      music_group: K.ITEM('s32', p.group),
      hands_mode: version.isFirstOrForte() ? K.ITEM('s32', p.mode) : K.ITEM('s8', p.mode),
      near_setting: K.ITEM('s8', p.near),
      judge_delay_offset: version.isFirstOrForte() ? K.ITEM('s32', p.offset) : K.ITEM('s8', p.offset),
      bingo_index: K.ITEM('s32', p.bingo),
      total_skill_value: K.ITEM('u64', BigInt(p.skill)),
      key_beam_level: K.ITEM('s8', p.keyBeam),
      orbit_type: K.ITEM('s8', p.orbit),
      note_height: K.ITEM('s8', p.noteHeight),
      note_width: K.ITEM('s8', p.noteWidth),
      judge_width_type: K.ITEM('s8', p.judgeWidth),
      beat_guide_volume: K.ITEM('s8', p.beatVolume),
      beat_guide_type: K.ITEM('s8', p.beatType),
      key_volume_offset: K.ITEM('s8', p.keyVolume),
      bgm_volume_offset: K.ITEM('s8', p.bgmVolume),
      note_disp_type: K.ITEM('s8', p.note),
      slow_fast: K.ITEM('s8', p.sf),
      judge_effect_adjust: K.ITEM('s8', p.judgeFX),
      simple_bg: K.ITEM('s8', p.simple),
    },
    // TODO: Full unlock instead of saving?
    cat_progress: {
      stair: stairs
    },
    brooch_list: { brooch },
    extra_param: { param },
    present_list: {},
    various_music_list: {
      data: [
        K.ATTR({ list_type: '0' }, {
          cond_flag: K.ITEM('s32', 0),
          flag: K.ITEM('s32', 0, { sheet_type: '0' }),
        }),
      ],
    },
    island_progress_list: { island_progress },
    player_information_list: {},
    kentei_record_list: { kentei_record },
    linkage_data_list: {},
    travel: {
      money: K.ITEM('s32', p.money),
      fame: K.ITEM('s32', p.fame),
      fame_index: K.ITEM('s32', p.fameId),
      island_id: K.ITEM('s32', p.island),
    },
  };
};

export const regist_playdata: EPR = async (info, data, send) => {
  const refid = $(data).str('refid');
  if (!refid) return send.deny();

  const name = $(data).str('name');
  console.debug(`nos op2 regist: ${name}`);

  send.object(await getPlayerData(refid, info, name));
};

export const get_playdata: EPR = async (info, data, send) => {
  const refid = $(data).str('refid');
  if (!refid) return send.deny();

  send.object(await getPlayerData(refid, info));
};

// export const set_stage_result: EPR = async (info, data, send) => {
//   return send.object();
// };

export const set_total_result: EPR = async (info, data, send) => {
  const refid = $(data).str('refid');
  if (!refid) return send.deny();

  const isForte = new NosVersionHelper(info).isFirstOrForte()
  const p = await readProfile(refid);

  p.playCount = $(data).number('play_count', p.playCount);
  p.todayPlayCount = $(data).number('today_play_count', p.todayPlayCount);

  const last = $(data).element('last');

  p.music = last.number('music_index', p.music);
  p.sheet = last.number('sheet_type', p.sheet);
  p.brooch = last.number('brooch_index', p.brooch);
  p.hispeed = last.number('hi_speed_level', p.hispeed);
  p.beatGuide = last.number('beat_guide', p.beatGuide);
  p.headphone = last.number('headphone_volume', p.headphone);
  p.judgeBar = last.number('judge_bar_pos', p.judgeBar);
  p.group = last.number('music_group', p.group);
  p.mode = last.number('hands_mode', p.mode);
  p.near = last.number('near_setting', p.near);
  p.offset = last.number('judge_delay_offset', p.offset);
  p.bingo = last.number('bingo_index', p.bingo);
  p.skill = `${last.bigint('total_skill_value') || p.skill}`;
  p.keyBeam = last.number('key_beam_level', p.keyBeam);
  p.orbit = last.number('orbit_type', p.orbit);
  p.noteHeight = last.number('note_height', p.noteHeight);
  p.noteWidth = last.number('note_width', p.noteWidth);
  p.judgeWidth = last.number('judge_width_type', p.judgeWidth);
  p.beatVolume = last.number('beat_guide_volume', p.beatVolume);
  p.beatType = last.number('beat_guide_type', p.beatType);
  p.keyVolume = last.number('key_volume_offset', p.keyVolume);
  p.bgmVolume = last.number('bgm_volume_offset', p.bgmVolume);
  p.note = last.number('note_disp_type', p.note);
  p.sf = last.number('slow_fast', p.sf);
  p.judgeFX = last.number('judge_effect_adjust', p.judgeFX);
  p.simple = last.number('simple_bg', p.simple);

  p.money = $(data).number('travel.money', p.money);
  p.fame = $(data).number('travel.fame', p.fame);
  p.fameId = $(data).number('travel.fame_index', p.fameId);
  p.island = $(data).number('travel.island_id', p.island);

  let flags = _.get($(data).obj, 'music_list.flag', []);
  if (!_.isArray(flags)) flags = [flags];
  for (const flag of flags) {
    const sheet = _.get(flag, '@attr.sheet_type', -1);
    if (sheet == '0') {
      p.musicList.type_0 = _.get(flag, '@content', p.musicList.type_0);
    } else if (sheet == '1') {
      p.musicList.type_1 = _.get(flag, '@content', p.musicList.type_1);
    } else if (sheet == '2') {
      p.musicList.type_2 = _.get(flag, '@content', p.musicList.type_2);
    } else if (sheet == '3') {
      p.musicList.type_3 = _.get(flag, '@content', p.musicList.type_3);
    }
  }

  let freeFlags = _.get($(data).obj, 'free_for_play_music_list.flag', []);
  if (!_.isArray(freeFlags)) freeFlags = [freeFlags];
  for (const flag of freeFlags) {
    const sheet = _.get(flag, '@attr.sheet_type', -1);
    if (sheet == '0') {
      p.musicList2.type_0 = _.get(flag, '@content', p.musicList2.type_0);
    } else if (sheet == '1') {
      p.musicList2.type_1 = _.get(flag, '@content', p.musicList2.type_1);
    } else if (sheet == '2') {
      p.musicList2.type_2 = _.get(flag, '@content', p.musicList2.type_2);
    } else if (sheet == '3') {
      p.musicList2.type_3 = _.get(flag, '@content', p.musicList2.type_3);
    }
  }

  // KENTEI
  let kenteis = $(data).elements('kentei_result_list.kentei_result');
  for (const kentei of kenteis) {
    const index = kentei.number('kentei_index', -1);
    if (index < 0) continue;

    const clearRate = kentei.number('clear_rate', 0);
    const oldClearRate = _.get(p, `kentei.${index}.rate`, 0);
    const isHigh = clearRate >= oldClearRate;

    p.kentei[index] = {
      rate: isHigh ? clearRate : oldClearRate,
      score: isHigh ? kentei.number('score', 0) : _.get(p, `kentei.${index}.score`, 0),
      stage: Math.max(kentei.number('stage_prog', 0), _.get(p, `kentei.${index}.stage`, 0)),
      flag: Math.max(kentei.number('clear_flag', 0), _.get(p, `kentei.${index}.flag`, 0)),
      count: Math.max(kentei.number('play_count', 0), _.get(p, `kentei.${index}.count`, 0)),
    };
  }

  // PARAMS
  let params = $(data).elements('extra_param.param');
  for (const param of params) {
    const type = param.attr().type;
    const parray = param.numbers('params_array');
    if (type == null || parray == null) continue;

    p.params[type] = parray;
  }

  // BROOCHES
  let broochs = $(data).elements('brooch_list.brooch');
  for (const brooch of broochs) {
    const index = parseInt(brooch.attr().index || '-1', 10);
    if (index < 0) continue;

    p.brooches[index] = {
      watch: brooch.number('watch_count', 0),
      level: brooch.number('level', 1),
      steps: brooch.number('invested_steps', 0),
      new: brooch.number('is_new_brooch', 0),
    };
  }

  // ISLAND
  let islands = $(data).elements('island_progress_list.island_progress');
  for (const island of islands) {
    const index = parseInt(island.attr().index || '-1', 10);
    if (index < 0) continue;

    const containers: Profile['islands']['0']['containers'] = {};
    let conts = $(data).elements('container');
    for (const cont of conts) {
      const index = cont.number('container_no', -1);
      if (index < 0) continue;

      const rewards: { [key: string]: number } = {};
      let rews = $(data).elements('reward');
      for (const rew of rews) {
        const index = rew.number('reward_index', -1);
        if (index < 0) continue;
        rewards[index] = rew.number('point', 0);
      }

      containers[index] = {
        prog: cont.number('movie_prog', 1),
        rewards,
      };
    }

    p.islands[index] = {
      look: island.number('lookUnlockWin', 0),
      select: island.number('select_container', 0),
      time: island.number('travelledTime', 0),
      containers,
    };
  }

  // CAT STAIR
  let stairs = $(data).elements('cat_progress.stair');
  if (!p.cat_stairs) {
    p.cat_stairs = defaultProfile.cat_stairs
  }
  for (const stair of stairs) {
    const index = parseInt(stair.attr().index || '-1', 10);
    if (index < 0) continue;

    p.cat_stairs[index] = {
      total: stair.number('total_steps', 0),
      index: stair.number('chapter_index', 1),
      steps: stair.number('chapter_steps', 0),
      goal: stair.number('chapter_goal', 0),
    };
  }

  await writeProfile(refid, p);

  const scoreData = await readScores(refid);
  // Save Scores
  let stages = $(data).elements('stageinfo.stage');
  for (const stage of stages) {
    const mid = stage.attr().music_index
    const type = stage.attr().sheet_type

    const key = `${mid}:${type}`;
    const c = isForte ? stage : stage.element('common');
    const o = _.get(scoreData, `scores.${key}`, {});
    const isHigh = c.number('score', 0) >= _.get(o, 'score', 0);
    scoreData.scores[key] = {
      score: Math.max(c.number('score', 0), _.get(o, 'score', 0)),
      grade: Math.max(Math.max(c.number('grade_basic', 0), c.number('evaluation', 0)), _.get(o, 'grade', 0)),
      recital: Math.max(c.number('grade_recital', 0), _.get(o, 'recital', 0)),
      mode: isHigh ? c.number('hands_mode', 0) : _.get(o, 'mode', 0),
      count: Math.max(c.number('play_count', 0), _.get(o, 'count', 1)),
      clear: c.number('clear_count', _.get(o, 'clear', 0)),
      multi: c.number('multi_count', _.get(o, 'multi', 0)),
      flag: Math.max(c.number('clear_flag', 0), _.get(o, 'flag', 0)),
    };
  }

  // Save Recitals
  const rInfo = $(data).element('recital_info.recital');
  if (rInfo) {
    const rIndex = rInfo.number('recital_index', -1);
    if (rIndex >= 0) {
      const r = rInfo.element('result');
      const o = _.get(scoreData, `recitals.${rIndex}`);
      const isHigh = r.number('total_score', 0) >= _.get(o, 'score', 0);
      scoreData.recitals[rIndex] = {
        count: Math.max(rInfo.number('recital_count', 0), _.get(o, 'count', 1)),
        hall: isHigh ? rInfo.number('hall_index') : _.get(o, 'hall', 0),
        cat: isHigh ? rInfo.numbers('cat_index') : _.get(o, 'cat', [3, 7, 9, 5, 16]),
        audience: isHigh ? r.number('audience') : _.get(o, 'audience', 0),
        money: isHigh ? r.number('money') : _.get(o, 'money', 0),
        fame: isHigh ? r.number('fame') : _.get(o, 'fame', 0),
        player: isHigh ? r.number('player_fame') : _.get(o, 'player', 0),
        score: isHigh ? r.number('total_score', 0) : _.get(o, 'score', 0),
        start: (isHigh ? r.number('recital_start_time', 0) : _.get(o, 'start', 0)).toString(),
        end: (isHigh ? r.number('recital_end_time', 0) : _.get(o, 'end', 0)).toString(),
      };
    }
  }

  await writeScores(refid, scoreData);

  send.success()
};

export const get_musicdata: EPR = async (info, data, send) => {
  const refid = $(data).str('refid');
  if (!refid) return send.deny();

  const version = new NosVersionHelper(info)
  const scoreData = await readScores(refid);

  const recital_record: any[] = [];
  const music: any[] = [];

  for (const r in scoreData.recitals) {
    const reci = scoreData.recitals[r];
    recital_record.push({
      recital_index: K.ITEM('s32', parseInt(r, 10)),
      recital_count: K.ITEM('s32', reci.count),
      hall_index: K.ITEM('s8', reci.hall),
      cat_index: K.ARRAY('s16', reci.cat),
      audience: K.ITEM('s32', reci.audience),
      money: K.ITEM('s32', reci.money),
      fame: K.ITEM('s32', reci.fame),
      player_fame: K.ITEM('s32', reci.player),
      total_score: K.ITEM('s32', reci.score),
      total_base_score: K.ITEM('s32', reci.score),
      best_start_time: K.ITEM('u64', BigInt(reci.start)),
      best_end_time: K.ITEM('u64', BigInt(reci.end)),
    });
  }

  for (const m in scoreData.scores) {
    const mdata = m.split(':');
    const musi = scoreData.scores[m];

    if (parseInt(mdata[0], 10) > version.getMusicMaxIndex()) continue;

    music.push(K.ATTR({
      music_index: mdata[0],
      sheet_type: mdata[1],
    }, {
      score: K.ITEM('s32', musi.score),
      evaluation: K.ITEM('u32', musi.grade), // Forte
      grade_basic: K.ITEM('u32', musi.grade),
      grade_recital: K.ITEM('u32', musi.recital),
      play_count: K.ITEM('s32', musi.count),
      clear_count: K.ITEM('s32', musi.clear),
      multi_count: K.ITEM('s32', musi.multi),
      hands_mode: K.ITEM('s8', musi.mode),
      clear_flag: K.ITEM('s32', musi.flag),
    }));
  }

  send.object({
    recital_record,
    music,
  });
};

async function readProfile(refid: string): Promise<Profile> {
  const profile = await DB.FindOne<Profile>(refid, { collection: 'profile' })
  return profile || defaultProfile
}

async function writeProfile(refid: string, profile: Profile) {
  await DB.Upsert<Profile>(refid, { collection: 'profile' }, profile)
}

async function readScores(refid: string): Promise<Scores> {
  const score = await DB.FindOne<Scores>(refid, { collection: 'scores' })
  return score || { collection: 'scores', recitals: {}, scores: {} }
}

async function writeScores(refid: string, scores: Scores) {
  await DB.Upsert<Scores>(refid, { collection: 'scores' }, scores)
}

const defaultProfile: Profile = {
  collection: 'profile',

  name: 'GUEST',
  music: 0,
  sheet: 0,
  brooch: 0,
  hispeed: 0,
  beatGuide: 1,
  headphone: 0,
  judgeBar: 250,
  group: 0,
  mode: 0,
  near: 0,
  offset: 0,
  bingo: 0,
  skill: '0',
  playCount: 0,
  todayPlayCount: 0,
  keyBeam: 0,
  orbit: 0,
  noteHeight: 10,
  noteWidth: 0,
  judgeWidth: 0,
  beatVolume: 0,
  beatType: 0,
  keyVolume: 0,
  bgmVolume: 0,
  note: 0,
  sf: 0,
  judgeFX: 0,
  simple: 0,
  money: 0,
  fame: 0,
  fameId: 0,
  island: 0,
  brooches: {
    '1': {
      level: 1,
      watch: 0,
      steps: 0,
      new: 0,
    },
  },
  islands: {},
  kentei: {},
  cat_stairs: {
    '0': {
      total: 0,
      index: 0,
      steps: 0,
      goal: 0
    }
  },
  params: {
    '1': [0],
  },
  musicList: {
    type_0: Array(32).fill(-1),
    type_1: Array(32).fill(-1),
    type_2: Array(32).fill(-1),
    type_3: Array(32).fill(-1),
  },
  musicList2: {
    type_0: Array(32).fill(-1),
    type_1: Array(32).fill(-1),
    type_2: Array(32).fill(-1),
    type_3: Array(32).fill(-1),
  },
}
