/*
 * 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.ingreso.Ingreso;
import danielcortes.xyz.models.tipo_ingreso.TipoIngreso;
import danielcortes.xyz.models.tipo_ingreso.TipoIngresoToStringWrapper;
import danielcortes.xyz.views.IngresosView;
import danielcortes.xyz.views.components.table_model.IngresosTableModel;
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.JComponent;
import javax.swing.JTable;
import javax.swing.KeyStroke;

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

  private IngresosView view;
  private Caja caja;

  private int editingId;
  private Ingreso editingIngreso;
  private boolean editing;

  /**
   * Crea el controlado de egresos, el cual esta acoplado con la vista de ingresos, controlando el
   * estado y contenido de esta. Al iniciarse ejecuta. - Metodo que llena el combobox de tipos de
   * ingreso - Metodo que genera los eventos para la vista - Metodo que actualiza el estado de los
   * botones
   */
  public IngresosController(IngresosView view) {
    this.view = view;
    this.fillTipoIngresoCombo();
    this.setupViewEvents();
    this.updateButtonsEnabled();
  }


  /**
   * Guarda la caja ingresada y actualiza el contenido de la tabla de ingresos y el campo de total
   * de ingresos
   */
  public void updateCaja(Caja caja) {
    this.caja = caja;
    this.fillIngresosTable();
    this.updateTotalIngresos();
  }

  /**
   * LLena el combobox de tipos de ingresos
   */
  private void fillTipoIngresoCombo() {
    JComboBox<TipoIngresoToStringWrapper> tipoCombo = this.view.getTipoCombo();
    for (TipoIngreso tipo : DAOManager.getTipoIngresoDAO().getAll()) {
      tipoCombo.addItem(new TipoIngresoToStringWrapper(tipo));
    }
  }

  /**
   * Llena la tabla de ingresos con los ingresos pertenecientes a la caja guarda
   */
  private void fillIngresosTable() {
    IngresosTableModel ingresosTableModel = this.view.getIngresosTableModel();
    ingresosTableModel.removeRows();
    for (Ingreso ingreso : DAOManager.getIngresoDAO().getByCaja(this.caja)) {
      ingresosTableModel.addRow(ingreso);
    }
  }

  /**
   * Genera los eventos para los distintos componentes de la vista - Cuando se presiona el boton de
   * guardar o se apreta enter en los fields de valor, nro inicial, nro final y tipo se llama a
   * guardarActionListener - Cuando se presiona el boton de eliminar se llama al
   * eliminarActionListener - Cuando se selecciona una fila en la tabla se llama a
   * updateButtonsEnabled - Cuando se presiona el boton de editar o se hace doble click sobre una
   * fila de la tabla se llama a editarActionListener
   */
  private void setupViewEvents() {
    moveTo(this.view.getValorField(), this.view.getNroZInicialField());
    moveTo(this.view.getNroZInicialField(), this.view.getNroZFinalField());
    moveTo(this.view.getNroZFinalField(), this.view.getNroInicialField());
    moveTo(this.view.getNroInicialField(), this.view.getNroFinalField());
    moveTo(this.view.getNroFinalField(), this.view.getTipoCombo());
    doAction(this.view.getTipoCombo(), "save", KeyStroke.getKeyStroke("ENTER"),
        e -> this.guardarActionListener());

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

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

  /**
   * Realiza las preparaciones previas a guardar un ingreso Primero llama a normalizar los inputs y
   * a esconder los mensajes de error Luego dependiendo si se tiene la flag editing en true o false
   * se llama a editar ingreso o a guardarlo Tras terminar esto se llama a resetear el focus.
   */
  private void guardarActionListener() {
    this.normalizeInputs();
    this.hideErrorMessages();

    int valor = this.view.getValorField().getValue();
    String nroZInicial = this.view.getNroZInicialField().getText();
    String nroZFinal = this.view.getNroZFinalField().getText();
    String nroInicial = this.view.getNroInicialField().getText();
    String nroFinal = this.view.getNroFinalField().getText();
    TipoIngreso tipoIngreso = (TipoIngreso) this.view.getTipoCombo().getSelectedItem();

    if (editing) {
      this.editarIngreso(valor, nroZInicial, nroZFinal, nroInicial, nroFinal, tipoIngreso,
          this.caja);
    } else {
      this.guardarIngreso(valor, nroZInicial, nroZFinal, nroInicial, nroFinal, tipoIngreso,
          this.caja);
    }
    this.resetFocus();
  }

  /**
   * Realiza las acciones necesarias para eliminar un ingreso Solo lo va a realizar si es que esta
   * seleccionada una fila de la tabla, se eliminara el ingreso seleccionado Una vez eliminado se
   * llama a actualizar el total de ingresos y  el estado de los botones
   */
  private void eliminarActionListener() {
    int selectedId = this.view.getIngresosTable().getSelectedRow();
    if (selectedId >= 0) {
      Ingreso ingreso = this.view.getIngresosTableModel().getIngreso(selectedId);
      this.view.getIngresosTableModel().removeRow(selectedId);
      DAOManager.getIngresoDAO().delete(ingreso);
      this.updateTotalIngresos();
      this.updateButtonsEnabled();
      this.resetFocus();
    }
  }

  /**
   * Realiza las preparaciones previas a editar un ingreso Primero llama a esconder los mensajes de
   * error. Guarda globlarmente el ingreso a ser editar, el id de este y una flag que indica que se
   * esta en modo de editar. Finalmente llena los campos de inputs con los datos del ingreso a
   * editar.
   */
  private void editarActionListener() {
    this.hideErrorMessages();

    int selectedID = this.view.getIngresosTable().getSelectedRow();
    int selectedModelID = this.view.getIngresosTable().getRowSorter()
        .convertRowIndexToModel(selectedID);
    if (selectedModelID >= 0) {
      Ingreso ingreso = this.view.getIngresosTableModel().getIngreso(selectedModelID);

      this.editingId = selectedModelID;
      this.editingIngreso = ingreso;
      this.editing = true;

      this.view.getTipoCombo().setSelectedItem(ingreso.getTipoIngreso());
      this.view.getValorField().setValue(ingreso.getValor());
      this.view.getNroZInicialField().setText(String.valueOf(ingreso.getNroZInicial()));
      this.view.getNroZFinalField().setText(String.valueOf(ingreso.getNroZFinal()));
      this.view.getNroInicialField().setText(String.valueOf(ingreso.getNroInicial()));
      this.view.getNroFinalField().setText(String.valueOf(ingreso.getNroFinal()));
    }
  }

  /**
   * Obtiene el total de ingresos de la caja y lo coloca en el el field totalingresos
   */
  private void updateTotalIngresos() {
    int total = DAOManager.getIngresoDAO().getTotalIngreso(this.caja);
    this.view.getTotalIngresoField().setValue(total);
  }

  /**
   * Actualiza si los botones estan habilitados Esto depende de si se encuentra al menos una fila en
   * la tabla seleccionada Si es asi, son habilidatos, si no, de deshabilitan
   */
  private void updateButtonsEnabled() {
    if (this.view.getIngresosTable().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 ingreso tras llamar a validar el input Luego de guardar agrega a la tabla el ingreso,
   * llama a limpiar los campos de input y a actualizar el total de ingresos
   */
  private void guardarIngreso(int valor, String nroZInicial, String nroZFinal, String nroInicial,
      String nroFinal, TipoIngreso tipoIngreso, Caja caja) {
    if (this.validateInput(nroZInicial, nroZFinal, nroInicial, nroFinal, tipoIngreso, caja)) {
      Ingreso ingreso = new Ingreso();
      ingreso.setTipoIngreso(tipoIngreso);
      ingreso.setCaja(caja);
      ingreso.setValor(valor);
      ingreso.setNroZInicial(nroZInicial);
      ingreso.setNroZFinal(nroZFinal);
      ingreso.setNroInicial(nroInicial);
      ingreso.setNroFinal(nroFinal);

      DAOManager.getIngresoDAO().insert(ingreso);
      this.view.getIngresosTableModel().addRow(ingreso);

      this.clearInputs();
      this.updateTotalIngresos();
    }
  }

  /**
   * Edita el ingreso tras llamar a validar el input Tras esto actualiza el ingreso en la tabla,
   * llama a actualizar el total de ingresos, a limpiar los campos de input y a desactivar la flag
   * de editing.
   */
  private void editarIngreso(int valor, String nroZInicial, String nroZFinal, String nroInicial,
      String nroFinal, TipoIngreso tipoIngreso, Caja caja) {
    if (this.validateInput(nroZInicial, nroZFinal, nroInicial, nroFinal, tipoIngreso, caja)) {
      this.editingIngreso.setTipoIngreso(tipoIngreso);
      this.editingIngreso.setValor(valor);
      this.editingIngreso.setNroZInicial(nroZInicial);
      this.editingIngreso.setNroZFinal(nroZFinal);
      this.editingIngreso.setNroInicial(nroInicial);
      this.editingIngreso.setNroFinal(nroFinal);
      DAOManager.getIngresoDAO().update(this.editingIngreso);
      this.view.getIngresosTableModel().setIngreso(this.editingId, this.editingIngreso);
      this.updateTotalIngresos();
      this.clearInputs();
      this.editing = false;
    }
  }

  /**
   * Llama a los metodos necesarios para validar el input
   *
   * @return true cuando todas las validaciones retoran true, si no, false
   */
  private boolean validateInput(String nroZInicial, String nroZFinal, String nroInicial,
      String nroFinal, TipoIngreso tipoIngreso, Caja caja) {
    this.hideErrorMessages();

    boolean nroInicialValidation = this.validateNroInicial(nroInicial);
    boolean nroFinalValidation = this.validateNroFinal(nroFinal);
    boolean tipoIngresoValidation = this.validateTipoIngreso(tipoIngreso);
    boolean cajaValidation = this.validateCaja(caja);

    return nroInicialValidation && nroFinalValidation && tipoIngresoValidation && cajaValidation;
  }

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

  /**
   * Valida la variable nroInicial 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 validateNroInicial(String nroInicial) {
    if (nroInicial == null) {
      this.view.getErrorNroInicial().setText("Hubo un problema con los datos");
      this.view.getErrorNroInicial().setVisible(true);
      return false;
    }

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

  /**
   * Valida la variable nroFinal 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 validateNroFinal(String nroFinal) {
    if (nroFinal == null) {
      this.view.getErrorNroFinal().setText("Hubo un problema con los datos");
      this.view.getErrorNroFinal().setVisible(true);
      return false;
    }

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

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

  /**
   * Esconde los mensajes de error en la ventana de ingresos
   */
  private void hideErrorMessages() {
    this.view.getErrorTipoIngreso().setVisible(false);
    this.view.getErrorNroInicial().setVisible(false);
    this.view.getErrorNroFinal().setVisible(false);
  }

  /**
   * Vacia los jtextfields y selecciona la primera opcion del jcombobox
   */
  private void clearInputs() {
    this.view.getTipoCombo().setSelectedIndex(0);
    this.view.getValorField().setValue(0);
    this.view.getValorField().setText("");
    this.view.getNroZInicialField().setText("");
    this.view.getNroZFinalField().setText("");
    this.view.getNroInicialField().setText("");
    this.view.getNroFinalField().setText("");
  }

  /**
   * Ejecuta un trim sobre todos los jtextfield
   */
  private void normalizeInputs() {
    this.view.getNroZInicialField().setText(this.view.getNroZInicialField().getText().trim());
    this.view.getNroZFinalField().setText(this.view.getNroZFinalField().getText().trim());
    this.view.getNroInicialField().setText(this.view.getNroInicialField().getText().trim());
    this.view.getNroFinalField().setText(this.view.getNroFinalField().getText().trim());
  }

  /**
   * Le pide focus al tipo combo
   */
  private void resetFocus() {
    this.view.getValorField().requestFocus();
  }

  private class NextAction extends AbstractAction {

    JComponent next;

    NextAction(JComponent next) {
      this.next = next;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      this.next.requestFocus();
    }
  }

  private class GuardarAction extends AbstractAction {

    IngresosController controller;

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

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