package me.dannytatom.xibalba.screens.creation;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.Colors;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.utils.viewport.FitViewport;

import java.util.ArrayList;
import java.util.Map;

import me.dannytatom.xibalba.Main;
import me.dannytatom.xibalba.screens.MainMenuScreen;
import me.dannytatom.xibalba.ui.ActionButton;
import me.dannytatom.xibalba.utils.PlayerSetup;
import me.dannytatom.xibalba.utils.yaml.DefectData;
import me.dannytatom.xibalba.utils.yaml.TraitData;

import org.apache.commons.lang3.text.WordUtils;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

public class YouScreen implements Screen {
  private final Stage stage;
  private final PlayerSetup playerSetup;
  private final ArrayList<TraitData> traits;
  private final ArrayList<DefectData> defects;
  private final VerticalGroup attributesGroup;
  private final VerticalGroup skillsGroup;
  private final VerticalGroup defectsGroup;
  private final VerticalGroup traitsGroup;
  private int attributePoints;
  private int skillPoints;
  private int traitPoints;
  private Section sectionSelected = Section.ATTRIBUTES;
  private int itemSelected = 0;

  /**
   * Character Creation: You Screen.
   *
   * @param main Instance of main class
   */
  public YouScreen(Main main) {
    attributePoints = 5;
    skillPoints = 10;
    traitPoints = 0;
    stage = new Stage(new FitViewport(960, 540));
    playerSetup = new PlayerSetup();

    traits = new ArrayList<>();
    Yaml traitYaml = new Yaml(new Constructor(TraitData.class));
    for (Map.Entry<String, String> entry : Main.traitsData.entrySet()) {
      traits.add((TraitData) traitYaml.load(entry.getValue()));
    }

    defects = new ArrayList<>();
    Yaml defectYaml = new Yaml(new Constructor(DefectData.class));
    for (Map.Entry<String, String> entry : Main.defectsData.entrySet()) {
      defects.add((DefectData) defectYaml.load(entry.getValue()));
    }

    Table table = new Table();
    table.setFillParent(true);
    table.left().top();
    table.pad(10);
    stage.addActor(table);

    Table titleTable = new Table();

    HorizontalGroup titleGroup = new HorizontalGroup();
    titleGroup.space(10);
    titleTable.add(titleGroup).pad(0, 0, 10, 0).width(Gdx.graphics.getWidth());

    ActionButton backButton = new ActionButton("Q", "Back");
    backButton.setKeys(Input.Keys.Q);
    backButton.setAction(table, () -> main.setScreen(new MainMenuScreen(main)));
    titleGroup.addActor(backButton);

    Label title = new Label("You", Main.skin);
    titleGroup.addActor(title);

    Label instructions = new Label(
        "[LIGHT_GRAY]Up & down to navigate a list, left & right to switch lists."
            + " x to add a point or trait/defect, z to remove.",
        Main.skin
    );
    titleGroup.addActor(instructions);

    attributesGroup = new VerticalGroup().top().left().columnLeft();
    skillsGroup = new VerticalGroup().top().left().columnLeft();
    defectsGroup = new VerticalGroup().top().left().columnLeft();
    traitsGroup = new VerticalGroup().top().left().columnLeft();

    float width = Gdx.graphics.getWidth() / 2;
    Table mainTable = new Table();
    mainTable.add(attributesGroup).pad(0, 0, 10, 0).width(width);
    mainTable.add(skillsGroup).pad(0, 0, 10, 0).width(width);
    mainTable.row();
    mainTable.add(defectsGroup).pad(0, 0, 10, 0).width(width);
    mainTable.add(traitsGroup).pad(0, 0, 10, 0).width(width);

    ActionButton continueButton = new ActionButton("ENTER", "Select Your GodData");
    continueButton.setKeys(Input.Keys.ENTER);
    continueButton.setAction(table, () -> main.setScreen(new GodScreen(main, playerSetup)));

    table.add(titleTable);
    table.row();
    table.add(mainTable);
    table.row();
    table.add(continueButton).left();

    updateAttributesGroup();
    updateSkillsGroup();
    updateDefectsGroup();
    updateTraitsGroup();

    Gdx.input.setInputProcessor(stage);
    stage.setKeyboardFocus(table);
  }

  @Override
  public void show() {

  }

  @Override
  public void render(float delta) {
    Gdx.gl.glClearColor(
        Colors.get("screenBackground").r,
        Colors.get("screenBackground").g,
        Colors.get("screenBackground").b,
        Colors.get("screenBackground").a
    );

    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    if (Gdx.input.isKeyJustPressed(Input.Keys.UP)) {
      switch (sectionSelected) {
        case ATTRIBUTES:
          if (itemSelected > 0) {
            itemSelected -= 1;

            updateAttributesGroup();
          }

          break;
        case SKILLS:
          if (itemSelected > 0) {
            itemSelected -= 1;

            updateSkillsGroup();
          }

          break;
        case DEFECTS:
          if (itemSelected > 0) {
            itemSelected -= 1;

            updateDefectsGroup();
          }

          break;
        case TRAITS:
          if (itemSelected > 0) {
            itemSelected -= 1;

            updateTraitsGroup();
          }

          break;
        default:
      }
    }

    if (Gdx.input.isKeyJustPressed(Input.Keys.DOWN)) {
      switch (sectionSelected) {
        case ATTRIBUTES:
          if (itemSelected < attributesGroup.getChildren().size - 6) {
            itemSelected += 1;

            updateAttributesGroup();
          }

          break;
        case SKILLS:
          if (itemSelected < skillsGroup.getChildren().size - 6) {
            itemSelected += 1;

            updateSkillsGroup();
          }

          break;
        case DEFECTS:
          if (itemSelected < defectsGroup.getChildren().size - 5) {
            itemSelected += 1;

            updateDefectsGroup();
          }

          break;
        case TRAITS:
          if (itemSelected < traitsGroup.getChildren().size - 5) {
            itemSelected += 1;

            updateTraitsGroup();
          }

          break;
        default:
      }
    }

    if (Gdx.input.isKeyJustPressed(Input.Keys.RIGHT)) {
      switch (sectionSelected) {
        case ATTRIBUTES:
          sectionSelected = Section.SKILLS;
          itemSelected = 0;
          break;
        case SKILLS:
          sectionSelected = Section.DEFECTS;
          itemSelected = 0;
          break;
        case DEFECTS:
          sectionSelected = Section.TRAITS;
          itemSelected = 0;
          break;
        case TRAITS:
          break;
        default:
      }

      updateAttributesGroup();
      updateSkillsGroup();
      updateDefectsGroup();
      updateTraitsGroup();
    }

    if (Gdx.input.isKeyJustPressed(Input.Keys.LEFT)) {
      switch (sectionSelected) {
        case ATTRIBUTES:
          break;
        case SKILLS:
          sectionSelected = Section.ATTRIBUTES;
          itemSelected = 0;
          break;
        case DEFECTS:
          sectionSelected = Section.SKILLS;
          itemSelected = 0;
          break;
        case TRAITS:
          sectionSelected = Section.DEFECTS;
          itemSelected = 0;
          break;
        default:
      }

      updateAttributesGroup();
      updateSkillsGroup();
      updateDefectsGroup();
      updateTraitsGroup();
    }

    if (Gdx.input.isKeyJustPressed(Input.Keys.X)) {
      switch (sectionSelected) {
        case ATTRIBUTES:
          increaseAttribute();
          updateAttributesGroup();
          break;
        case SKILLS:
          increaseSkill();
          updateSkillsGroup();
          break;
        case DEFECTS:
          addDefect();
          updateDefectsGroup();
          updateTraitsGroup();
          break;
        case TRAITS:
          addTrait();
          updateDefectsGroup();
          updateTraitsGroup();
          break;
        default:
      }
    }

    if (Gdx.input.isKeyJustPressed(Input.Keys.Z)) {
      switch (sectionSelected) {
        case ATTRIBUTES:
          decreaseAttribute();
          updateAttributesGroup();
          break;
        case SKILLS:
          decreaseSkill();
          updateSkillsGroup();
          break;
        case DEFECTS:
          removeDefect();
          updateDefectsGroup();
          updateTraitsGroup();
          break;
        case TRAITS:
          removeTrait();
          updateDefectsGroup();
          updateTraitsGroup();
          break;
        default:
      }
    }

    stage.act(delta);
    stage.draw();
  }

  private void updateAttributesGroup() {
    attributesGroup.clear();

    attributesGroup.addActor(new Label("Attributes", Main.skin));
    attributesGroup.addActor(new Label("", Main.skin));

    Label instructions = new Label("[DARK_GRAY]1 point is 1 die upgrade", Main.skin);
    attributesGroup.addActor(instructions);
    Label pointsLeft = new Label(attributePoints + "[LIGHT_GRAY] points left", Main.skin);
    attributesGroup.addActor(pointsLeft);
    attributesGroup.addActor(new Label("", Main.skin));

    attributesGroup.addActor(
        new Label(createAttributeText(0, "Agility", playerSetup.attributes.agility), Main.skin)
    );

    attributesGroup.addActor(
        new Label(createAttributeText(1, "Strength", playerSetup.attributes.strength), Main.skin)
    );

    attributesGroup.addActor(
        new Label(createAttributeText(2, "Toughness", playerSetup.attributes.toughness), Main.skin)
    );
  }

  private void updateSkillsGroup() {
    skillsGroup.clear();

    skillsGroup.addActor(new Label("Skills", Main.skin));
    skillsGroup.addActor(new Label("", Main.skin));

    Label instructions = new Label(
        WordUtils.wrap(
            "[DARK_GRAY]1 point is 1 die upgrade up to the related "
                + "attribute level, above that is 2 points", 70
        ), Main.skin
    );

    skillsGroup.addActor(instructions);
    Label pointsLeft = new Label(skillPoints + "[LIGHT_GRAY] points left", Main.skin);
    skillsGroup.addActor(pointsLeft);
    skillsGroup.addActor(new Label("", Main.skin));

    int index = 0;

    for (Map.Entry<String, Integer> entry : playerSetup.skills.levels.entrySet()) {
      String skill = entry.getKey();
      Integer level = entry.getValue();

      skillsGroup.addActor(new Label(createSkillText(index, skill, level), Main.skin));

      index++;
    }
  }

  private void updateDefectsGroup() {
    defectsGroup.clear();

    defectsGroup.addActor(new Label("Defects", Main.skin));
    defectsGroup.addActor(new Label("", Main.skin));

    Label instructions = new Label(
        WordUtils.wrap(
            "[DARK_GRAY]Taking a major defect gives you 2 points, taking a minor gives you 1", 70
        ), Main.skin
    );

    defectsGroup.addActor(instructions);
    defectsGroup.addActor(new Label("", Main.skin));

    for (int i = 0; i < this.defects.size(); i++) {
      DefectData defectData = this.defects.get(i);

      defectsGroup.addActor(
          new Label(createDefectText(i, defectData), Main.skin)
      );
    }
  }

  private void updateTraitsGroup() {
    traitsGroup.clear();

    traitsGroup.addActor(new Label("Traits", Main.skin));
    traitsGroup.addActor(new Label("", Main.skin));

    Label pointsLeft = new Label(traitPoints + "[LIGHT_GRAY] points left", Main.skin);
    traitsGroup.addActor(pointsLeft);
    traitsGroup.addActor(new Label("", Main.skin));

    for (int i = 0; i < this.traits.size(); i++) {
      TraitData traitData = this.traits.get(i);

      traitsGroup.addActor(
          new Label(createTraitText(i, traitData), Main.skin)
      );
    }
  }

  private String createAttributeText(int index, String name, int level) {
    String details = "";

    switch (name) {
      case "Agility":
        details = "\n[DARK_GRAY]Accuracy: d[LIGHT_GRAY]" + level
            + "[DARK_GRAY], Dodge: d[LIGHT_GRAY]" + level;

        break;
      case "Strength":
        details = "\n[DARK_GRAY]Damage: d[LIGHT_GRAY]" + level;

        break;
      case "Toughness":
        details = "\n[DARK_GRAY]Max Health: [LIGHT_GRAY]" + level * 10
            + "[DARK_GRAY], Max Oxygen: [LIGHT_GRAY]" + level * 4
            + "[DARK_GRAY], Defense: d[LIGHT_GRAY]" + level;

        break;
      default:
    }

    if (sectionSelected == Section.ATTRIBUTES && index == itemSelected) {
      return "[DARK_GRAY]> [WHITE]" + name + " [DARK_GRAY]d[WHITE]" + level + details;
    } else {
      return "[LIGHT_GRAY]" + name + " [DARK_GRAY]d[WHITE]" + level + details;
    }
  }

  private String createSkillText(int index, String name, int level) {
    String capitalizedName = WordUtils.capitalize(name);
    String levelString = level == 0 ? " [WHITE]" + level : " [DARK_GRAY]d[WHITE]" + level;
    String details = "[DARK_GRAY] " + playerSetup.skills.associations.get(name);

    if (sectionSelected == Section.SKILLS && index == itemSelected) {
      return "[DARK_GRAY]> [WHITE]" + capitalizedName + levelString + details;
    } else {
      return "[LIGHT_GRAY]" + capitalizedName + levelString + details;
    }
  }

  private String createDefectText(int index, DefectData defectData) {
    String selected = playerSetup.defects.contains(defectData.name, false) ? "[WHITE]x " : "";
    String type = defectData.reward == 1 ? "Minor" : "Major";
    String desc = WordUtils.wrap(defectData.description, 70);

    boolean hasConflictingTrait = false;

    for (int i = 0; i < playerSetup.traits.size; i++) {
      String trait = playerSetup.traits.get(i);

      if (defectData.cantHave != null && defectData.cantHave.contains(trait)) {
        hasConflictingTrait = true;
      }
    }

    if (hasConflictingTrait) {
      if (sectionSelected == Section.TRAITS && index == itemSelected) {
        return selected + "[DARK_GRAY]> " + type + " " + defectData.name + "\n" + desc;
      } else {
        return selected + "[DARK_GRAY]" + type + " " + defectData.name + "\n" + desc;
      }
    } else if (sectionSelected == Section.DEFECTS && index == itemSelected) {
      return selected + "[DARK_GRAY]> [WHITE]" + type
          + " [WHITE]" + defectData.name + "\n[DARK_GRAY]" + desc;
    } else {
      return selected + "[WHITE]" + type
          + " [LIGHT_GRAY]" + defectData.name + "\n[DARK_GRAY]" + desc;
    }
  }

  private String createTraitText(int index, TraitData traitData) {
    String selected = playerSetup.traits.contains(traitData.name, false) ? "[WHITE]x " : "";
    String type = traitData.cost == 1 ? "Minor" : "Major";
    String desc = WordUtils.wrap(traitData.description, 70);

    boolean hasConflictingDefect = false;

    for (int i = 0; i < playerSetup.defects.size; i++) {
      String defect = playerSetup.defects.get(i);

      if (traitData.cantHave != null && traitData.cantHave.contains(defect)) {
        hasConflictingDefect = true;
      }
    }

    if (hasConflictingDefect) {
      if (sectionSelected == Section.TRAITS && index == itemSelected) {
        return selected + "[DARK_GRAY]> " + type + " " + traitData.name + "\n" + desc;
      } else {
        return selected + "[DARK_GRAY]" + type + " " + traitData.name + "\n" + desc;
      }
    } else if (sectionSelected == Section.TRAITS && index == itemSelected) {
      return selected + "[DARK_GRAY]> [WHITE]"
          + type + " [WHITE]" + traitData.name + "\n[DARK_GRAY]" + desc;
    } else {
      return selected + "[WHITE]"
          + type + " [LIGHT_GRAY]" + traitData.name + "\n[DARK_GRAY]" + desc;
    }
  }

  private void increaseAttribute() {
    if (attributePoints == 0) {
      return;
    }

    switch (itemSelected) {
      case 0:
        int agilityLevel = playerSetup.attributes.agility;

        if (agilityLevel < 12) {
          playerSetup.attributes.agility = agilityLevel == 0 ? 4 : agilityLevel + 2;
          attributePoints -= 1;
        }

        break;
      case 1:
        int strengthLevel = playerSetup.attributes.strength;

        if (strengthLevel < 12) {
          playerSetup.attributes.strength = strengthLevel == 0 ? 4 : strengthLevel + 2;
          attributePoints -= 1;
        }

        break;
      case 2:
        int toughnessLevel = playerSetup.attributes.toughness;

        if (toughnessLevel < 12) {
          playerSetup.attributes.toughness = toughnessLevel == 0 ? 4 : toughnessLevel + 2;
          attributePoints -= 1;
        }

        break;
      default:
    }
  }

  private void decreaseAttribute() {
    switch (itemSelected) {
      case 0:
        int agilityLevel = playerSetup.attributes.agility;

        if (agilityLevel > 0) {
          playerSetup.attributes.agility = agilityLevel == 4 ? 0 : agilityLevel - 2;
          attributePoints += 1;
        }

        break;
      case 1:
        int strengthLevel = playerSetup.attributes.strength;

        if (strengthLevel > 0) {
          playerSetup.attributes.strength = strengthLevel == 4 ? 0 : strengthLevel - 2;
          attributePoints += 1;
        }

        break;
      case 2:
        int toughnessLevel = playerSetup.attributes.toughness;

        if (toughnessLevel > 0) {
          playerSetup.attributes.toughness = toughnessLevel == 4 ? 0 : toughnessLevel - 2;
          attributePoints += 1;
        }

        break;
      default:
    }
  }

  private void increaseSkill() {
    if (skillPoints == 0) {
      return;
    }

    int index = 0;

    for (String key : playerSetup.skills.levels.keySet()) {
      if (index == itemSelected) {
        int level = playerSetup.skills.levels.get(key);

        if (level < 12) {
          playerSetup.skills.levels.put(key, level == 0 ? 4 : level + 2);

          if (level >= playerSetup.attributes.strength) {
            skillPoints -= 2;
          } else {
            skillPoints -= 1;
          }
        }

        return;
      }

      index++;
    }
  }

  private void decreaseSkill() {
    int index = 0;

    for (String key : playerSetup.skills.levels.keySet()) {
      if (index == itemSelected) {
        int level = playerSetup.skills.levels.get(key);

        if (level > 0) {
          playerSetup.skills.levels.put(key, level == 4 ? 0 : level - 2);

          if (level >= playerSetup.attributes.strength) {
            skillPoints += 2;
          } else {
            skillPoints += 1;
          }
        }

        return;
      }

      index++;
    }
  }

  private void addDefect() {
    DefectData defectData = this.defects.get(itemSelected);

    boolean hasConflictingTrait = false;

    for (int i = 0; i < playerSetup.traits.size; i++) {
      String trait = playerSetup.traits.get(i);

      if (defectData.cantHave != null && defectData.cantHave.contains(trait)) {
        hasConflictingTrait = true;
      }
    }

    if (!playerSetup.defects.contains(defectData.name, false) && !hasConflictingTrait) {
      playerSetup.defects.add(defectData.name);
      traitPoints += defectData.reward;
    }
  }

  private void removeDefect() {
    DefectData defectData = this.defects.get(itemSelected);

    if (playerSetup.defects.contains(defectData.name, false)) {
      playerSetup.defects.removeValue(defectData.name, false);
      traitPoints -= defectData.reward;
    }
  }

  private void addTrait() {
    TraitData traitData = this.traits.get(itemSelected);

    boolean hasConflictingDefect = false;

    for (int i = 0; i < playerSetup.defects.size; i++) {
      String defect = playerSetup.defects.get(i);

      if (traitData.cantHave != null && traitData.cantHave.contains(defect)) {
        hasConflictingDefect = true;
      }
    }

    if (!playerSetup.traits.contains(traitData.name, false)
        && traitPoints >= traitData.cost && !hasConflictingDefect) {
      playerSetup.traits.add(traitData.name);
      traitPoints -= traitData.cost;
    }
  }

  private void removeTrait() {
    TraitData traitData = this.traits.get(itemSelected);

    if (playerSetup.traits.contains(traitData.name, false)) {
      playerSetup.traits.removeValue(traitData.name, false);
      traitPoints += traitData.cost;
    }
  }

  @Override
  public void resize(int width, int height) {
    stage.getViewport().update(width, height, true);
  }

  @Override
  public void pause() {

  }

  @Override
  public void resume() {

  }

  @Override
  public void hide() {

  }

  @Override
  public void dispose() {
    stage.dispose();
  }

  private enum Section {
    ATTRIBUTES, SKILLS, TRAITS, DEFECTS
  }
}