import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import org.opengis.referencing.operation.MathTransform;

import de.micromata.opengis.kml.v_2_2_0.AltitudeMode;
import de.micromata.opengis.kml.v_2_2_0.ColorMode;
import de.micromata.opengis.kml.v_2_2_0.Document;
import de.micromata.opengis.kml.v_2_2_0.Folder;
import de.micromata.opengis.kml.v_2_2_0.IconStyle;
import de.micromata.opengis.kml.v_2_2_0.Kml;
import de.micromata.opengis.kml.v_2_2_0.LineString;
import de.micromata.opengis.kml.v_2_2_0.LinearRing;
import de.micromata.opengis.kml.v_2_2_0.Link;
import de.micromata.opengis.kml.v_2_2_0.Location;
import de.micromata.opengis.kml.v_2_2_0.Model;
import de.micromata.opengis.kml.v_2_2_0.Orientation;
import de.micromata.opengis.kml.v_2_2_0.Placemark;
import de.micromata.opengis.kml.v_2_2_0.Point;
import de.micromata.opengis.kml.v_2_2_0.Polygon;
import de.micromata.opengis.kml.v_2_2_0.Scale;
import de.micromata.opengis.kml.v_2_2_0.Style;
import fr.ocelet.runtime.List;

/**
 * An object that can be progressively filled with timestamped folders,
 * containing geographic data and then save in a KML format file.
 * 
 * @author pdegenne@teledetection.fr
 */
public class KmlExport {

	private final String ERR_HEADER = "Datafacer ShapeFile KmlExport: ";
	private final String MAX_HEIGHT_STYLE = "Max_Height_Style";
	private Kml kml;
	private Document doc;
	private MathTransform mt;

	/**
	 * An empty constructor is mandatory for Ocelet datafacers.
	 */
	public KmlExport() {
		kml = new Kml();
		doc = kml.createAndSetDocument();
		defStyle("_defaultStyle", 1.0, "AF000000", "AFFFFFFF");
		defStyle("Max_Height_Style", 1.0, "7F000000", "7FFFFFFF");
	}

	/**
	 * Initializes an empty kml object and prepares a Math coordinates
	 * transformation from the ShapeFile crs to the WGS84 Lat/Long crs.
	 * 
	 * @param sf
	 *            A ShapeFile having a properly initialized coordinates system
	 */
	public void setShapeFile(ShapeFile sf) {
		mt = sf.getTransformCrs("EPSG:4326");
	}

	/**
	 * Add the content of a ShpRecord a the end of the KmlExport content. The
	 * coordinates will be transformed to WGS84 while being added to the
	 * KmlExport. The content of the ShpRecord is exported in the form of one
	 * folder having a name and timespan dates.
	 * 
	 * @param caption
	 *            The title of the folder that will be created for this
	 *            ShpRecord
	 * @param beginDate
	 *            Begining date of the timespan
	 * @param endDate
	 *            End date of the timespan
	 * @param sr
	 *            ShpRecord containing the data to export in one folder.
	 */
	public void addRecord(String caption, String beginDate, String endDate,
			ShpRecord sr) {
		addStyledRecord(caption, beginDate, endDate, sr, "_defaultStyle");
	}

	/**
	 * Add the content of a ShpRecord a the end of the KmlExport content. The
	 * coordinates will be transformed to WGS84 while being added to the
	 * KmlExport. The content of the ShpRecord is exported in the form of one
	 * folder having a name and timespan dates.
	 * 
	 * @param caption
	 *            The title of the folder that will be created for this
	 *            ShpRecord
	 * @param beginDate
	 *            Begining date of the timespan
	 * @param endDate
	 *            End date of the timespan
	 * @param sr
	 *            ShpRecord containing the data to export in one folder.
	 */
	public void addStyledRecord(String caption, String beginDate,
			String endDate, ShpRecord sr, String styleName) {
		addStyledRecordEx(caption, beginDate, endDate, sr, styleName, 0.0);
	}

	/**
	 * Add the content of a ShpRecord a the end of the KmlExport content. The
	 * coordinates will be transformed to WGS84 while being added to the
	 * KmlExport. The content of the ShpRecord is exported in the form of one
	 * folder having a name and timespan dates.
	 * 
	 * @param caption
	 *            The title of the folder that will be created for this
	 *            ShpRecord
	 * @param beginDate
	 *            Begining date of the timespan
	 * @param endDate
	 *            End date of the timespan
	 * @param sr
	 *            ShpRecord containing the data to export in one folder.
	 * @param height
	 *            Height of the feature to draw. If > 0 the feature will be
	 *            shown extruded to the given height (relative to the ground
	 *            level). If <= 0 the feature will be drawn flat on the ground.
	 */
	public void addStyledRecordEx(String caption, String beginDate,
			String endDate, ShpRecord sr, String styleName, double height) {
		Folder fold = doc.createAndAddFolder();
		fold.withName(caption).createAndSetTimeSpan().withBegin(beginDate)
				.withEnd(endDate);
		if (sr.isMPoints()) {
			MPoints pt = sr.getTransformedMPoints(mt);
			int pllsz = pt.nbPoints();
			for (int pli = 0; pli < pllsz; pli++) {
				Position plot = pt.getPoint(pli);
				Placemark placemark = fold.createAndAddPlacemark()
						.withStyleUrl("#" + styleName);
				Point ls = placemark.createAndSetPoint();
				if (height > 0.0) {
					ls.setExtrude(true);
					ls.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND);
					ls.addToCoordinates(plot.x, plot.y, height);
				} else
					ls.addToCoordinates(plot.x, plot.y);
			}
		} else if (sr.isPolyline()) {
			Polyline pll = sr.getTransformedPolyline(mt);
			int pllsz = pll.nbLines();
			for (int pli = 0; pli < pllsz; pli++) {
				List<Position> pline = pll.getLine(pli);
				Placemark placemark = fold.createAndAddPlacemark()
						.withStyleUrl("#" + styleName);
				LineString ls = placemark.createAndSetLineString();
				if (height > 0.0) {
					ls.setExtrude(true);
					ls.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND);
					for (Position pos : pline) {
						ls.addToCoordinates(pos.x, pos.y, height);
					}
				} else
					for (Position pos : pline) {
						ls.addToCoordinates(pos.x, pos.y);
					}
			}
		} else if (sr.isSPolygon()) {
			MPolygon mpl = sr.getTransformedMPolygon(mt);
			int nbpolys = mpl.nbPolys();

			for (int pli = 0; pli < nbpolys; pli++) {
				SPolygon polyg = mpl.getSPolygon(pli);
				Placemark placemark = fold.createAndAddPlacemark()
						.withStyleUrl("#" + styleName);
				Polygon pol = placemark.createAndSetPolygon();
				LinearRing shell_lr = pol.createAndSetOuterBoundaryIs()
						.createAndSetLinearRing();
				// Shell
				if (height > 0.0) {
					pol.setExtrude(true);
					pol.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND);
					for (Position pos : polyg.getShell()) {
						shell_lr.addToCoordinates(pos.x, pos.y, height);
					}
				} else {
					pol.setTessellate(true);
					for (Position pos : polyg.getShell()) {
						shell_lr.addToCoordinates(pos.x, pos.y);
					}
				}
				// Holes
				for (int hi = 0; hi < polyg.getNbHoles(); hi++) {
					LinearRing hole_lr = pol.createAndAddInnerBoundaryIs()
							.createAndSetLinearRing();
					if (height > 0.0) {
						pol.setExtrude(true);
						pol.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND);
						for (Position pos : polyg.getHole(hi)) {
							hole_lr.addToCoordinates(pos.x, pos.y, height);
						}
					} else {
						pol.setTessellate(true);
						for (Position pos : polyg.getHole(hi)) {
							hole_lr.addToCoordinates(pos.x, pos.y);
						}
					}

				}

			}
		}
	}

	/**
	 * Add the content of a ShpRecord a the end of the KmlExport content. The
	 * coordinates will be transformed to WGS84 while being added to the
	 * KmlExport. The content of the ShpRecord is exported in the form of one
	 * folder having a name and timespan dates.
	 * 
	 * @param caption
	 *            The title of the folder that will be created for this
	 *            ShpRecord
	 * @param beginDate
	 *            Begining date of the timespan
	 * @param endDate
	 *            End date of the timespan
	 * @param sr
	 *            ShpRecord containing the data to export in one folder.
	 */
	public void addStyledRecordEx2(String caption, String beginDate,
			String endDate, ShpRecord sr, String styleName, double height,
			double maxheight) {
		addStyledRecordEx(caption, beginDate, endDate, sr, styleName, height);
		addStyledRecordEx(caption, beginDate, endDate, sr, MAX_HEIGHT_STYLE,
				maxheight);
	}

	/**
	 * Adds a KML Extruded Label (see
	 * https://kml-samples.googlecode.com/svn/trunk
	 * /interactive/index.html#./Point_Placemarks/Point_Placemarks.Extruded.kml)
	 * 
	 * @param xpos
	 *            Latitude of the label's ground position
	 * @param ypos
	 *            Lontitude of the label's ground position
	 * @param height
	 *            Height at which the label should be displayed
	 * @param beginDate
	 *            Begining date of the timespan
	 * @param endDate
	 *            End date of the timespan
	 * @param name
	 *            Title (always displayed)
	 * @param description
	 *            Description (only displayed on mouse click on the label)
	 */
	public void addLabel(double xpos, double ypos, double height,
			String beginDate, String endDate, String name, String description,
			String styleName) {
		Folder fold = doc.createAndAddFolder();
		fold.withName(name).createAndSetTimeSpan().withBegin(beginDate)
				.withEnd(endDate);
		Placemark placemark = fold.createAndAddPlacemark().withStyleUrl(
				"#" + styleName);
		Point ls = placemark.createAndSetPoint();
		ls.setExtrude(true);
		ls.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND);
		ls.addToCoordinates(xpos, ypos, height);
		placemark.setName(name);
		placemark.setDescription(description);
	}

	/**
	 * Adds a 3D model (most probably made with Sketchup)
	 * 
	 * @param xpos
	 *            Latitude of the model's position
	 * @param ypos
	 *            Longitude of the model's position
	 * @param orientation
	 *            Orientation
	 * @param scale
	 *            Scale
	 * @param beginDate
	 *            Begining date of the timespan
	 * @param endDate
	 *            End date of the timespan
	 * @param daefile
	 *            Collada (.dae) file
	 */
	public void addModel(double xpos, double ypos, double orientation,
			double scale, String beginDate, String endDate, String daefile) {
		Folder fold = doc.createAndAddFolder();
		fold.createAndSetTimeSpan().withBegin(beginDate).withEnd(endDate);
		Placemark placemark = fold.createAndAddPlacemark();
		Model model = placemark.createAndSetModel();
		model.setAltitudeMode(AltitudeMode.RELATIVE_TO_GROUND);
		Location loc = new Location().withLatitude(ypos).withLongitude(xpos)
				.withAltitude(0.0);
		model.setLocation(loc);
		model.setScale(new Scale().withX(scale).withY(scale).withZ(scale));
		model.setLink(new Link().withHref(daefile));
		model.setOrientation(new Orientation().withHeading(orientation));
	}

	/**
	 * Saves the content of this KmlExport object into a KML format file.
	 * 
	 * @param fileName
	 *            Name and path of the file to save.
	 */
	public void saveAsKml(String fileName) {
		try {
			kml.marshal(new File(fileName));
		} catch (FileNotFoundException e) {
			System.out.println(ERR_HEADER + "Failed to open " + fileName
					+ " for saving.");
		}
	}

	/**
	 * Saves the content of this KmlExport object into a KMZ format file.
	 * 
	 * @param fileName
	 *            Name and path of the file to save.
	 */
	public void saveAsKmz(String fileName) {
		try {
			kml.marshalAsKmz(fileName);
		} catch (IOException e) {
			System.out.println(ERR_HEADER + "Failed to open " + fileName
					+ " for saving.");
		}
	}

	/**
	 * Defines a new style to be used with addStyledRecord
	 * 
	 * @param name
	 *            Style name. Must be unique.
	 * @param lineWidth
	 *            Width of the line. A thin line should have a value 1.0.
	 * @param lineColor
	 *            Color of the line in HEX text format : ABGR. Example
	 *            "7FFFFF44" is half transparent yellow.
	 * @param fillColor
	 *            Polygon fill color. HEX text format ABGR also.
	 */
	public void defStyle(String name, double lineWidth, String lineColor,
			String fillColor) {
		Style style = doc.createAndAddStyle().withId(name);
		style.createAndSetLineStyle().withColor(lineColor).withWidth(lineWidth);
		style.createAndSetPolyStyle().withColor(fillColor)
				.withColorMode(ColorMode.NORMAL);
	}

	/**
	 * Defines a new IconStyle to be used with addStyledRecord
	 * 
	 * @param name
	 *            Style name. Must be unique.
	 * @param iconFile
	 *            Name of an image (png) file. The path is relative to the
	 *            location where the KML file will be saved.
	 * @param scale
	 *            Scale factor applied to the image. Choose 1.0 if you don't
	 *            know.
	 * @param heading
	 *            Orientation heading of the icon. Should be a value between 0.0
	 *            and 360.0. 0.0 is the normal orientation of the icon. Higher
	 *            numbers apply a clockwise rotation of the icon.
	 */

	public void defIconStyle(String name, String iconFile, double scale,
			double heading) {
		Style style = doc.createAndAddStyle().withId(name);
		IconStyle iconstyle = style.createAndSetIconStyle().withScale(scale)
				.withHeading(heading);
		iconstyle.createAndSetIcon().withHref(iconFile);
	}

}
