
package de.ivu.elect.presentation.gebietsbaum.polygon;

import static de.ivu.elect.business.gebietsbaum.entity.AbstractGebietPolygonPoint.berechneMitteLatLng;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;
import javax.inject.Inject;

import org.primefaces.event.map.MarkerDragEvent;
import org.primefaces.event.map.PointSelectEvent;
import org.primefaces.event.map.StateChangeEvent;
import org.primefaces.model.map.DefaultMapModel;
import org.primefaces.model.map.LatLng;
import org.primefaces.model.map.MapModel;
import org.primefaces.model.map.Polygon;

import de.ivu.elect.business.gebietsbaum.boundary.GebietSelectChangeEvent;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygon;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygonPoint;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygonPointArt;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygone;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.jee.faces.FacesContextUtil;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.presentation.gebietsbaum.tree.AbstractNoStoreGebietsbaumView;

public abstract class AbstractGebietGeometrieMapEditView extends AbstractGebietGeometrieView {

  private static final long serialVersionUID = 1L;

  public static final int DEFAULT_ZOOM = 9;

  public static final double REMOVE_DISTANCE_IN_METER = 20.d;

  @Inject
  private AbstractNoStoreGebietsbaumView gebietsbaumView;

  private boolean changed;

  private String center;

  private Integer zoom = DEFAULT_ZOOM;

  private MapModel geometrieModel;

  private List<Polygon> polygoneDesAtuellenGebietes = new ArrayList<>();

  public AbstractGebietGeometrieMapEditView() {
    super();
  }

  public void onGebietSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) GebietSelectChangeEvent event) {
    if (getViewId().equals(FacesContextUtil.getCurrentViewId())) {
      if (isChanged() && !getPolygoneDesAtuellenGebietes().isEmpty() && !isReadOnly(event.getAlteEntity())) {
        save(event.getAlteEntity());
      }

      gebietsbaumView.reset();
      reset();
      FacesContextUtil.redirectToView(getViewId());
    } else {
      reset();
    }

  }

  protected abstract void save(Gebiet entity);

  protected abstract String getViewId();

  public boolean isChanged() {
    return changed;
  }

  public void setChanged() {
    this.changed = true;
  }

  public void setChanged(boolean changed) {
    this.changed = changed;
  }

  @Override
  @Redirect
  public String reset() {
    changed = false;
    geometrieModel = null;
    polygoneDesAtuellenGebietes = new ArrayList<>();
    return null;
  }

  public boolean isReadOnly() {
    return isReadOnly(getGebiet());
  }

  protected boolean isReadOnly(Gebiet gebiet) {
    return gebiet.hasUntergeordneteGebiete() || !getBearbeitenGebietSecurity().isRechtVorhanden(gebiet);
  }

  public MapModel getMapModel() {
    if (geometrieModel == null) {
      geometrieModel = createModel();
    }

    return geometrieModel;
  }

  protected MapModel createModel() {
    setDefaultCenter();
    DefaultMapModel result = new DefaultMapModel();
    geometrieModel = result;
    changed = false;

    return result;
  }

  protected Polygon addPolygonDesAktuellenGebietes() {
    Polygon polygon = createPolygon(getGebietName(getEntity()), FARBE_POLYGON_GEBIET);
    getMapModel().addOverlay(polygon);
    polygoneDesAtuellenGebietes.add(polygon);
    return polygon;
  }

  public void setCenter(String center) {
    this.center = center;
  }

  public List<Polygon> getPolygoneDesAtuellenGebietes() {
    return polygoneDesAtuellenGebietes;
  }

  protected void setDefaultCenter() {
    setCenter("52.30,13.25");
  }

  public String getCenter() {
    getMapModel(); // bauen, wenn noch nicht erfolgt...
    return center;
  }

  public void onPointSelect(PointSelectEvent event) {
    getMapModel(); // bauen, wenn noch nicht erfolgt...
    if (isReadOnly()) {
      return;
    }
    onPointSelectInternal(event);

  }

  protected abstract void onPointSelectInternal(PointSelectEvent event);

  public void onMarkerDrag(MarkerDragEvent event) {
    getMapModel();
    if (isReadOnly()) {
      return;
    }
    onMarkerDragInternal(event);
  }

  protected abstract void onMarkerDragInternal(MarkerDragEvent event);

  public Integer getZoom() {
    getMapModel();
    return zoom;
  }

  public void onStateChange(StateChangeEvent event) {
    getMapModel();
    center = event.getCenter().getLat() + "," + event.getCenter().getLng();
    zoom = event.getZoomLevel();
  }

  @Override
  protected boolean isGebietBearbeitenRechtPruefen() {
    return true;
  }

  @Override
  protected boolean isSavePermissionVorhanden() {
    return !getGebiet().hasUntergeordneteGebiete() && hasPermission(UseCasePermission.GEBIET_GEOMETRIE_BEARBEITEN);
  }

  protected String berechneMitte(List<GebietPolygonPoint> polygoneDesGebietes) {
    LatLng latLng = berechneMitteLatLng(polygoneDesGebietes);
    return latLng.getLat() + "," + latLng.getLng();
  }

  protected Polygon createNachbarPolygon(String name) {
    return createPolygon(name, FARBE_POLYGON_NACHBAR);
  }

  protected Polygon createPolygon(String name, String farbe) {
    Polygon nachbarPolygon = new Polygon();
    nachbarPolygon.setData(name);
    nachbarPolygon.setStrokeColor("#000000");
    nachbarPolygon.setFillColor(farbe);
    nachbarPolygon.setStrokeOpacity(0.6);
    nachbarPolygon.setFillOpacity(0.6);
    return nachbarPolygon;
  }

  protected String berechneMitte(List<GebietPolygonPoint> polygoneDesGebietes, Collection<List<GebietPolygonPoint>> nachbarGebiete) {
    if (polygoneDesGebietes.isEmpty()) {

      List<GebietPolygonPoint> alle = new ArrayList<>();
      for (List<GebietPolygonPoint> gebietPolygonPoints : nachbarGebiete) {
        alle.addAll(gebietPolygonPoints);
      }
      return berechneMitte(alle);

    } else {
      return berechneMitte(polygoneDesGebietes);
    }

  }

  protected boolean isMindestensEinPolgonVorhanden(List<GebietPolygonPoint> polygoneDesGebietes,
      Collection<List<GebietPolygonPoint>> nachbarGebiete) {
    return !polygoneDesGebietes.isEmpty() || isPolygonVorhanden(nachbarGebiete);
  }

  protected boolean isPolygonVorhanden(Collection<List<GebietPolygonPoint>> nachbarGebiete) {
    for (List<GebietPolygonPoint> gebietPolygonPoints : nachbarGebiete) {
      if (!gebietPolygonPoints.isEmpty()) {
        return true;
      }
    }
    return false;
  }

  protected GebietPolygone convertPolygonPath(Collection<Polygon> polygone) {
    long pos = 0;
    GebietPolygone result = new GebietPolygone();

    for (Polygon polygon : polygone) {
      GebietPolygon gebietPolygon = new GebietPolygon();
      result.getPolygonList().add(gebietPolygon);
      for (LatLng latLng : polygon.getPaths()) {
        GebietPolygonPoint gebietPolygonPoint = new GebietPolygonPoint();
        if (gebietPolygon.getAnzahlPunkte() == 0) {
          gebietPolygonPoint.setGebietPolygonPointArt(GebietPolygonPointArt.BEGINN);
        } else {
          gebietPolygonPoint.setGebietPolygonPointArt(GebietPolygonPointArt.PUNKT);
        }
        gebietPolygonPoint.setPosition(pos++);
        gebietPolygonPoint.setLatitude(latLng.getLat());
        gebietPolygonPoint.setLongitude(latLng.getLng());
        gebietPolygon.add(gebietPolygonPoint);
      }
    }
    return result;
  }

}
