package com.alexanderlogistics;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.inject.Inject;

import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.SignalAdapter;
import org.imixs.workflow.WorkflowKernel;
import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.engine.plugins.SplitAndJoinPlugin;
import org.imixs.workflow.exceptions.AdapterException;
import org.imixs.workflow.exceptions.ModelException;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.office.util.SequenceService;

/**
 * Der CargosoftSplitAdapter erezeugt anhand der Positionstablelle eignee
 * cargosoft export workitems, getrennt für jede Buchungsperdiode. Dies ist
 * notwendig, da Cargosoft selbst nicht mit unterschiedlichen Buchungsperioden
 * in einer Rechnung umgehen kann.
 * <p>
 * adapter exports the invoice data to a ftp server connected to cargosoft. Da
 * auch eine fortlaufene Rechnungsnummer von Cargosoft zwingend vorgeschrieben
 * ist, erzeugt der adapter auch für jedes weiteres cargosoft export worktiem
 * ein neue Sequencenummer die im Hautpworkitem gespeichert wird.
 * <p>
 * Der Adapter wird im Modell wie folgt konfiguriert
 * 
 * <pre>
 * {@code
        <cargosoft>
            <modelversion>cargosoft-export-1.0</modelversion>
            <task>1000</task>
            <event>100</event>
            <items>(?!txtworkflowhistory)(^[a-zA-Z]|^_)</items>
        </cargosoft>
       
   }
 * </pre>
 * <p>
 * Because we also export the attachment data to cargosoft, the adaper lookups
 * the conente of the attachment in the snapshot of the origin workitem
 * 
 * 
 * @version 1.0
 * @author rsoika
 */
public class CargosoftSplitAdapter implements SignalAdapter {

    public static final String CONFIG_ERROR = "CONFIG_ERROR";
    private static Logger logger = Logger.getLogger(CargosoftSplitAdapter.class.getName());

    @Inject
    WorkflowService workflowService;

    @Inject
    SequenceService sequenceService;

    /**
     * This method computes the cargosoft export data file
     * 
     * @throws PluginException
     */
    @SuppressWarnings("rawtypes")
    @Override
    public ItemCollection execute(ItemCollection workitem, ItemCollection event)
            throws AdapterException, PluginException {

        logger.info("......starting export...");
        try {

            // read the cargosoft split options
            ItemCollection evalItemCollection = workflowService.evalWorkflowResult(event, "cargosoft", workitem, false);

            if (evalItemCollection == null) {
                throw new PluginException(CargosoftSplitAdapter.class.getSimpleName(), CONFIG_ERROR,
                        "missign cargosoft configuration in model event - please check model configuration");
            }
            String model = evalItemCollection.getItemValueString("model");
            int taskID = evalItemCollection.getItemValueInteger("task");
            int eventID = evalItemCollection.getItemValueInteger("event");
            String items = evalItemCollection.getItemValueString("items");

            if (model.isEmpty() || taskID == 0 || eventID == 0) {
                throw new PluginException(CargosoftSplitAdapter.class.getSimpleName(), CONFIG_ERROR,
                        "missign cargosoft configuration 'model', 'task', 'event' - please check model configuration");
            }

            // Zunaechst müssen wir festelstellen, ob es mehrere unterschiedliche
            // Buchungsperioden in der Positionstabelle gibt.
            List<String> buchungsPersioden = new ArrayList<String>();
            // add haupt buchungsperiode
            buchungsPersioden.add(workitem.getItemValueString("invoice.period"));
            List<ItemCollection> positionsTabelle = InvoicePlugin.explodeChildList(workitem);
            for (ItemCollection posItem : positionsTabelle) {
                String childPeriod = posItem.getItemValueString(InvoicePlugin.ITEM_INVOICE_PERIOD);
                if (!childPeriod.isEmpty() && !buchungsPersioden.contains(childPeriod)) {
                    buchungsPersioden.add(childPeriod);
                }

            }

            // nun wird für jede Buchungsperiode ein eigener corgosoft Export angestoßen....
            boolean nextSequencenumber = false;
            for (String buchungsPeriode : buchungsPersioden) {
                // nun entnehmen wir aus der positionsTabelle alle einträge die zu dieser
                // Periode passen...
                // (die erste Periode ist die default periode welche wir nehemn wenn keine
                // Periode angegeben wurde).
                ItemCollection workitemSubProcess = new ItemCollection();
                // now clone the field list...
                copyItemList(items, workitem, workitemSubProcess);

                // in die _childs Liste kommen jetzt nur passende Positionseinträge mit der
                // selben Buchungsperiode
                List<Map> splitPositionen = new ArrayList<Map>();
                ListIterator<ItemCollection> iter = positionsTabelle.listIterator();
                int row = 1;
                float netto = 0;
                float total = 0;
                // float tax=0;
                while (iter.hasNext()) {
                    ItemCollection posItem = iter.next();
                    String childPeriod = posItem.getItemValueString(InvoicePlugin.ITEM_INVOICE_PERIOD);
                    if (childPeriod.isEmpty() || buchungsPeriode.equals(childPeriod)) {
                        posItem.setItemValue("numpos", row);
                        splitPositionen.add(posItem.getAllItems());
                        iter.remove();
                        row++;
                        netto = netto + posItem.getItemValueFloat("amount");
                        total = total + posItem.getItemValueFloat("total");

                    }
                }
                workitemSubProcess.setItemValue(InvoicePlugin.ITEM_INVOICE_PERIOD, buchungsPeriode);
                // jetzt muessen wir die neue Rechnungssumme bilden....
                workitemSubProcess.setItemValue("order.total", total);
                workitemSubProcess.setItemValue("order.total.tax", total - netto);
                workitemSubProcess.setItemValue("order.total.netto", netto);

                logger.info("......create cargosoft split buchung - invoice.period=" + buchungsPeriode);
                // update the child items (reduzierte Positionstabelle)
                workitemSubProcess.setItemValue(InvoicePlugin.CHILD_ITEM_PROPERTY, splitPositionen);

                workitemSubProcess.model(model).task(taskID).event(eventID);
                workitemSubProcess.replaceItemValue(WorkflowService.UNIQUEIDREF, workitem.getUniqueID());

                // ab dem zweiten durchlauf benötigen wir immer eine neue sequencenumer
                if (nextSequencenumber) {
                    workitemSubProcess.removeItem("numsequencenumber");
                    // wegen numernkreis bildung muss hier die WorkflowGroup kurzfristig auf
                    // 'Rechnungseingang' gesetzt werden!
                    workitemSubProcess.setItemValue(WorkflowKernel.WORKFLOWGROUP, workitem.getWorkflowGroup());
                    sequenceService.computeSequenceNumber(workitemSubProcess);
                    logger.info(".......compute new sequence number: "
                            + workitemSubProcess.getItemValueLong("numsequencenumber"));
                    workitem.appendItemValueUnique("numsequencenumber_sub",
                            workitemSubProcess.getItemValueLong("numsequencenumber"));
                } else {
                    nextSequencenumber = true;
                }
                workitemSubProcess = workflowService.processWorkItem(workitemSubProcess);
                // finally add the new workitemRef into the origin
                workitem.appendItemValueUnique(SplitAndJoinPlugin.LINK_PROPERTY, workitemSubProcess.getUniqueID());

            }

        } catch (ModelException e) {
            logger.severe("cargosoft split failed: " + e.getMessage());
            throw new PluginException(CargosoftSplitAdapter.class.getSimpleName(), CONFIG_ERROR, e.getMessage(), e);
        }

        return workitem;
    }

    /**
     * This Method copies the fields defined in 'items' into the targetWorkitem.
     * Multiple values are separated with comma ','.
     * <p>
     * In case a item name contains '|' the target field name will become the right
     * part of the item name.
     * <p>
     * Example: {@code
     *   txttitle,txtfirstname
     *   
     *   txttitle|newitem1,txtfirstname|newitem2
     *   
     * }
     * 
     * <p>
     * Optional also reg expressions are supported. In this case mapping of the item
     * name is not supported.
     * <p>
     * Example: {@code
     *   (^artikel$|^invoice$),txtTitel|txtNewTitel
     *   
     *   
     * } A reg expression must be includes in brackets.
     * 
     */
    protected void copyItemList(String items, ItemCollection source, ItemCollection target) {
        // clone the field list...
        StringTokenizer st = new StringTokenizer(items, ",");
        while (st.hasMoreTokens()) {
            String field = st.nextToken().trim();

            // test if field is a reg ex
            if (field.startsWith("(") && field.endsWith(")")) {
                Pattern itemPattern = Pattern.compile(field);
                Map<String, List<Object>> map = source.getAllItems();
                for (String itemName : map.keySet()) {
                    if (itemPattern.matcher(itemName).find()) {
                        target.replaceItemValue(itemName, source.getItemValue(itemName));
                    }
                }

            } else {
                // default behavior without reg ex
                int pos = field.indexOf('|');
                if (pos > -1) {
                    target.replaceItemValue(field.substring(pos + 1).trim(),
                            source.getItemValue(field.substring(0, pos).trim()));
                } else {
                    target.replaceItemValue(field, source.getItemValue(field));
                }
            }
        }
    }

}