/**********************************************************************
 * Copyright (c) by Heiner Jostkleigrewe
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * <p>
 *  This program is distributed in the hope that it will be useful,  but WITHOUT ANY WARRANTY; without
 *  even the implied warranty of  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 *  the GNU General Public License for more details.
 * <p>
 * You should have received a copy of the GNU General Public License along with this program.  If not,
 * see <http://www.gnu.org/licenses/>.
 * <p>
 * heiner@jverein.de
 * www.jverein.de
 **********************************************************************/
package de.jost_net.JVerein.gui.control;

import java.rmi.RemoteException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import de.jost_net.JVerein.Einstellungen;
import de.jost_net.JVerein.Einstellungen.Property;
import de.jost_net.JVerein.DBTools.DBTransaction;
import de.jost_net.JVerein.gui.action.EditAction;
import de.jost_net.JVerein.gui.control.WirtschaftsplanNode.Type;
import de.jost_net.JVerein.gui.menu.WirtschaftsplanListMenu;
import de.jost_net.JVerein.gui.parts.EditTreePart;
import de.jost_net.JVerein.gui.parts.JVereinTablePart;
import de.jost_net.JVerein.gui.parts.WirtschaftsplanUebersichtPart;
import de.jost_net.JVerein.gui.view.WirtschaftsplanDetailView;
import de.jost_net.JVerein.keys.BuchungsartSort;
import de.jost_net.JVerein.keys.VorlageTyp;
import de.jost_net.JVerein.rmi.Buchungsklasse;
import de.jost_net.JVerein.rmi.JVereinDBObject;
import de.jost_net.JVerein.rmi.Projekt;
import de.jost_net.JVerein.rmi.Wirtschaftsplan;
import de.jost_net.JVerein.rmi.WirtschaftsplanItem;
import de.jost_net.JVerein.server.WirtschaftsplanImpl;
import de.jost_net.JVerein.util.JVDateFormatTTMMJJJJ;
import de.jost_net.JVerein.util.VorlageUtil;
import de.willuhn.datasource.BeanUtil;
import de.willuhn.datasource.GenericIterator;
import de.willuhn.datasource.pseudo.PseudoIterator;
import de.willuhn.datasource.rmi.DBIterator;
import de.willuhn.datasource.rmi.DBService;
import de.willuhn.jameica.gui.AbstractView;
import de.willuhn.jameica.gui.GUI;
import de.willuhn.jameica.gui.formatter.CurrencyFormatter;
import de.willuhn.jameica.gui.formatter.DateFormatter;
import de.willuhn.jameica.gui.input.SelectInput;
import de.willuhn.jameica.gui.parts.TreePart;
import de.willuhn.jameica.gui.parts.table.Feature;
import de.willuhn.jameica.gui.parts.table.Feature.Context;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;

public class WirtschaftsplanControl extends VorZurueckControl implements Savable
{
  public final static String AUSWERTUNG_CSV = ".csv";

  private JVereinTablePart wirtschaftsplaene;

  private EditTreePart einnahmen;

  private EditTreePart ausgaben;

  private EditTreePart ruecklagen;

  private WirtschaftsplanUebersichtPart uebersicht;

  private Wirtschaftsplan wirtschaftsplan;

  private SelectInput projekt;

  private boolean tableChanged = false;

  /**
   * Erzeugt einen neuen WirtschaftsplanControl fuer die angegebene View.
   *
   * @param view
   *          die View, fuer die dieser WirtschaftsplanControl zustaendig ist.
   * @throws RemoteException
   */
  public WirtschaftsplanControl(AbstractView view) throws RemoteException
  {
    super(view);
  }

  /**
   * Liefert die Liste der Wirtschaftsplaene.
   *
   * @return die Liste der Wirtschaftsplaene.
   * @throws RemoteException
   *           wenn ein Fehler beim Zugriff auf die Datenbank auftritt.
   */
  @Override
  public JVereinTablePart getTablePart() throws RemoteException
  {
    if (wirtschaftsplaene != null)
    {
      return wirtschaftsplaene;
    }

    DBService service = Einstellungen.getDBService();

    wirtschaftsplaene = new JVereinTablePart(
        service.createList(Wirtschaftsplan.class), null);

    CurrencyFormatter formatter = new CurrencyFormatter("",
        Einstellungen.DECIMALFORMAT);
    DateFormatter dateFormatter = new DateFormatter(new JVDateFormatTTMMJJJJ());

    wirtschaftsplaene.addColumn("ID", "id");
    wirtschaftsplaene.addColumn("Bezeichnung", "bezeichnung");
    wirtschaftsplaene.addColumn("Von", "datum_von", dateFormatter);
    wirtschaftsplaene.addColumn("Bis", "datum_bis", dateFormatter);
    wirtschaftsplaene.addColumn("Einnahmen Soll", "planEinnahme", formatter);
    wirtschaftsplaene.addColumn("Ausgaben Soll", "planAusgabe", formatter);
    wirtschaftsplaene.addColumn("Saldo Soll", "planSaldo", formatter);
    wirtschaftsplaene.addColumn("Einnahmen Ist", "istPlus", formatter);
    wirtschaftsplaene.addColumn("Ausgaben Ist", "istMinus", formatter);
    wirtschaftsplaene.addColumn("Saldo Ist", "istSaldo", formatter);
    wirtschaftsplaene.addColumn("Saldo Differenz", "differenz", formatter);

    wirtschaftsplaene
        .setContextMenu(new WirtschaftsplanListMenu(wirtschaftsplaene));
    wirtschaftsplaene.setAction(
        new EditAction(WirtschaftsplanDetailView.class, wirtschaftsplaene));
    VorZurueckControl.setObjektListe(null, null);
    wirtschaftsplaene.setMulti(true);
    return wirtschaftsplaene;
  }

  /**
   * Liefert den aktuellen Wirtschaftsplan.
   *
   * @return der aktuelle Wirtschaftsplan oder null, wenn kein Wirtschaftsplan
   *         geladen ist.
   */
  public Wirtschaftsplan getWirtschaftsplan()
  {
    if (wirtschaftsplan != null)
    {
      return wirtschaftsplan;
    }
    wirtschaftsplan = (Wirtschaftsplan) getCurrentObject();
    return wirtschaftsplan;
  }

  public SelectInput getProjekt() throws RemoteException
  {
    if (projekt != null)
    {
      return projekt;
    }
    if (getWirtschaftsplan().isNewObject())
    {
      projekt = new SelectInput(new Object[0], null);
      projekt.setPleaseChoose("Zur Auswahl erst Speichern");
      projekt.disable();
    }
    else
    {
      projekt = new SelectInput(getProjektliste(),
          getWirtschaftsplan().getProjekt());
      projekt.setPleaseChoose("Keine Einschränkung");
    }
    projekt.setAttribute("bezeichnung");
    return projekt;
  }

  @SuppressWarnings("unchecked")
  private List<Projekt> getProjektliste() throws RemoteException
  {
    DBIterator<Projekt> list = Einstellungen.getDBService()
        .createList(Projekt.class);
    // Nur Projekte anzeigen die in dem Zeitraum aktiv sind
    list.addFilter("(startdatum is null or startdatum <= ?)",
        getWirtschaftsplan().getDatumBis());
    list.addFilter("(endedatum is null or endedatum >= ?)",
        getWirtschaftsplan().getDatumVon());
    list.setOrder("ORDER BY bezeichnung");
    List<Projekt> finallist = PseudoIterator.asList(list);
    Projekt projekt = getWirtschaftsplan().getProjekt();
    if (finallist != null && projekt != null)
    {
      boolean found = false;
      for (Projekt p : finallist)
      {
        if (BeanUtil.equals(p, projekt))
        {
          found = true;
          break;
        }
      }
      if (!found)
      {
        finallist.add(projekt);
      }
    }
    return finallist;
  }

  public EditTreePart getEinnahmen() throws RemoteException
  {
    if (einnahmen == null)
    {
      einnahmen = generateTree(WirtschaftsplanImpl.EINNAHME);
    }
    else
    {
      @SuppressWarnings("rawtypes")
      List items = einnahmen.getItems();
      einnahmen.setList(items);
    }
    return einnahmen;
  }

  public EditTreePart getAusgaben() throws RemoteException
  {
    if (ausgaben == null)
    {
      ausgaben = generateTree(WirtschaftsplanImpl.AUSGABE);
    }
    else
    {
      @SuppressWarnings("rawtypes")
      List items = ausgaben.getItems();
      ausgaben.setList(items);
    }
    return ausgaben;
  }

  public EditTreePart getRuecklagen() throws RemoteException
  {
    if (ruecklagen == null)
    {
      ruecklagen = generateTree(WirtschaftsplanImpl.RUECKLAGE);
    }
    else
    {
      @SuppressWarnings("rawtypes")
      List items = ruecklagen.getItems();
      ruecklagen.setList(items);
    }
    return ruecklagen;
  }

  @SuppressWarnings("unchecked")
  private EditTreePart generateTree(int art) throws RemoteException
  {
    Wirtschaftsplan wirtschaftsplan = getWirtschaftsplan();

    if (wirtschaftsplan == null)
    {
      return null;
    }

    ArrayList<WirtschaftsplanNode> nodes = new ArrayList<>();

    DBService service = Einstellungen.getDBService();

    DBIterator<Buchungsklasse> buchungsklasseIterator = service
        .createList(Buchungsklasse.class);
    switch ((Integer) Einstellungen.getEinstellung(Property.BUCHUNGSARTSORT))
    {
      case BuchungsartSort.NACH_NUMMER:
        buchungsklasseIterator.setOrder("Order by nummer is null, nummer");
        break;
      case BuchungsartSort.NACH_BEZEICHNUNG:
      default:
        buchungsklasseIterator
            .setOrder("Order by bezeichnung is NULL, bezeichnung");
        break;
    }
    while (buchungsklasseIterator.hasNext())
    {
      Buchungsklasse klasse = buchungsklasseIterator.next();
      nodes.add(new WirtschaftsplanNode(klasse, art, wirtschaftsplan));
    }

    EditTreePart treePart = new EditTreePart(nodes, null)
    {
      @Override
      protected Context createFeatureEventContext(Feature.Event e, Object data)
      {
        Context ctx = super.createFeatureEventContext(e, data);
        if (e.equals(Feature.Event.PAINT))
        {
          autoExpand(this);
        }
        if (e.equals(Feature.Event.REFRESH))
        {
          // Listener ausführen um das Icon zu aktualisieren
          Tree tree = (Tree) ctx.control;
          for (TreeItem klasseItem : tree.getItems())
          {
            for (TreeItem item : klasseItem.getItems())
            {
              Event event = new Event();
              event.item = item;

              tree.notifyListeners(
                  item.getExpanded() ? SWT.Expand : SWT.Collapse, event);
            }
          }
        }
        return ctx;
      }
    };

    CurrencyFormatter formatter = new CurrencyFormatter("",
        Einstellungen.DECIMALFORMAT);
    treePart.addColumn("Buchungsklasse / Buchungsart / Posten",
        "buchungsklassebezeichnung", null, true);
    treePart.addColumn("Soll", "soll", formatter, true);
    treePart.addColumn("Ist", "ist", formatter);

    treePart.addChangeListener(((object, attribute, newValue) -> {
      if (!(object instanceof WirtschaftsplanNode))
      {
        throw new ApplicationException("Fehler!");
      }

      WirtschaftsplanNode node = (WirtschaftsplanNode) object;

      WirtschaftsplanItem item = node.getWirtschaftsplanItem();

      if (item == null)
      {
        throw new ApplicationException("Fehler beim Bearbeiten!");
      }
      try
      {
        switch (attribute)
        {
          case "buchungsklassebezeichnung":
            item.setPosten(newValue);
            break;
          case "soll":
            try
            {
              NumberFormat nf = NumberFormat.getInstance(Locale.getDefault());
              item.setSoll(nf.parse(newValue).doubleValue());
            }
            catch (NumberFormatException | ParseException e)
            {
              GUI.getStatusBar().setErrorText(
                  "Bitte gib eine gültige Zahl im deutschen Format ein!");
              throw new ApplicationException("Keine Zahl eingegeben");
            }
            break;
          default:
            throw new ApplicationException("Fehler!");
        }

        node.setWirtschaftsplanItem(item);
        node.setSoll(item.getSoll());

        GenericIterator<WirtschaftsplanNode> children = node.getChildren();
        if (node.getType() == Type.BUCHUNGSART && children != null
            && children.size() == 1)
        {
          WirtschaftsplanNode child = children.next();
          child.setWirtschaftsplanItem(item);
          child.setSoll(item.getSoll());
          reloadSoll(node);
        }
        else
        {
          WirtschaftsplanNode parent = (WirtschaftsplanNode) node.getParent();
          reloadSoll(parent);
        }

        tableChanged = true;
      }
      catch (RemoteException e)
      {
        Logger.error("Fehler", e);
        throw new ApplicationException("Fehler!");
      }
    }));

    treePart.addEditListener(((object, attribute) -> {
      WirtschaftsplanNode node = (WirtschaftsplanNode) object;

      switch (node.getType())
      {
        case POSTEN:
          return true;
        case BUCHUNGSART:
          try
          {
            // Wenn nur ein Posten existiert, kann direkt bei der Buchungsart
            // der Betrag eingegeben werden.
            return node.getChildren().size() == 1 && attribute.equals("soll");
          }
          catch (RemoteException e)
          {
            return false;
          }
        default:
          return false;
      }

    }));

    treePart.setRememberColWidths(true);
    return treePart;
  }

  @SuppressWarnings("unchecked")
  private void autoExpand(TreePart tree)
  {
    try
    {
      for (WirtschaftsplanNode klasseNode : (List<WirtschaftsplanNode>) tree
          .getItems())
      {
        GenericIterator<WirtschaftsplanNode> children = klasseNode
            .getChildren();
        if (children == null)
        {
          continue;
        }
        while (children.hasNext())
        {
          WirtschaftsplanNode node = children.next();
          tree.setExpanded(node, node.getChildren().size() != 1);
        }
      }
      tree.featureEvent(Feature.Event.REFRESH, null);
    }
    catch (RemoteException e1)
    {
      Logger.error("Fehler beim AutoExpand", e1);
    }
  }

  public WirtschaftsplanUebersichtPart getUebersicht()
  {
    return uebersicht;
  }

  public void setUebersicht(WirtschaftsplanUebersichtPart uebersicht)
  {
    this.uebersicht = uebersicht;
  }

  public void reloadSoll(WirtschaftsplanNode parent)
      throws RemoteException, ApplicationException
  {
    if (parent.getType() == WirtschaftsplanNode.Type.BUCHUNGSKLASSE)
    {
      @SuppressWarnings("rawtypes")
      GenericIterator outerIterator = parent.getChildren();
      double klasseSoll = 0;
      while (outerIterator.hasNext())
      {
        WirtschaftsplanNode child = (WirtschaftsplanNode) outerIterator.next();
        double artSoll = 0;
        @SuppressWarnings("rawtypes")
        GenericIterator innerIterator = child.getChildren();
        while (innerIterator.hasNext())
        {
          WirtschaftsplanNode leaf = (WirtschaftsplanNode) innerIterator.next();
          artSoll += leaf.getSoll();
        }
        child.setSoll(artSoll);
        klasseSoll += artSoll;
      }
      parent.setSoll(klasseSoll);
    }
    else
    {
      while (parent != null)
      {
        @SuppressWarnings("rawtypes")
        GenericIterator iterator = parent.getChildren();
        double soll = 0;
        while (iterator.hasNext())
        {
          WirtschaftsplanNode child = (WirtschaftsplanNode) iterator.next();
          soll += child.getSoll();
        }
        parent.setSoll(soll);

        parent = (WirtschaftsplanNode) parent.getParent();
      }
    }
    uebersicht.updateSoll();
    autoExpand(einnahmen);
    autoExpand(ausgaben);
    if (ruecklagen != null)
    {
      autoExpand(ruecklagen);
    }
  }

  @Override
  public JVereinDBObject prepareStore()
      throws RemoteException, ApplicationException
  {
    Wirtschaftsplan wirtschaftsplan = getWirtschaftsplan();

    wirtschaftsplan
        .setBezeichnung((String) uebersicht.getBezeichnung().getValue());
    Date von = (Date) uebersicht.getVon().getValue();
    Date bis = (Date) uebersicht.getBis().getValue();
    wirtschaftsplan.setDatumBis(bis);
    wirtschaftsplan.setDatumVon(von);
    Long pid = null;
    Projekt p = uebersicht.getProjekt() == null ? null
        : (Projekt) uebersicht.getProjekt().getValue();
    if (p != null)
    {
      pid = Long.valueOf(p.getID());
    }
    wirtschaftsplan.setProjektID(pid);

    return wirtschaftsplan;
  }

  @Override
  public void handleStore() throws ApplicationException
  {
    try
    {
      @SuppressWarnings("unchecked")
      List<WirtschaftsplanNode> rootNodesEinnahmen = (List<WirtschaftsplanNode>) einnahmen
          .getItems();
      @SuppressWarnings("unchecked")
      List<WirtschaftsplanNode> rootNodesAusgaben = (List<WirtschaftsplanNode>) ausgaben
          .getItems();

      DBService service = Einstellungen.getDBService();
      Wirtschaftsplan wirtschaftsplan = (Wirtschaftsplan) prepareStore();

      DBTransaction.starten();

      checkDate();

      wirtschaftsplan.store();

      if (!wirtschaftsplan.isNewObject())
      {
        DBIterator<WirtschaftsplanItem> iterator = service
            .createList(WirtschaftsplanItem.class);
        iterator.addFilter("wirtschaftsplan = ?", wirtschaftsplan.getID());
        while (iterator.hasNext())
        {
          iterator.next().delete(); // Löschen alter Einträge, wird später neu
                                    // angelegt
        }
      }

      for (WirtschaftsplanNode rootNode : rootNodesEinnahmen)
      {
        storeNodes(rootNode.getChildren(), wirtschaftsplan.getID(),
            WirtschaftsplanImpl.EINNAHME);
      }
      for (WirtschaftsplanNode rootNode : rootNodesAusgaben)
      {
        storeNodes(rootNode.getChildren(), wirtschaftsplan.getID(),
            WirtschaftsplanImpl.AUSGABE);
      }
      if (ruecklagen != null)
      {
        @SuppressWarnings("unchecked")
        List<WirtschaftsplanNode> rootNodesRuecklagen = (List<WirtschaftsplanNode>) ruecklagen
            .getItems();
        for (WirtschaftsplanNode rootNode : rootNodesRuecklagen)
        {
          storeNodes(rootNode.getChildren(), wirtschaftsplan.getID(),
              WirtschaftsplanImpl.RUECKLAGE);
        }
      }

      DBTransaction.commit();

      tableChanged = false;

      view.reload();
    }
    catch (ApplicationException e)
    {
      DBTransaction.rollback();

      throw e;
    }
    catch (Exception e)
    {
      DBTransaction.rollback();

      String fehler = "Fehler beim Speichern des Wirtschaftsplans";
      Logger.error(fehler, e);
      throw new ApplicationException(fehler);
    }
  }

  @SuppressWarnings("rawtypes")
  private void storeNodes(GenericIterator iterator, String id, int art)
      throws RemoteException, ApplicationException
  {
    while (iterator.hasNext())
    {
      WirtschaftsplanNode currentNode = (WirtschaftsplanNode) iterator.next();
      if (currentNode.getType().equals(WirtschaftsplanNode.Type.POSTEN))
      {
        WirtschaftsplanItem item = Einstellungen.getDBService()
            .createObject(WirtschaftsplanItem.class, null);
        WirtschaftsplanItem oldItem = currentNode.getWirtschaftsplanItem();
        item.setPosten(oldItem.getPosten());
        item.setSoll(oldItem.getSoll());
        item.setWirtschaftsplanId(id);
        WirtschaftsplanNode parent = (WirtschaftsplanNode) currentNode
            .getParent();
        item.setBuchungsartId(parent.getBuchungsart().getID());
        WirtschaftsplanNode root = (WirtschaftsplanNode) parent.getParent();
        item.setBuchungsklasseId(root.getBuchungsklasse().getID());
        item.setArt(art);
        item.store();
      }
      else
      {
        storeNodes(currentNode.getChildren(), id, art);
      }
    }
  }

  public void checkDate() throws ApplicationException
  {
    Date von = (Date) uebersicht.getVon().getValue();
    Date bis = (Date) uebersicht.getBis().getValue();
    if (von == null)
    {
      throw new ApplicationException("Von-Datum darf nicht leer sein!");
    }
    if (bis == null)
    {
      throw new ApplicationException("Bis-Datum darf nicht leer sein!");
    }
    if (bis.before(von))
    {
      throw new ApplicationException("Bis-Datum muss nach Von-Datum liegen");
    }
  }

  @Override
  public boolean hasChanged() throws RemoteException
  {
    if (!(getWirtschaftsplan() instanceof Wirtschaftsplan))
    {
      return false;
    }
    Wirtschaftsplan wirtschaftsplan = getWirtschaftsplan();

    if (wirtschaftsplan.isNewObject())
    {
      return true;
    }

    if (wirtschaftsplan.isChanged())
    {
      return true;
    }

    return tableChanged;
  }

  /**
   * Setzt den Status des Controls auf "geändert". Wird z.B. von den Editoren
   * aufgerufen, wenn sich etwas geändert hat.
   */
  public void setToChanged()
  {
    tableChanged = true;
  }

  @Override
  protected String getTableTitle()
  {
    return VorlageUtil.getName(VorlageTyp.WIRTSCHAFTSPLAENE_TITEL);
  }

  @Override
  protected String getTableSubtitle()
  {
    return VorlageUtil.getName(VorlageTyp.WIRTSCHAFTSPLAENE_SUBTITEL);
  }

  @Override
  protected String getTableDateiname()
  {
    return VorlageUtil.getName(VorlageTyp.WIRTSCHAFTSPLAENE_DATEINAME);
  }
}
