package nxm.sys.lib;

import java.util.ArrayList;
import java.util.Map;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.MapKBT;

/** This class is used to store the objects created during the parsing of the GeoJSON file.
    JsonUtilities is used to get the JSON into a MapKBT. The objects are formed from there.

    @author Jeanette McCall
    @since NeXtMidas 4.1.3
 */
@InternalUseOnly("Experimental Prototype in NeXtMidas 4.1.3")
public class GeoJSONObjects {

  private ArrayList<GeoJSONPoint>      points;       // All points that have been drawn on. Used in MousePopups
  private ArrayList<GeoJSONPolygon>    polygons;     // All polygons that have been drawn on. Used in MousePopups
  private ArrayList<GeoJSONLineString> lineStrings;  // All the line strings

  /** Constructor instantiates the ArrayLists   */
  public GeoJSONObjects () {
    points = new ArrayList<>();
    polygons = new ArrayList<>();
    lineStrings = new ArrayList<>();
  }

  /** Generic method to add a <code>GeoJSONObject</code>
   *  @param obj The <code>GeoJSONObject</code> to add */
  public void addGeoJSONObject (Object obj) {
    //TODO if needed
  }

  @SuppressWarnings("rawtypes")
  void addGeoJSONPoint (String name, double[] coordsPoint, int atomsPerElem, Map properties, GeoJSONGroup group) {
    points.add(new GeoJSONPoint(name,coordsPoint,atomsPerElem,properties,group));
  }

  @SuppressWarnings("rawtypes")
  void addGeoJSONLineString (String name, double[] coordsLineString, int atomsPerElem, Map properties, GeoJSONGroup group) {
    lineStrings.add(new GeoJSONLineString(name,coordsLineString,atomsPerElem,properties,group));
  }

  @SuppressWarnings("rawtypes")
  void addGeoJSONPolygon (String name, double[] outerCoordsPoly, int atomsPerElem, Map properties, GeoJSONGroup group) {
    polygons.add(new GeoJSONPolygon(name,outerCoordsPoly,atomsPerElem,properties,group));
  }

  @SuppressWarnings("rawtypes")
  void addGeoJSONPolygon (String name, double[] outerCoordsPoly, double[][] innerCoordsPoly, int atomsPerElem, Map properties, GeoJSONGroup group) {
    polygons.add(new GeoJSONPolygon(name,outerCoordsPoly,innerCoordsPoly,atomsPerElem,properties,group));
  }

  /** Gets the <code>GeoJSONPoint</code>s
   *  @return The <code>GeoJSONPoint</code>s
   */
  @InternalUseOnly
  public ArrayList<GeoJSONPoint> getGeoJSONPoints () {
    return points;
  }

  /** Gets the <code>GeoJSONLineString</code>s
   *  @return The <code>GeoJSONLineString</code>s
   */
  @InternalUseOnly
  public ArrayList<GeoJSONLineString> getGeoJSONLineStrings () {
    return lineStrings;
  }

  /** Gets the <code>GeoJSONPolygon</code>s
   *  @return The <code>GeoJSONPolygon</code>s
   */
  @InternalUseOnly
  public ArrayList<GeoJSONPolygon> getGeoJSONPolygons () {
    return polygons;
  }

  public String toString() {
    StringBuilder out = new StringBuilder();
    for (GeoJSONPoint       pt    : points)      out.append(pt).append("\n");
    for (GeoJSONPolygon     poly  : polygons)    out.append(poly).append("\n");
    for (GeoJSONLineString lineStr: lineStrings) out.append(lineStr).append("\n");
    return out.toString();
  }

  /////////////////  Inner classes below this point /////////////////

  ///////////////////////////////////////////////////////////////////
  /** GeoJSON Object */
  @InternalUseOnly
  public class GeoJSONObject {
    String    name;

    /** <code>GeoJSONObject</code> default constructor */
    public GeoJSONObject () {}

    /** Gets the name
     *  @return The name
     */
    public String getName () { return name; }

    /** Table representing GeoJSONObject
     *  @return table representing object
     */
    public Table toTable () {
      return new Table("{GeoJSONObject={name="+name+"}}");
    }
  }

  ///////////////////////////////////////////////////////////////////
  /** GeoJSON Feature */
  @InternalUseOnly
  @SuppressWarnings("rawtypes")
  public class GeoJSONFeature extends GeoJSONObject {
    Map    properties;
    double[] coords;    // used for buf
    int atomsPerElem;   // used for dim when doing draw
    GeoJSONGroup group; // used for multipoints, etc.

    /** <code>GeoJSONFeature</code> default constructor */
    public GeoJSONFeature () {
      name          = "";
      properties    = MapKBT.newMap();
    }

    /** <code>GeoJSONFeature</code> constructor
     *  @param name         name
     *  @param flatCoords   coordinates
     *  @param atomsPerElem atoms per element
     *  @param properties   properties
     *  @param group        Multi Feature group (or null)
     */
    public GeoJSONFeature (String name, double[] flatCoords, int atomsPerElem, Map properties, GeoJSONGroup group)  {
      this.name = name;
      this.coords = flatCoords;
      this.atomsPerElem = atomsPerElem;
      this.properties = properties;
      this.group = group;
      if (group != null) group.add(this);
    }

    /** get Atoms Per Element
     *  @return atoms per element
     */
    public int getAtomsPerElem () {
      return atomsPerElem;
    }

    /** get coordinates
     *  @return coordinates
     */
    public double[] getCoords () { return coords; }

    /** Gets the associated <code>GeoJSONGroup</code>.
     *  @return The associated <code>GeoJSONGroup</code>
     */
    public GeoJSONGroup getGroup() { return group; }

    /** Get Properties
     *  @return properties
     */
    public Map getProperties () {
      return properties;
    }

    String getCoordsAsString (double[] coords, int atomsPerElem) {
      StringBuilder stb = new StringBuilder("[");
      int coorGroups = coords.length/atomsPerElem;
      boolean multCoordGroups = coorGroups > 1;
      int coordIdx = 0;
      for (int cg = 0; cg < coorGroups; cg++) {
        if (multCoordGroups) stb.append("[");
        for (int atom = 0; atom < atomsPerElem; atom++) {
          stb.append(coords[coordIdx++]+",");
        }
        stb.replace(stb.length()-1,stb.length(),"");
        if (multCoordGroups) stb.append("]");
        stb.append(",");
      }
      stb.replace(stb.length()-1,stb.length(),"]");
      return stb.toString();
    }

    String infoString () {
      return "name="+name+",coords=\""+getCoordsAsString(coords, atomsPerElem)+"\"";
    }

    public String toString() {
      return getClass().getSimpleName()+" "+infoString();
    }

    public Table toTable () {
      return new Table("{"+getClass().getSimpleName()+"={"+infoString()+"}}",Table.AUTOREADPREV|Table.CASESENSITIVE);
    }
  }

  ///////////////////////////////////////////////////////////////////
  /** GeoJSON Point */
  @InternalUseOnly
  public class GeoJSONPoint extends GeoJSONFeature {

    /** <code>GeoJSONPoint</code> default constructor */
    public GeoJSONPoint () {
      coords = new double[3];
    }

    /** <code>GeoJSONPoint</code> constructor
     *  @param name           name
     *  @param flatCoordArray coordinates
     *  @param atomsPerElem   atoms per element
     *  @param properties     properties
     *  @param group          MultiPoint group (or null)
     */
    @SuppressWarnings("rawtypes")
    public GeoJSONPoint (String name, double[] flatCoordArray, int atomsPerElem, Map properties, GeoJSONGroup group) {
      super(name, flatCoordArray, atomsPerElem, properties, group);
    }
  }

  ///////////////////////////////////////////////////////////////////
  /** GeoJSON Line String */
  @InternalUseOnly
  public class GeoJSONLineString extends GeoJSONFeature {

    /** <code>GeoJSONLineString</code> constructor
     *  @param name         name
     *  @param flatCoords   coordinates
     *  @param atomsPerElem atoms per element
     *  @param properties   properties
     *  @param group        MultiLineString group (or null)
     */
    @SuppressWarnings("rawtypes")
    public GeoJSONLineString (String name, double[] flatCoords, int atomsPerElem, Map properties, GeoJSONGroup group) {
      super(name, flatCoords, atomsPerElem, properties, group);
    }
  }

  ///////////////////////////////////////////////////////////////////
  /** GeoJSON Polygon */
  @InternalUseOnly
  public class GeoJSONPolygon extends GeoJSONFeature {
    private double[][] innerCoords = null;  // can be 0 or more
    private boolean fill = false;

    /** <code>GeoJSONPolygon</code> default constructor */
    public GeoJSONPolygon () {}

    /** <code>GeoJSONPolygon</code> constructor with just outer coordinates
     *  @param name         polygon name
     *  @param outerCoords  outer coordinates
     *  @param atomsPerElem atoms per element
     *  @param properties   properties
     *  @param group        MultiPolygon group (or null)
     */
    @SuppressWarnings("rawtypes")
    public GeoJSONPolygon (String name, double[] outerCoords, int atomsPerElem, Map properties, GeoJSONGroup group) {
      super(name, outerCoords, atomsPerElem, properties, group);
      if (properties != null) {
        fill = Convert.o2z(properties.get("fill"));
      }
    }

    /** <code>GeoJSONPolygon</code> constructor with just outer coordinates
     *  @param name         polygon name
     *  @param outerCoords  outer coordinates
     *  @param innerCoords  inner coordinates
     *  @param atomsPerElem atoms per element
     *  @param properties   properties
     *  @param group        MultiPolygon group (or null)
     */
    @SuppressWarnings("rawtypes")
    public GeoJSONPolygon (String name, double[] outerCoords, double[][] innerCoords, int atomsPerElem, Map properties, GeoJSONGroup group) {
      this(name, outerCoords, atomsPerElem, properties, group);
      this.innerCoords = innerCoords;
      if (properties != null) {
        fill = Convert.o2z(properties.get("fill"));
      }
    }

    /** get outer boundary
     *  @return double array with outer boundary
     */
    public double[] getOuterBoundary () {
      return coords;
    }

    /** get inner boundaries
     *  @return 2D double array with inner boundaries
     */
    public double[][] getInnerBoundaries () {
      return innerCoords;
    }

    /** get if should be filled
     *  @return true if should be filled
     */
    public boolean getFill () {
      return fill;
    }

    private String getInnerCoordsAsString () {
      StringBuilder stb = new StringBuilder("[");
      for (double[] elem : innerCoords) {
        stb.append(getCoordsAsString(elem,atomsPerElem)).append(",");
      }
      stb.replace(stb.length()-1,stb.length(),"]");
      return stb.toString();
    }

    String infoString () {
      StringBuilder stb = new StringBuilder(super.infoString());
      if (innerCoords != null) {
        stb.append(",innercoords=\"").append(getInnerCoordsAsString()).append("\"");
      }
      return stb.toString();
    }
  }

  ///////////////////////////////////////////////////////////////////
  /** Group to link Multi Features (ex: MultiLineStrings MultiPoints and MultiPolygons) */
  @InternalUseOnly
  public static class GeoJSONGroup {
    private ArrayList<GeoJSONObject> objectsInGroup = new ArrayList<>();
    private String name;
    private String type;

    GeoJSONGroup (String name, String type) {
      this.name = name;
      this.type = type;
    }

    boolean add (GeoJSONObject groupObject) {
      return objectsInGroup.add(groupObject);
    }

    boolean remove (GeoJSONObject groupObject) {
      return objectsInGroup.remove(groupObject);
    }

    public String toString() {
      StringBuilder stb = new StringBuilder("GroupName="+name+",GroupType="+type+",Objects=[");
      for (int i = 0; i < objectsInGroup.size(); i++) {
        stb.append(objectsInGroup.get(i).toString()+",");
      }
      stb.replace(stb.length()-1,stb.length(),"]");
      return stb.toString();
    }

    /** Gets the <code>GeoJSONGroup</code> as a Table
     *  @return The <code>GeoJSONGroup</code> as a Table
     */
    public Table toTable () {
      Table groupInnerTable = new Table();
      groupInnerTable.setFlags(groupInnerTable.getFlagsInt()|Table.AUTOREADPREV|Table.CASESENSITIVE);
      for (GeoJSONObject groupObject : objectsInGroup) {
        String oldObjKey = groupObject.getClass().getSimpleName();
        Table objTbl = ((Table)groupObject.toTable().get(oldObjKey));
        String objName = groupObject.getName();
        char newObjKey = objName.charAt(objName.length()-1);
        if (type.contains("GeometryCollection")) objTbl.addIfNotPresent("type",oldObjKey);
        groupInnerTable.addIfNotPresent(""+newObjKey,objTbl);
      }
      Table groupTable = new Table();
      groupTable.setFlags(groupTable.getFlagsInt()|Table.AUTOREADPREV|Table.CASESENSITIVE);
      groupTable.addIfNotPresent(type,groupInnerTable);
      return groupTable;
    }
  }
  ///////////////////////////////////////////////////////////////////

}
