import * as mc from '@minecraft/server';
import * as config from './pb_scraft_config.js';

export const SPELL_PLAYERS = new Map();
const PLAYER_TICK_INTERVAL = 1;

const SOUND_FAIL = "pb_scraft.spell_fail";
const SOUND_OUT_OF_MANA = "pb_scraft.out_of_mana";
const SOUND_MANA_RESTORE = "pb_scraft.mana_restore";
const SOUND_OPTIONS = { pitch: 1.0, volume: 1.0, };

const MANA_REG_BASE = 5;
const MANA_MAX_BASE = 100;
const SPELLPOWER_BASE = 0;

export default class SpellPlayer {

  tickingSpells = new Map();  //all active spell instances on the player, a spell is active when holding, charging or on cooldown and cannot be recast during active state
  tickClock = 0;

  player;
  manaReg = config.MANA_REG;
  manaMax = MANA_MAX_BASE;
  spellPower = SPELLPOWER_BASE;
  manaCurrent = 100;

  messageOptions = {
    actionbar: "compact",
    failText: true,
    failSound: true,
  }
  isCasting = false;

  constructor(player) {
    this.player = player;
    mc.world.scoreboard.getObjective("pb_scraft_mana").addScore(player, 0); //add player to scoreboard
    mc.world.scoreboard.getObjective("pb_scraft_actionbar").addScore(player, 0); //add player to scoreboard
    mc.world.scoreboard.getObjective("pb_scraft_fail").addScore(player, 0); //add player to scoreboard

    this.readMessageOptions();
  }

  //runs every tick
  tick() {
    this.tickSpells();
    this.manaDisplay();

    //run every 20 ticks or 1.00 seconds
    if (this.tickClock % 20 == 0) {
      this.doManaTick();
    }

    if (this.tickClock == 20) this.tickClock = 0;
    this.tickClock++;
  }

  doManaTick() {
    if (this.isCasting) return;
    this.addMana(config.MANA_REG);
  }

  tickSpells() {
    for (let spell of this.tickingSpells.values()) {
      spell.tick();
      if (spell.isActive == false) this.removeSpell(spell.name)
    }
  }

  manaDisplay() {
    const item = this.player.getComponent('equippable').getEquipment('Mainhand');
    if (!item) return;
    if (this.messageOptions.actionbar == "off") return;

    if (config.SHOW_MANA_ITEMS.includes(item.typeId)) {
      this.player.onScreenDisplay.setActionBar(this.actionbarString(item));
    }
  }

  actionbarString(item) {

    let actionbar = "§d" + this.manaCurrent + "/" + this.manaMax;

    const spellId = item.getDynamicProperty("pb_scraft_spell");
    if (spellId === undefined) return actionbar + this.createBar(0, 10);
    const spell = this.tickingSpells.get(spellId)
    if (spell == undefined) return actionbar + this.createBar(0, 10);

    //if (spell.timeIndex >= spell.castTime) return actionbar + this.createBar(0, 10);

    if (config.SPELL_LIST.find((f) => f.identifier == spellId).type == "aura" && spell.timeIndex > spell.castTime) return actionbar + this.createBar(0, 10);

    return actionbar + this.createBar(spell.timeIndex, spell.castTime);
  }

  createBar(value, max) {
    let bar = " §l§u";
    if (value >= max) {
      bar = " §l§d";
      if (value % 4 < 2) bar = " §l§f";
      value = max;
    }

    let percentage = Math.round((value / max) * 100).toFixed(0);

    for (let index = 0; index < 20; index++) {
      if (index == Math.round(percentage / 4)) bar = bar + "§7";
      bar = bar + "|";
    };
    bar = bar + "§f";
    return bar;
  }

  launchSpell(spell, item) {
    const cooldown = item.getComponent("minecraft:cooldown");

    try {
      mc.world.getDimension(this.player.dimension.id).getBlock(this.player.location)
    }
    catch (error) {
      if (this.messageOptions.failSound) this.player.playSound(SOUND_FAIL, SOUND_OPTIONS);
      if (this.messageOptions.failText) this.player.sendMessage("§cOut of bounds");
      this.player.startItemCooldown(cooldown.cooldownCategory, 0);
      return false;
    };

    if (spell.manacost > this.manaCurrent) {
      if (this.messageOptions.failSound) this.player.playSound(SOUND_OUT_OF_MANA, SOUND_OPTIONS);
      if (this.messageOptions.failText) this.player.sendMessage("§cOut of mana");
      this.player.startItemCooldown(cooldown.cooldownCategory, 0);
      return false;
    };

    if (this.canCastSpell(spell.name) == false) {
      if (this.messageOptions.failSound) this.player.playSound(SOUND_OUT_OF_MANA, SOUND_OPTIONS);
      if (this.messageOptions.failText) this.player.sendMessage("§cSpell on cooldown");
      return false;
    };

    this.addSpell(spell.name, spell);
  }

  addMana(amount) {
    let manascore = mc.world.scoreboard.getObjective("pb_scraft_mana").getScore(this.player);
    manascore += amount;
    if (manascore > this.manaMax) manascore = this.manaMax;
    this.manaCurrent = manascore;
    mc.world.scoreboard.getObjective("pb_scraft_mana").setScore(this.player, manascore);
  }

  consumeMana(amount) {
    this.manaCurrent += -amount;
    if(this.manaCurrent < 0) this.manaCurrent = 0;
    mc.world.scoreboard.getObjective("pb_scraft_mana").setScore(this.player, this.manaCurrent);
  }

  stop(spellId) {
    const spell = this.tickingSpells.get(spellId)
    if (spell == undefined) return;
    if (spell.timeIndex >= spell.castTime) {
      this.tickingSpells.get(spellId).release();
      return;
    }
    else {
      this.tickingSpells.get(spellId).stop();
    }
  }

  addSpell(spellId, spell) {
    this.tickingSpells.set(spellId, spell);
  }

  removeSpell(spellId) {
    this.tickingSpells.delete(spellId);
  }

  manaConsumable(value) {
    this.player.playSound(SOUND_MANA_RESTORE, SOUND_OPTIONS);
    this.addMana(value);
  }

  effectConsumable(spell) {
    this.addSpell(spell.name, spell);
  }

  canCastSpell(spellName) {
    if (config.SPELL_LIST.find((f) => f.identifier == spellName).type == "aura") return true; //can always re-cast auras
    if (this.tickingSpells.get(spellName) === undefined) return true;
    return false;
  }

  updateSettings() {
    this.readMessageOptions();
  }

  readMessageOptions() {
    const actionbar = mc.world.scoreboard.getObjective("pb_scraft_actionbar").getScore(this.player);
    if (actionbar == 0) this.messageOptions.actionbar = "compact";

    const fail = mc.world.scoreboard.getObjective("pb_scraft_fail").getScore(this.player);
    if (fail == 0) {
      this.messageOptions.failText = true;
      this.messageOptions.failSound = true;
    }
  }
}

registerPlayers();

function registerPlayers() {
  mc.world.getAllPlayers().forEach(player => {
    SPELL_PLAYERS.set(player.id, new SpellPlayer(player));
  });

  mc.world.afterEvents.playerSpawn.subscribe((eventData) => {
    SPELL_PLAYERS.set(eventData.player.id, new SpellPlayer(eventData.player));
  })

  mc.world.afterEvents.playerLeave.subscribe((eventData) => {
    SPELL_PLAYERS.delete(eventData.playerId)
  })

  mc.system.runInterval(() => {
    for (let spellplayer of SPELL_PLAYERS.values()) {
      spellplayer.tick();
    }
  }, PLAYER_TICK_INTERVAL);
}