/**
 *
 */
package brn.gui.fwdgraph.diagram.custom.editors;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;

import brn.gui.Activator;
import brn.gui.datasource.Source;
import brn.gui.diagram.SimulatorResource;
import brn.gui.graph.model.Graph;
import brn.gui.graph.model.FwdLink;
import brn.gui.graph.model.Node;
import brn.gui.graph.model.ModelFactory;
import brn.gui.fwdgraph.diagram.part.ModelDiagramEditorPlugin;
import brn.sim.data.ForwardGraphData;

/**
 * @author kurth
 *
 */
public class FwdGraphResouce extends SimulatorResource {

  private int id;

  public FwdGraphResouce(URI uri, Source simulator) {
    super(uri, simulator);
  }

  public void setId(int id) {
    this.id = id;
  }

  /*
   * NOTE the transaction is only executed if necessary, otherwise
   * drag and drop will not work properly
   */
  public void refresh() throws CoreException {
    if (null == simulator || !simulator.isConnected())
      throw new CoreException(Activator.createError("Simulator not available"));

    final ForwardGraphData fwdGraph = (ForwardGraphData) simulator.getContribution(id);
    final Graph model = (Graph) getEObject("model");

    if (!changed(fwdGraph, model))
      return;

    TransactionalEditingDomain editingDomain =
      TransactionUtil.getEditingDomain(model);

    AbstractTransactionalCommand command = new AbstractTransactionalCommand(
        editingDomain, "Update node", null) { //$NON-NLS-1$
      protected CommandResult doExecuteWithResult(IProgressMonitor monitor,
          IAdaptable info) throws ExecutionException {
        Collection nodes = fwdGraph.getNodesCollection();
        Iterator iter = nodes.iterator();
        while (null != iter && iter.hasNext()) {
          ForwardGraphData.Node node = (ForwardGraphData.Node) iter.next();

          Node modelNode = getModelNode(node);
          if (null == modelNode)
            modelNode = createNode(model, node);
          else
            updateNode(node, modelNode);
        }

        Collection links = fwdGraph.getLinksCollection();
        iter = links.iterator();
        while (null != iter && iter.hasNext()) {
          ForwardGraphData.FwdLink link = (ForwardGraphData.FwdLink) iter.next();

          FwdLink modelLink = getModelLink(link, false);
          if (null == modelLink)
            modelLink = createLink(link, false);
          else
            updateLink(link, modelLink, false);

          // duplicate link
          modelLink = getModelLink(link, true);
          if (null == modelLink)
            modelLink = createLink(link, true);
          else
            updateLink(link, modelLink, true);
        }
        return CommandResult.newOKCommandResult();
      }
    };

    try {
      OperationHistoryFactory.getOperationHistory().execute(command,
          null/*new SubProgressMonitor(null, 1)*/, null);
    } catch (ExecutionException e) {
      throw new CoreException(
          Activator.createError("Error during simulator communication", e));
    }
  }

  private boolean changed(ForwardGraphData fwdGraph, Graph model) {
    Collection nodes = fwdGraph.getNodesCollection();
    Iterator iter = nodes.iterator();
    while (null != iter && iter.hasNext()) {
      ForwardGraphData.Node node = (ForwardGraphData.Node) iter.next();
      Node modelNode = getModelNode(node);

      // TODO address
      if (null == modelNode
          ||modelNode.getId() != node.nodeId
          ||modelNode.getX() != node.locX
          ||modelNode.getY() != node.locY)
        return true;
    }

    Collection links = fwdGraph.getLinksCollection();
    iter = links.iterator();
    while (null != iter && iter.hasNext()) {
      ForwardGraphData.FwdLink link = (ForwardGraphData.FwdLink) iter.next();

      FwdLink modelLink = getModelLink(link, false);
      if (null == modelLink
          ||modelLink.getSourceChannel() != link.fromCh
          ||modelLink.getTargetChannel() != link.toCh
          ||modelLink.getFlowId() != link.flowId
          ||modelLink.getNoPackets() != link.noPackets)
        return true;

      modelLink = getModelLink(link, true);
      if (null == modelLink
          ||modelLink.getSourceChannel() != link.fromCh
          ||modelLink.getTargetChannel() != link.toCh
          ||modelLink.getFlowId() != link.flowId
          ||modelLink.getNoPackets() != link.noDuplicates )
        return true;
    }

    return false;
  }

  private void updateLink(ForwardGraphData.FwdLink link, FwdLink modelLink,
      boolean duplicate) {
    modelLink.setSourceChannel(link.fromCh);
    modelLink.setTargetChannel(link.toCh);
    modelLink.setFlowId(link.flowId);
    modelLink.setNoPackets(duplicate ? link.noDuplicates : link.noPackets);
  }

  private FwdLink createLink(ForwardGraphData.FwdLink link, boolean duplicate) {
    FwdLink modelLink = ModelFactory.eINSTANCE.createFwdLink();
    setID(modelLink, getLinkId(link, duplicate));

    Node modelNodeSrc = getModelNode(link.from);
    Node modelNodeDst = getModelNode(link.to);

    modelLink.setSource(modelNodeSrc);
    modelLink.setTarget(modelNodeDst);

    modelNodeSrc.getSourceConnections().add(modelLink);
    modelNodeDst.getTargetConnections().add(modelLink);

    modelLink.setSourceChannel(link.fromCh);
    modelLink.setTargetChannel(link.toCh);
    modelLink.setFlowId(link.flowId);
    modelLink.setDuplicate(duplicate);
    modelLink.setNoPackets(duplicate ? link.noDuplicates : link.noPackets);

    return modelLink;
  }

  private FwdLink getModelLink(ForwardGraphData.FwdLink link, boolean duplicate) {
    return (FwdLink) getEObjectByID(getLinkId(link, duplicate));
  }

  private Node getModelNode(ForwardGraphData.Node node) {
    return (Node) getEObjectByID(getNodeId(node));
  }

  private String getNodeId(brn.sim.data.ForwardGraphData.Node node) {
    return String.valueOf(node.nodeId);
  }

  private String getLinkId(ForwardGraphData.FwdLink link, boolean duplicate) {
    StringBuilder b = new StringBuilder();
    b.append(getNodeId(link.from));b.append(':');
    b.append(getNodeId(link.to));b.append(':');
    b.append(link.flowId);b.append(':');
    b.append(link.fromCh);b.append(':');
    b.append(link.toCh);b.append(':');
    b.append(duplicate);
    return b.toString();
  }

  private Node createNode(Graph model, ForwardGraphData.Node node) {
    Node modelNode;
    modelNode = ModelFactory.eINSTANCE.createNode();
    setID(modelNode, getNodeId(node));
    model.getNodes().add(modelNode);

    modelNode.setId(node.nodeId);
    modelNode.setX(node.locX);
    modelNode.setY(node.locY);

    return modelNode;
  }

  private void updateNode(ForwardGraphData.Node node, Node modelNode) {
    modelNode.setId(node.nodeId);
    modelNode.setX(node.locX);
    modelNode.setY(node.locY);
  }
}
