/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */
package ilog.views.maps.format.fme;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import COM.safe.fmeobjects.FMEException;
import COM.safe.fmeobjects.FMEFormatInfo;
import COM.safe.fmeobjects.FMEObjects;
import COM.safe.fmeobjects.IFMEFeature;
import COM.safe.fmeobjects.IFMEFeatureVector;
import COM.safe.fmeobjects.IFMESession;
import COM.safe.fmeobjects.IFMEStringArray;
import COM.safe.fmeobjects.IFMEUniversalReader;
import ilog.views.maps.IlvAttributeInfoProperty;
import ilog.views.maps.IlvCoordinate;
import ilog.views.maps.IlvFeatureAttribute;
import ilog.views.maps.IlvFeatureAttributeProperty;
import ilog.views.maps.IlvMapFeature;
import ilog.views.maps.IlvMapGeometry;
import ilog.views.maps.attribute.IlvStringAttribute;
import ilog.views.maps.geometry.IlvMapGeometryCollection;
import ilog.views.maps.geometry.IlvMapLineString;
import ilog.views.maps.geometry.IlvMapMultiPoint;
import ilog.views.maps.geometry.IlvMapPolygon;
import ilog.views.maps.geometry.IlvMapSegmentRing;
import ilog.views.maps.geometry.IlvMapText;
import ilog.views.maps.srs.coordsys.IlvCoordinateSystem;
import ilog.views.maps.srs.wkt.IlvWKTCoordinateSystemFactory;

/**
 * FME Objects reader.
 */
public class FMEDataset {
  // fme_type
  static final private String kFME_Type = "fme_type"; //$NON-NLS-1$
  // static final private String kFME_No_Geom = "fme_no_geom"; //$NON-NLS-1$
  static final private String kFME_Point = "fme_point"; //$NON-NLS-1$
  // static final private String kFME_Line = "fme_line"; //$NON-NLS-1$
  // static final private String kFME_Area = "fme_area"; //$NON-NLS-1$
  static final private String kFME_Text = "fme_text"; //$NON-NLS-1$
  static final private String kFME_Ellipse = "fme_ellipse"; //$NON-NLS-1$
  static final private String kFME_Arc = "fme_arc"; //$NON-NLS-1$
  static final private String kFME_Raster = "fme_raster"; //$NON-NLS-1$
  // static final private String kFME_Undefined = "fme_undefined"; //$NON-NLS-1$
  // Arc, ellipse, text attributes
  static final private String kFME_Rotation = "fme_rotation"; //$NON-NLS-1$
  static final private String kFME_Primary_Axis = "fme_primary_axis"; //$NON-NLS-1$
  static final private String kFME_Secondary_Axis = "fme_secondary_axis"; //$NON-NLS-1$
  static final private String kFME_Start_Angle = "fme_start_angle"; //$NON-NLS-1$
  static final private String kFME_Sweep_Angle = "fme_sweep_angle"; //$NON-NLS-1$
  static final private String kFME_Text_String = "fme_text_string"; //$NON-NLS-1$
  // static final private String kFME_Text_Size = "fme_text_size"; //$NON-NLS-1$
  private IFMESession fmeSession;
  private IFMEUniversalReader fmeReader;
  private Hashtable<String, IlvAttributeInfoProperty> fmeAttributeInfoProperties = new Hashtable<String, IlvAttributeInfoProperty>();

  /**
   * Creates a new <code>FMEDataset</code>.
   * 
   * @throws FMEException
   */
  FMEDataset() throws FMEException {
    fmeSession = FMEObjects.createSession();
    IFMEStringArray fmeDirectives = fmeSession.createStringArray();
    fmeSession.init(fmeDirectives);
    fmeDirectives.dispose();
  }

  /**
   * @see java.lang.Object#finalize()
   */
  public void dispose() {
    // Make that we don't leave an open reader behind.
    try {
      closeReader();
    } catch (FMEException e) {
      // Handle the exception here since this method will be called from
      // within catch() blocks.
      new Exception(e.getMessage() + " " + e.getErrorNumber() + " " + e.getFMEStackTrace()).printStackTrace(); //$NON-NLS-1$//$NON-NLS-2$
    } finally {
      // Free all the FME Objects resources.
      fmeSession.dispose();
      fmeSession = null;
    }
  }

  Override
  protected void finalize() {
    dispose();
  }

  /**
   * Opens the FME dataset.
   * 
   * @param format
   *          A valid FME Format.
   * @param fileName
   *          The file name.
   * @throws FMEException
   */
  public void open(String format, String fileName) throws FMEException {
    closeReader();
    FMEFormatInfo datasetInfo = new FMEFormatInfo(format, fileName);
    IFMEStringArray fmeDirectives = fmeSession.createStringArray();
    fmeReader = fmeSession.createReader(datasetInfo.format, true, fmeDirectives);
    fmeDirectives.dispose();
    IFMEStringArray fmeParameters = fmeSession.createStringArray();
    fmeReader.open(datasetInfo.dataset, fmeParameters);
    fmeParameters.dispose();
  }

  private void closeReader() throws FMEException {
    // Close the reader if it's open.
    if (fmeReader != null) {
      fmeReader.close();
      fmeReader.dispose();
      fmeReader = null;
    }
    fmeAttributeInfoProperties.clear();
  }

  /**
   * Reads the FME Schema and returns the list of layer names.
   * 
   * @return Returns the list of layer names.
   * @throws FMEException
   */
  public String[] readLayerList() throws FMEException {
    List<String> layers = new ArrayList<String>();
    // Get the schema features from the reader.
    IFMEFeature feature = fmeSession.createFeature();
    while (fmeReader.readSchema(feature)) {
      layers.add(feature.getFeatureType());
    }
    feature.dispose();
    return layers.toArray(new String[0]);
  }

  // This function extracts the coordinate geometry of the features for
  // later use in drawing the features when a feature is selected.
  private IlvMapGeometry getFeatureCoords(IFMEFeature feature) throws FMEException {

    String strFme_Type = feature.getStringAttribute(kFME_Type);
    switch (feature.getGeometryType()) {
    case IFMEFeature.FME_GEOM_POINT:
      if (strFme_Type.compareTo(kFME_Text) == 0) {
        return getTextCoords(feature);
      } else if (strFme_Type.compareTo(kFME_Point) == 0) {
        return getPointCoords(feature);
      } else if (strFme_Type.compareTo(kFME_Arc) == 0 || strFme_Type.compareTo(kFME_Ellipse) == 0) {
        return getArcCoords(feature);
      } else {
        System.err.println("Invalid fme_type."); //$NON-NLS-1$
      }
      break;
    case IFMEFeature.FME_GEOM_LINE:
      return getSimpleCoords(feature);
    case IFMEFeature.FME_GEOM_POLYGON:
      if (strFme_Type.compareTo(kFME_Raster) == 0) {
        ArrayList<String> attributeNames = new ArrayList<String>();
        feature.getAllAttributeNames(attributeNames);
        return new IlvMapPolygon(new IlvMapSegmentRing(getSimpleCoords(feature)));
      }
      return new IlvMapPolygon(new IlvMapSegmentRing(getSimpleCoords(feature)));

    case IFMEFeature.FME_GEOM_DONUT:
      return getDonutCoords(feature);
    case IFMEFeature.FME_GEOM_AGGREGATE:
      return getAggregateCoords(feature);
    case IFMEFeature.FME_GEOM_UNDEFINED:
      break;
    case IFMEFeature.FME_GEOM_PIP:
    default:
      System.err.println("Invalid fme_geometry."); //$NON-NLS-1$
    }
    return null;
  }

  private IlvMapLineString getSimpleCoords(IFMEFeature feature) {
    IlvMapLineString primitivePointList = new IlvMapLineString();
    for (int i = 0; i < feature.numCoords(); i++) {
      IlvCoordinate c = new IlvCoordinate(feature.getXCoordinate(i), feature.getYCoordinate(i));
      primitivePointList.addPoint(c);
    }
    return primitivePointList;
  }

  private IlvMapMultiPoint getPointCoords(IFMEFeature feature) {
    IlvMapMultiPoint primitivePointList = new IlvMapMultiPoint();
    for (int i = 0; i < feature.numCoords(); i++) {
      IlvCoordinate c = new IlvCoordinate(feature.getXCoordinate(i), feature.getYCoordinate(i));
      primitivePointList.addPoint(c);
    }
    return primitivePointList;
  }

  private IlvMapText getTextCoords(IFMEFeature feature) throws FMEException {
    String str = feature.getStringAttribute(kFME_Text_String);
    IlvCoordinate c = new IlvCoordinate(feature.getXCoordinate(0), feature.getYCoordinate(0));
    return new IlvMapText(c, str);
  }

  private IlvMapLineString getArcCoords(IFMEFeature feature) throws FMEException {
    double centerX = feature.getXCoordinate(0);
    double centerY = feature.getYCoordinate(0);
    double rotation = feature.getDoubleAttribute(kFME_Rotation);
    double primaryAxis = feature.getDoubleAttribute(kFME_Primary_Axis);
    double secondaryAxis = feature.getDoubleAttribute(kFME_Secondary_Axis);
    double startAngle = 0;
    double sweepAngle = 360;
    double endAngle = 360;
    String strFme_Type = feature.getStringAttribute(kFME_Type);
    if (strFme_Type.compareTo(kFME_Arc) == 0) {
      startAngle = feature.getDoubleAttribute(kFME_Start_Angle);
      sweepAngle = feature.getDoubleAttribute(kFME_Sweep_Angle);
      endAngle = startAngle + sweepAngle;
    }
    feature.convertArcToPoints(centerX, centerY, primaryAxis, secondaryAxis, 0, startAngle, endAngle, rotation);
    return getSimpleCoords(feature);
  }

  private IlvMapPolygon getDonutCoords(IFMEFeature feature) throws FMEException {
    IlvMapPolygon geom = null;
    IFMEFeatureVector featureVector = fmeSession.createFeatureVector();
    feature.getDonutParts(featureVector);
    for (int j = 0; j < featureVector.entries(); j++) {
      IFMEFeature polygonFeature = featureVector.getAt(j);
      if (geom == null) {
        geom = new IlvMapPolygon(new IlvMapSegmentRing(getSimpleCoords(polygonFeature)));
      } else {
        geom.addInteriorRing(new IlvMapSegmentRing(getSimpleCoords(polygonFeature)));
      }
      polygonFeature.dispose();
      polygonFeature = null;
    }
    featureVector.dispose();
    featureVector = null;
    return geom;
  }

  private IlvMapGeometryCollection getAggregateCoords(IFMEFeature feature) throws FMEException {
    IlvMapGeometryCollection geom = new IlvMapGeometryCollection();
    IFMEFeatureVector featureVector = fmeSession.createFeatureVector();
    feature.splitAggregate(featureVector, false);
    for (int j = 0; j < featureVector.entries(); j++) {
      IFMEFeature featureElem = featureVector.getAt(j);
      geom.addGeometry(getFeatureCoords(featureElem));
      featureElem.dispose();
      featureElem = null;
    }
    featureVector.dispose();
    featureVector = null;
    return geom;
  }

  static final private IlvWKTCoordinateSystemFactory wktf = new IlvWKTCoordinateSystemFactory();

  /**
   * Reads the next JViews Feature and sets its geometry, attributes, and
   * possibly its coordinate system.
   * 
   * @return The next JViews Maps feature in order.
   * @throws IOException
   */
  public IlvMapFeature getNextFeature() throws IOException {

    IFMEFeature feature = fmeSession.createFeature();
    IlvMapFeature ifeature = new IlvMapFeature();
    try {
      String type = null;
      while (fmeReader.read(feature)) {
        type = feature.getFeatureType();
        if (type != null && type.length() != 0) {
          break;
        }
      }
      if (type == null || type.length() == 0) {
        feature.dispose();
        return null;
      }
      String coordsys = feature.getCoordSys();
      if (coordsys != null && coordsys.length() != 0) {
        /* Perforce reproject */
        String wkt = fmeSession.coordSysManager().getCoordSysAsOGCDef(coordsys);
        try {
          IlvCoordinateSystem cs = wktf.fromWKT(wkt);
          ifeature.setCoordinateSystem(cs);
        } catch (Exception e) {
          System.err.println("Cannot reproject feature " + e); //$NON-NLS-1$
          // ignore
        }
      }
      IlvMapGeometry geom = getFeatureCoords(feature);
      ifeature.setGeometry(geom);
      IlvAttributeInfoProperty attrInfos = (IlvAttributeInfoProperty) fmeAttributeInfoProperties.get(type);
      if (attrInfos == null) {
        ArrayList<String> attributeNames = new ArrayList<String>();
        feature.getAllAttributeNames(attributeNames);
        Iterator<?> iterator = attributeNames.iterator();

        while (iterator.hasNext()) {
          String attributeName = (String) iterator.next();
          if (attributeName.startsWith("*")) { //$NON-NLS-1$
            iterator.remove();
          } else if (attributeName.startsWith("fme_")) {//$NON-NLS-1$
            iterator.remove();
          }
        }

        attributeNames.add(IlvFMEReader.FME_FEATURE_TYPE);
        String names[] = (String[]) attributeNames.toArray(new String[0]);
        Class<?> classes[] = new Class[names.length];
        boolean nullables[] = new boolean[names.length];
        Arrays.fill(classes, IlvStringAttribute.class);
        Arrays.fill(nullables, true);
        attrInfos = new IlvAttributeInfoProperty(names, classes, nullables);
        fmeAttributeInfoProperties.put(type, attrInfos);
      }
      ifeature.setAttributeInfo(attrInfos);
      IlvFeatureAttribute values[] = new IlvFeatureAttribute[attrInfos.getAttributesCount()];
      for (int i = 0; i < values.length; i++) {
        String attribute = attrInfos.getAttributeName(i);
        if (IlvFMEReader.FME_FEATURE_TYPE.equals(attribute)) {
          values[i] = new IlvStringAttribute(type);
        } else if (feature.attributeExists(attribute)) {
          values[i] = new IlvStringAttribute(feature.getStringAttribute(attribute));
        } else {
          values[i] = null;
        }
      }
      ifeature.setAttributes(new IlvFeatureAttributeProperty(attrInfos, values));
    } catch (FMEException e) {
      ifeature = null;
      throw new IOException(e.getMessage() + " " + e.getErrorNumber() + " " + e.getFMEStackTrace()); //$NON-NLS-1$//$NON-NLS-2$
    }
    feature.dispose();
    return ifeature;
  }
}