/*
Copyright 2011-2016 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.security.zynamics.zylib.yfileswrap.gui.zygraph.realizers;

import com.google.common.base.Preconditions;
import com.google.security.zynamics.zylib.general.ListenerProvider;
import com.google.security.zynamics.zylib.gui.zygraph.edges.ZyEdgeData;
import com.google.security.zynamics.zylib.gui.zygraph.realizers.IEdgeRealizerUpdater;
import com.google.security.zynamics.zylib.gui.zygraph.realizers.IZyEdgeRealizer;
import com.google.security.zynamics.zylib.gui.zygraph.realizers.IZyEdgeRealizerListener;
import com.google.security.zynamics.zylib.gui.zygraph.realizers.ZyLabelContent;

import y.base.Graph;
import y.geom.YPoint;
import y.view.Arrow;
import y.view.Bend;
import y.view.LineType;
import y.view.PolyLineEdgeRealizer;

import java.awt.Color;
import java.awt.Graphics2D;

public class ZyEdgeRealizer<EdgeType> extends PolyLineEdgeRealizer implements IZyEdgeRealizer {
  private ZyEdgeData<EdgeType> m_edgeData;

  private IEdgeRealizerUpdater<EdgeType> m_updater;

  private boolean m_drawSloppyEdges = true;

  private boolean m_drawBends = false;

  private boolean m_isHighlighted = false;

  private boolean m_isSloppyPainting = false;

  private final ListenerProvider<IZyEdgeRealizerListener<EdgeType>> m_listeners = new ListenerProvider<>();

  private ZyLabelContent m_content;


  public ZyEdgeRealizer(final ZyLabelContent content, final IEdgeRealizerUpdater<EdgeType> updater) {

    m_content = Preconditions.checkNotNull(content);

    setArrow(Arrow.STANDARD);
    setSmoothedBends(true);

    if (content.getLineCount() > 0) {
      addLabel(new ZyEdgeLabel(content));
    }

    setUpdater(updater);
  }

  private void notifyLocationChanged() {
    for (final IZyEdgeRealizerListener<EdgeType> listener : m_listeners) {
      try {
        listener.changedLocation(this);
      } catch (final Exception exception) {
        exception.printStackTrace();
      }
    }
  }

  @Override
  protected void paintHighlightedBends(final Graphics2D gfx) {
    if (m_drawBends && !m_isSloppyPainting) {
      super.paintHighlightedBends(gfx);
    }
  }

  public void addListener(final IZyEdgeRealizerListener<EdgeType> listener) {
    m_listeners.addListener(listener);
  }

  public void addSilent(final double x, final double y) {
    super.appendBend(x, y);
  }

  @Override
  public Bend appendBend(final double x, final double y) {
    final Bend bend = super.appendBend(x, y);
    bend.setSelected(isSelected());

    return bend;
  }

  @Override
  public void bendChanged(final Bend bend, final double x, final double y) {
    final int index = bendPos(bend);

    super.bendChanged(bend, x, y);

    for (final IZyEdgeRealizerListener<EdgeType> listener : m_listeners) {
      try {
        listener.bendChanged(index, bend.getX(), bend.getY());
      } catch (final Exception exception) {
        exception.printStackTrace();
      }
    }
  }

  @Override
  public void clearBends() {
    super.clearBends();

    for (final IZyEdgeRealizerListener<EdgeType> listener : m_listeners) {
      try {
        listener.clearedBends();
      } catch (final Exception exception) {
        exception.printStackTrace();
      }
    }
  }

  public ZyLabelContent getEdgeLabelContent() {
    return m_content;
  }

  public ZyEdgeData<EdgeType> getUserData() {
    return m_edgeData;
  }

  @Override
  public void paint(final Graphics2D gfx) {
    m_content.setSloppy(false);
    m_isSloppyPainting = false;
    super.paint(gfx);
  }

  @Override
  public void paintBends(final Graphics2D gfx) {
    return;
  }

  @Override
  public void paintPorts(final Graphics2D gfx) {
    return;
  }

  @Override
  public void paintSloppy(final Graphics2D gfx) {
    // A sloppy edge draws twice as fast as a non-sloppy edge
    m_isSloppyPainting = true;

    m_content.setSloppy(true);

    if (!m_drawSloppyEdges) {
      return;
    }

    if (isSelected() || m_isHighlighted) {
      final LineType originalLineType = getLineType();
      final Color originalSelectionColor = getSelectionColor();

      setLineType(LineType.LINE_5);
      setSelectionColor(getLineColor());

      super.paint(gfx);

      setLineType(originalLineType);
      setSelectionColor(originalSelectionColor);
    } else {
      super.paintSloppy(gfx);
    }
  }

  /**
   * Regenerates the content of the realizer.
   */
  @Override
  public void regenerate() {
    m_content = m_updater.generateContent(this);

    removeLabel(getLabel());

    if (m_content.getLineCount() > 0) {
      addLabel(new ZyEdgeLabel(m_content));
    }

    for (final IZyEdgeRealizerListener<EdgeType> listener : m_listeners) {
      try {
        listener.regenerated(this);
      } catch (final Exception exception) {
        exception.printStackTrace();
      }
    }

    getLabel().repaint();
    repaint();
  }

  @Override
  public void reInsertBend(final Bend bend, final Bend refBend, final int dir) {

    final int index = ((bendPos(refBend) + dir) == Graph.BEFORE) || (refBend == null) ? 0 : 1;

    super.reInsertBend(bend, refBend, dir);

    for (final IZyEdgeRealizerListener<EdgeType> listener : m_listeners) {
      try {
        listener.insertedBend(index, bend.getX(), bend.getY());
      } catch (final Exception exception) {
        exception.printStackTrace();
      }
    }
  }

  @Override
  public Bend removeBend(final Bend bend) {
    final int index = bendPos(bend);

    final Bend rbend = super.removeBend(bend);

    for (final IZyEdgeRealizerListener<EdgeType> listener : m_listeners) {
      try {
        listener.removedBend(this, index);
      } catch (final Exception exception) {
        exception.printStackTrace();
      }
    }

    return rbend;
  }

  public void removeListener(final IZyEdgeRealizerListener<EdgeType> listener) {
    m_listeners.removeListener(listener);
  }

  public void setDrawBends(final boolean draw) {
    m_drawBends = draw;
  }

  public void setDrawSloppyEdges(final boolean draw) {
    m_drawSloppyEdges = draw;
  }

  @Override
  public void setLineType(final LineType lineType) {
    m_isHighlighted =
        (lineType == LineType.LINE_5) || (lineType == LineType.DASHED_5)
            || (lineType == LineType.DOTTED_5) || (lineType == LineType.DASHED_DOTTED_5);

    super.setLineType(lineType);
  }

  @Override
  public void setSelected(final boolean selected) {
    super.setSelected(selected);
    for (int i = 0; i < bendCount(); ++i) {
      getBend(i).setSelected(selected);
    }
  }

  @Override
  public void setSourcePoint(final YPoint point) {
    super.setSourcePoint(point);

    notifyLocationChanged();
  }

  @Override
  public void setTargetPoint(final YPoint point) {
    super.setTargetPoint(point);

    notifyLocationChanged();
  }

  public void setUpdater(final IEdgeRealizerUpdater<EdgeType> updater) {
    m_updater = updater;

    if (updater != null) {
      updater.setRealizer(this);
    }
  }

  public void setUserData(final ZyEdgeData<EdgeType> data) {
    m_edgeData = data;
  }

  @Override
  public void setVisible(final boolean visible) {
    super.setVisible(visible);

    for (final IZyEdgeRealizerListener<EdgeType> listener : m_listeners) {
      try {
        listener.changedVisibility(this);
      } catch (final Exception exception) {
        exception.printStackTrace();
      }
    }
  }
}
