public final class PlayerInformation implements IExtendedEntityProperties, QuestProvider {

  public static final String IDENTIFIER = "questology_playerinfo";

  public static PlayerInformation forPlayer(Entity player) {
    return (PlayerInformation) player.getExtendedProperties(IDENTIFIER);
  }

  public static final int MAX_KARMA_VALUE = 50;

  private boolean dirty = true;
  private float karma;
  private byte[] eventAmounts = new byte[PlayerInformation.CountableKarmaEvent.values().length];

  private final EntityPlayer player;

  private int invisibilityCooldown;

  private int flyTime;

  private byte[] enderbookRecipes = new byte[0];

  private List<Quest> activeQuests = Lists.newArrayList();

  private EntityCCTVBug cctv;

  public PlayerInformation(EntityPlayer player) {
    this.player = player;
  }

  @Override
  public void init(Entity entity, World world) {
    // nothing for now
  }

  @Override
  public void saveNBTData(NBTTagCompound nbtPlayer) {
    NBTTagCompound nbt = new NBTTagCompound();

    nbt.setFloat("karma", karma);
    nbt.setShort("flyTime", (short) flyTime);
    nbt.setShort("invisCooldown", (short) invisibilityCooldown);

    NBTTagList eventList = new NBTTagList();
    for (int i = 0; i < eventAmounts.length; i++) {
      NBTTagCompound evtInfo = new NBTTagCompound();
      evtInfo.setByte("id", (byte) i);
      evtInfo.setByte("value", eventAmounts[i]);
      eventList.appendTag(evtInfo);
    }
    nbt.setTag("events", eventList);

    nbt.setByteArray("enderBook", enderbookRecipes);

    nbt.setTag("quests", Quest.writeQuests(activeQuests));

    nbtPlayer.setCompoundTag(IDENTIFIER, nbt);
  }

  @Override
  public void loadNBTData(NBTTagCompound playerNbt) {
    NBTTagCompound nbt = playerNbt.getCompoundTag(IDENTIFIER);

    karma = nbt.getFloat("karma");
    flyTime = nbt.getShort("flyTime");
    invisibilityCooldown = nbt.getShort("invisCooldown");

    NBTTagList eventList = nbt.getTagList("events");
    for (int i = 0; i < eventList.tagCount(); i++) {
      NBTTagCompound evtInfo = (NBTTagCompound) eventList.tagAt(i);
      byte eventId = evtInfo.getByte("id");
      if (eventId >= 0 && eventId < eventAmounts.length) {
        eventAmounts[eventId] = evtInfo.getByte("value");
      }
    }

    enderbookRecipes = nbt.getByteArray("enderBook");
    activeQuests = Quest.readQuests(nbt.getTagList("quests"), player);
  }

  public float getKarma() {
    return karma;
  }

  public float setKarma(float karma) {
    if (this.karma != karma) {
      this.karma = karma;
      if (this.karma > MAX_KARMA_VALUE) {
        this.karma = MAX_KARMA_VALUE;
      }
      if (this.karma < -MAX_KARMA_VALUE) {
        this.karma = -MAX_KARMA_VALUE;
      }
      setDirty();
    }
    return this.karma;
  }

  public float modifyKarma(float modifier) {
    player.worldObj.playSoundAtEntity(
        player, "questology:karma" + (modifier < 0 ? "down" : "up"), 1, 1);

    return setKarma(karma + modifier);
  }

  public float modifyKarmaWithMax(float modifier, float max) {
    if (karma < max) {
      modifyKarma(modifier);
    }
    return karma;
  }

  public float modifyKarmaWithMin(float modifier, float min) {
    if (karma > min) {
      modifyKarma(modifier);
    }
    return karma;
  }

  public byte getEventAmount(CountableKarmaEvent event) {
    return eventAmounts[event.ordinal()];
  }

  public boolean setEventAmount(CountableKarmaEvent event, int amount) {
    if (amount < event.getMaxCount() && eventAmounts[event.ordinal()] != amount) {
      eventAmounts[event.ordinal()] = (byte) amount;
      setDirty();
      return true;
    } else {
      return false;
    }
  }

  public boolean increaseEventAmount(PlayerInformation.CountableKarmaEvent event) {
    return setEventAmount(event, eventAmounts[event.ordinal()] + 1);
  }

  public void addQuest(Quest quest) {
    activeQuests.add(quest);
  }

  public void setQuests(List<Quest> quests) {
    for (Quest quest : quests) {
      quest.setPlayer(player);
    }
    activeQuests = quests;
  }

  public static enum CountableKarmaEvent {
    PIGMEN_ATTACK(1),
    CREATE_SNOWGOLEM(2),
    CREATE_IRONGOLEM(3);

    private final int maxCount;

    private CountableKarmaEvent(int maxCount) {
      this.maxCount = maxCount;
    }

    public int getMaxCount() {
      return maxCount;
    }
  }

  public int getFlyTime() {
    return flyTime;
  }

  public void setFlyTime(int flyTime) {
    if (this.flyTime != flyTime) {
      this.flyTime = flyTime;
      setDirty();
    }
  }

  public int getInvisibilityCooldown() {
    return invisibilityCooldown;
  }

  public void setInvisibilityCooldown(int cooldown) {
    if (invisibilityCooldown != cooldown) {
      invisibilityCooldown = cooldown;
    }
  }

  public byte[] getEnderBookRecipesRaw() {
    return enderbookRecipes;
  }

  public SummoningRecipe[] getEnderBookRecipes() {
    SummoningRecipe[] recipes = new SummoningRecipe[enderbookRecipes.length];
    for (int i = 0; i < enderbookRecipes.length; i++) {
      recipes[i] = Multitypes.getType(QuestologyItem.recipePage, enderbookRecipes[i]);
    }
    return recipes;
  }

  public void setEnderBookRecipes(byte[] recipes) {
    enderbookRecipes = recipes;
  }

  public void addEnderBookRecipe(int recipe) {
    if (!hasEnderBookRecipe(recipe)) {
      enderbookRecipes = Arrays.copyOf(enderbookRecipes, enderbookRecipes.length + 1);
      enderbookRecipes[enderbookRecipes.length - 1] = UnsignedBytes.checkedCast(recipe);
      setDirty();
    }
  }

  public boolean hasEnderBookRecipe(int recipe) {
    return Bytes.contains(enderbookRecipes, UnsignedBytes.checkedCast(recipe));
  }

  public void tick() {
    if (cctv != null && (cctv.isDead || cctv.getDistanceSqToEntity(player) > 4096)) {
      cctv = null;
    }
    if (dirty) {
      updateClient();
      dirty = false;
    }
  }

  private void updateClient() {
    new PacketPlayerInfo(this).sendTo(player);
  }

  /** marks that this needs to be resend to the client */
  public void setDirty() {
    dirty = true;
  }

  public void setBoundCCTV(EntityCCTVBug cctv) {
    if (this.cctv != cctv) {
      setDirty();
    }
    this.cctv = cctv;
  }

  public EntityCCTVBug getCCTV() {
    return cctv;
  }

  @Override
  public List<Quest> getQuests() {
    return activeQuests;
  }
}
 public boolean increaseEventAmount(PlayerInformation.CountableKarmaEvent event) {
   return setEventAmount(event, eventAmounts[event.ordinal()] + 1);
 }