package gui.views.game;

import gui.Keys;
import gui.components.MovableComponent;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.sound.sampled.*;
import javax.swing.*;

/**
 * Graphic user interface of the game's main view
 *
 * @author Ignacio Slater Muñoz
 * @version 3.0b11
 * @since 3.0
 */
public class FieldPane extends JPanel {

  /** Each cell is a 128x128 square */
  private final int CELL_SIZE = 128;

  private MovableComponent sprite;

  /**
   * Creates the view for the game field.
   */
  public FieldPane() {
    setupKeyMappings();
    setFocusable(true);
    final String SPRITE_PATH = "resources/sprite.png";
    sprite = new MovableComponent(new Point(0, 0), SPRITE_PATH);
  }

  /**
   * Defines the actions realized when a key is pressed
   */
  private void setupKeyMappings() {
    addActionMapping(KeyStroke.getKeyStroke(Keys.DOWN), new MoveSpriteDownAction());
    addActionMapping(KeyStroke.getKeyStroke(Keys.UP), new MoveSpriteUpAction());
    addActionMapping(KeyStroke.getKeyStroke(Keys.RIGHT), new MoveSpriteRightAction());
    addActionMapping(KeyStroke.getKeyStroke(Keys.LEFT), new MoveSpriteLeftAction());
    addActionMapping(KeyStroke.getKeyStroke(Keys.ENTER), new PlaySoundAction());
  }

  /**
   * Associates a key stroke with an action.
   *
   * @param keyStroke
   *     the pressed key
   * @param action
   *     the action to be performed
   */
  private void addActionMapping(KeyStroke keyStroke, Action action) {
    this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(keyStroke, action.hashCode());
    this.getActionMap().put(action.hashCode(), action);
  }

  @Override
  public void paintComponent(Graphics graphics) {
    super.paintComponent(graphics);
    buildField(graphics);
  }

  /**
   * Builds the field according to the map generated by the controller
   *
   * @param graphics
   *     the object that's going to be displayed
   */
  private void buildField(Graphics graphics) {
    graphics.setColor(new Color(0, 137, 21));
    graphics.fillRect(0, 0, this.getWidth(), this.getHeight());

    ArrayList<MovableComponent> field = new ArrayList<>();

    field.add(sprite);

    for (MovableComponent item : field) {
      graphics.drawImage(item.getSprite(), item.getHorizontalPosition() + 2,
          item.getVerticalPosition() + 2, this);
    }
  }

  /**
   * This class represents the action of playing an audio file.
   * <p>
   * For this to work, the audio file must be of wav format.
   * </p>
   */
  private static class PlaySoundAction extends AbstractAction {

    @Override
    public void actionPerformed(final ActionEvent e) {
      try {
        final String AUDIO_PATH = "resources/prfvr.wav";
        AudioInputStream stream = AudioSystem
            .getAudioInputStream(new File(AUDIO_PATH).getAbsoluteFile());
        Clip clip = AudioSystem.getClip();
        clip.open(stream);
        clip.start();
      } catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
        ex.printStackTrace();
      }
    }
  }

  /**
   * This class represents the action of moving a sprite in any direction
   */
  private abstract class AbstractMoveSpriteAction extends AbstractAction {

    /**
     * Moves the sprite from it's current location.
     *
     * @param horizontalMovement
     *     the distance it's going to move horizontally
     * @param verticalMovement
     *     the distance it's going to move vertically
     */
    protected void moveSprite(int horizontalMovement, int verticalMovement) {
      sprite.move(horizontalMovement, verticalMovement);
      FieldPane.this.repaint();
    }
  }

  /**
   * This class represents the action of moving down the sprite in one cell
   */
  private final class MoveSpriteDownAction extends AbstractMoveSpriteAction {

    @Override
    public void actionPerformed(final ActionEvent actionEvent) {
      moveSprite(0, CELL_SIZE);
    }
  }

  /**
   * This class represents the action of moving up the sprite in one cell
   */
  private final class MoveSpriteUpAction extends AbstractMoveSpriteAction {

    @Override
    public void actionPerformed(final ActionEvent e) {
      moveSprite(0, -CELL_SIZE);
    }
  }

  /**
   * This class represents the action of moving right the sprite in one cell
   */
  private final class MoveSpriteRightAction extends AbstractMoveSpriteAction {

    @Override
    public void actionPerformed(final ActionEvent actionEvent) {
      moveSprite(CELL_SIZE, 0);
    }
  }

  /**
   * This class represents the action of moving left the sprite in one cell
   */
  private final class MoveSpriteLeftAction extends AbstractMoveSpriteAction {

    @Override
    public void actionPerformed(final ActionEvent actionEvent) {
      moveSprite(-CELL_SIZE, 0);
    }
  }
}