/*
 * MIT License
 *
 * Copyright (c) 2018-2019 Daniel Cortes
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package danielcortes.xyz.controllers;

import danielcortes.xyz.data.DAOManager;
import danielcortes.xyz.models.caja.Caja;
import danielcortes.xyz.models.egreso.Egreso;
import danielcortes.xyz.models.tipo_egreso.TipoEgreso;
import danielcortes.xyz.models.tipo_egreso.TipoEgresoToStringWrapper;
import danielcortes.xyz.views.EgresosView;
import danielcortes.xyz.views.components.table_model.EgresosTableModel;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.JComboBox;
import javax.swing.JTable;
import javax.swing.KeyStroke;

/**
 * Controlador el cual esta orientado a manejar la vista de EgresosView Maneja su contenido y las
 * acciones que esta realiza
 */
public class EgresosController extends BaseController {

  private EgresosView view;
  private Caja caja;

  private int editingId;
  private boolean editing;
  private Egreso editingEgreso;

  /**
   * Crea el controlador Al inicial ejecuta: - Metodo que genera los eventos para la vista. - Metodo
   * que llena los tipos de egresos en la vista. - Actualiza el estado de los botones.
   */
  public EgresosController(EgresosView view) {
    this.view = view;
    this.setUpViewEvents();
    this.fillTipoEgresoCombo();
    this.updateButtonsEnabled();
  }

  /**
   * Guarda la caja entregada y actualiza los datos de la tabla de egresos y actualiza el field con
   * el total de egresos.
   */
  public void updateCaja(Caja caja) {
    this.caja = caja;
    this.fillEgresosTable();
    this.updateTotalEgresos();
  }

  /**
   * Rellena el ComboBox con los tipos de egresos disponibles
   */
  private void fillTipoEgresoCombo() {
    JComboBox<TipoEgresoToStringWrapper> tipoCombo = view.getTipoCombo();
    for (TipoEgreso tipoEgreso : DAOManager.getTipoEgresoDAO().getAll()) {
      tipoCombo.addItem(new TipoEgresoToStringWrapper(tipoEgreso));
    }
  }

  /**
   * Rellena la tabla de egresos con los egresos correspondientes a la caja seleccionada
   */
  private void fillEgresosTable() {
    EgresosTableModel egresosTableModel = view.getEgresosTableModel();
    egresosTableModel.removeRows();
    for (Egreso egreso : DAOManager.getEgresoDAO().getByCaja(this.caja)) {
      egresosTableModel.addRow(egreso);
    }
  }

  /**
   * Asigna todos los eventos para la vista de egresos. - Cuando se apreta el boton de guardar o se
   * apreta enter en los fields de descripcion, nro, valor y tipo Se llama al metodo
   * guardarActionListener. - Cuando se apreta el boton de eliminar se llama al metodos
   * eliminarActionListener - Cuando se presiona editar o se realizan 2 clicks en la tabla de
   * egresos se llama a editarActionListener - Cuando se selecciona una fila en la tabla se llama a
   * updateButtonsEnabled
   */
  private void setUpViewEvents() {
    moveTo(this.view.getNroField(), this.view.getDescripcionField());
    moveTo(this.view.getDescripcionField(), this.view.getValorField());
    moveTo(this.view.getValorField(), this.view.getTipoCombo());
    doAction(this.view.getTipoCombo(), "save", KeyStroke.getKeyStroke("ENTER"),
        e -> this.guardarActionListener());

    this.view.getEgresosTable().getSelectionModel()
        .addListSelectionListener(e -> updateButtonsEnabled());
    this.view.getGuardarButton().addActionListener(e -> guardarActionListener());
    this.view.getEliminarButton().addActionListener(e -> eliminarActionListener());
    this.view.getEditarButton().addActionListener(e -> editarActionListener());

    this.view.getEgresosTable().addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent mouseEvent) {
        JTable table = (JTable) mouseEvent.getSource();
        if (mouseEvent.getClickCount() == 2 && table.getSelectedRow() != -1) {
          EgresosController.this.editarActionListener();
        }
      }
    });
  }

  /**
   * Realiza las preparaciones previas a guardar un egreso Primero llama a normalizar los inputs y a
   * ocultar los mensajes de error Luego si es que esta colocada la flag de editing se llama al
   * metodo editarEgreso y si no, se llama a guardarEgreso Al terminar esto, se llama a resetear el
   * focus en los inputs y a actualizar el total de egresos
   */
  private void guardarActionListener() {
    this.normalizeInputs();
    this.hideErrorMessages();

    String nro = this.view.getNroField().getText();
    String descripcion = this.view.getDescripcionField().getText();
    int valor = this.view.getValorField().getValue();
    TipoEgreso tipo = (TipoEgreso) this.view.getTipoCombo().getSelectedItem();

    if (editing) {
      this.editarEgreso(nro, descripcion, valor, tipo, this.caja);
    } else {
      this.guardarEgreso(nro, descripcion, valor, tipo, this.caja);
    }
    this.resetFocus();
  }

  /**
   * Realiza las acciones necesarias para eliminar un egreso Obtiene el egreso seleccionado y lo
   * elimina, luego llama a actualizar el total de egresos y a actualizar el estado de los botones.
   */
  private void eliminarActionListener() {
    int selectedID = this.view.getEgresosTable().getSelectedRow();
    if (selectedID >= 0) {
      Egreso egreso = this.view.getEgresosTableModel().getEgreso(selectedID);
      this.view.getEgresosTableModel().removeRow(selectedID);
      DAOManager.getEgresoDAO().delete(egreso);
      this.updateTotalEgresos();
      this.updateButtonsEnabled();
      this.resetFocus();
    }
  }

  /**
   * Realiza lo necesario para comenzar a editar un egreso Llama a esconder los mensajes de error.
   * Guarda globalmente en la clase el egreso que se esta editando, su id y una flag indicando que
   * se esta en modo editar. Ademas rellena los campos de input con los valores del egreso que se
   * esta editando.
   */
  private void editarActionListener() {
    this.hideErrorMessages();

    int selectedID = this.view.getEgresosTable().getSelectedRow();
    int selectedModelID = this.view.getEgresosTable().getRowSorter()
        .convertRowIndexToModel(selectedID);
    if (selectedModelID >= 0) {
      Egreso egreso = this.view.getEgresosTableModel().getEgreso(selectedModelID);

      this.editingId = selectedModelID;
      this.editingEgreso = egreso;
      this.editing = true;

      this.view.getNroField().setText(egreso.getNro());
      this.view.getDescripcionField().setText(egreso.getDescripcion());
      this.view.getValorField().setValue(egreso.getValor());
      this.view.getTipoCombo().setSelectedItem(egreso.getTipoEgreso());
    }
  }

  /**
   * Obtiene el total de egresos y los coloca en el campo de totalEgresosField.
   */
  private void updateTotalEgresos() {
    int total = DAOManager.getEgresoDAO().getTotalEgreso(this.caja);
    this.view.getTotalEgresosField().setValue(total);
  }

  /**
   * Cuando se tiene seleccionada una fila de la tabla activa los botones de eliminar y editar Si no
   * esta seleccionada los desactiva
   */
  private void updateButtonsEnabled() {
    if (this.view.getEgresosTable().getSelectedRow() >= 0) {
      this.view.getEliminarButton().setEnabled(true);
      this.view.getEditarButton().setEnabled(true);
    } else {
      this.view.getEliminarButton().setEnabled(false);
      this.view.getEditarButton().setEnabled(false);
    }
  }

  /**
   * Guarda un egreso tras llamar a validar su input Luego de guardar, agrega el egreso a la tabla,
   * llama a actualizar el total de egresos y llama a limpiar a los inputs
   */
  private void guardarEgreso(String nro, String descripcion, int valor, TipoEgreso tipo,
      Caja caja) {
    if (this.validateInput(nro, descripcion, tipo, caja)) {
      Egreso egreso = new Egreso();
      egreso.setValor(valor);
      egreso.setDescripcion(descripcion);
      egreso.setNro(nro);
      egreso.setTipoEgreso(tipo);
      egreso.setCaja(caja);
      DAOManager.getEgresoDAO().insert(egreso);
      this.view.getEgresosTableModel().addRow(egreso);
      this.updateTotalEgresos();
      this.clearInputs();
    }
  }

  /**
   * Actualiza un egreso tras llamar a validar su input Tras esto actualiza el egreso en la tabla,
   * llama a actualizar el total de egresos y a limpiar los inputs Finalmente setea la flag editing
   * a false
   */
  private void editarEgreso(String nro, String descripcion, int valor, TipoEgreso tipo, Caja caja) {
    if (this.validateInput(nro, descripcion, tipo, caja)) {
      this.editingEgreso.setValor(valor);
      this.editingEgreso.setDescripcion(descripcion);
      this.editingEgreso.setNro(nro);
      this.editingEgreso.setTipoEgreso(tipo);
      DAOManager.getEgresoDAO().update(this.editingEgreso);
      this.view.getEgresosTableModel().setEgreso(this.editingId, this.editingEgreso);
      this.updateTotalEgresos();
      this.clearInputs();
      this.editing = false;
    }
  }

  /**
   * llama a los metodos necesarios para validar los inputs entregados
   *
   * @return true cuando todas las validaciones retoran true, si no, false
   */
  private boolean validateInput(String nro, String descripcion, TipoEgreso tipoEgreso, Caja caja) {

    boolean nroValidation = this.validateNro(nro);
    boolean descripcionValidation = this.validateDescripcion(descripcion);
    boolean tipoEgresoValidation = this.validateTipoEgreso(tipoEgreso);
    boolean cajaValidation = this.validateCaja(caja);

    return nroValidation && descripcionValidation && tipoEgresoValidation;
  }

  /**
   * Valida la variable nro contra los casos - Es null - Esta vacio Cuando el primer caso sea true,
   * colocara un mensaje de error correspondiente en el jlabel correspondiente
   *
   * @return Si cualquiera de estos casos son true se retornara false, si no, se retorna true
   */
  private boolean validateNro(String nro) {
    if (nro == null) {
      this.view.getErrorNumero().setText("Hubo un problema con los datos");
      this.view.getErrorNumero().setVisible(true);
      return false;
    }

    nro = nro.trim();
    if (nro.isEmpty()) {
      this.view.getErrorNumero().setText("El campo esta vacio");
      this.view.getErrorNumero().setVisible(true);
      return false;
    }
    return true;

  }

  /**
   * Valida la variable descripcion contra los casos - Es null - Esta vacio Cuando el primer caso
   * sea true, colocara un mensaje de error correspondiente en el jlabel correspondiente
   *
   * @return Si cualquiera de estos casos son true se retornara false, si no, se retorna true
   */
  private boolean validateDescripcion(String descripcion) {
    if (descripcion == null) {
      this.view.getErrorDescripcion().setText("Hubo un problema con los datos");
      this.view.getErrorDescripcion().setVisible(true);
      return false;
    }

    if (descripcion.isEmpty()) {
      this.view.getErrorDescripcion().setText("El campo esta vacio");
      this.view.getErrorDescripcion().setVisible(true);
      return false;
    }
    return true;
  }

  /**
   * Valida la variable tipoEgreso contra los casos - Es null Cuando este caso sea true, colocara un
   * mensaje de error correspondiente en el jlabel correspondiente
   *
   * @return Si este caso es  true se retornara false, si no, se retorna true
   */
  private boolean validateTipoEgreso(TipoEgreso tipoEgreso) {
    if (tipoEgreso == null) {
      this.view.getErrorTipoEgreso().setText("Hubo un problema con los datos");
      this.view.getErrorTipoEgreso().setVisible(true);
      return false;
    }
    return true;
  }

  /**
   * Valida la variable caja contra los casos - Es null
   *
   * @return Si este caso es  true se retornara false, si no, se retorna true
   */
  private boolean validateCaja(Caja caja) {
    return caja != null;
  }

  /**
   * Esconde los mensajes de error en la ventana de egresos
   */
  private void hideErrorMessages() {
    this.view.getErrorTipoEgreso().setVisible(false);
    this.view.getErrorDescripcion().setVisible(false);
    this.view.getErrorNumero().setVisible(false);
  }

  /**
   * Vacia los campos de texto y selecciona la primera opcion en el jcombobox
   */
  private void clearInputs() {
    this.view.getTipoCombo().setSelectedIndex(0);
    this.view.getNroField().setText("");
    this.view.getValorField().setValue(0);
    this.view.getValorField().setText("");
    this.view.getDescripcionField().setText("");
  }

  /**
   * Ejecuta trim sobre todos los campos de texto
   */
  private void normalizeInputs() {
    this.view.getNroField().setText(this.view.getNroField().getText().trim());
    this.view.getDescripcionField().setText(this.view.getDescripcionField().getText().trim());

  }

  /**
   * Setea el focus en el campo nroField
   */
  private void resetFocus() {
    this.view.getNroField().requestFocus();
  }

  private class GuardarAction extends AbstractAction {

    EgresosController controller;

    GuardarAction(EgresosController controller) {
      this.controller = controller;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      this.controller.guardarActionListener();
    }
  }
}
