package org.mbari.seepsearch.tools;

import java.awt.geom.*;
import java.io.*;
import java.text.*;
import java.util.*;

import org.mbari.gis.util.*;
import org.mbari.io.*;
import org.mbari.seepsearch.grid.*;
import org.mbari.util.*;

 /**
 * <p>Calculates Altitude between the ROV navigation data and a user supplied
 * bathymetry grid</p>
 *
 * <p>The Seep Search project is a sub-project of the Continental
 * Margin Fluids and Gases project (200010). It involves the analysis of
 * archived ROV data to obtain understanding of seep distritution in Monterey
 * Bay.</p>
 *
 * <p>The Purpose of this Java package is to intercompare ROV Navigation files,
 * VIMS data on seep community observations and the EM-300 bathymetry. We are
 * looking for locations where the ROV navigation files and the EM-300 data
 * have similar depths between the 2 data sets, which implies that the ROV is
 * observing the sea floor.</p><hr><br>
 *
 * <p>MBARI provides this documentation and code &quot;as is&quot;, with no
 * warranty, express or implied, of its quality or consistency. It is provided
 * without support and without obligation on the part of the Monterey Bay
 * Aquarium Research Institute to assist in its use, correction, modification,
 * or enhancement. This information should not be published or distributed to
 * third parties without specific written permission from MBARI.</p><br>
 *
 * Copyright 2002 MBARI.<br>
 * MBARI Proprietary Information. All rights reserved.<br><hr><br>
 *
 * $Log: RovAltitudeCalculator.java,v $
 * Revision 1.7  2002/08/30 21:45:31  brian
 * Minor modifications
 *
 * Revision 1.6  2002/07/02 21:08:34  brian
 * Minor modifications
 *
 * Revision 1.5  2002/04/17 21:48:55  brian
 * Adding Vars Database support. Moving to IRovNavigationIterator as the common interface between disparate datasets
 *
 * Revision 1.4  2002/02/25 22:58:16  brian
 * Bug fixes.
 *
 * Revision 1.3  2002/02/25 16:40:09  brian
 * Modified tools to ignore eastings and northings columns.
 *
 * Revision 1.2  2002/02/09 00:07:19  brian
 * Working tools to calculate ROV altitude. There's a glitch that occurs for a few files. Need to look into this
 *<br><hr><br>
 *
 * @author  : $Author: brian $
 * @version : $Revision: 1.7 $
 */
public class RovAltitudeCalculator extends RovBathymetryProcess {

    public RovAltitudeCalculator(String rovDataPath, String outDataPath,
            String asciiBathymetryGrid) throws GridException, IOException {
        super(rovDataPath, asciiBathymetryGrid); // Set the locationof the ROV data
        oc = new RovNavFileOutputComponent(rovDataPath);
        oc.setOutputPath(outDataPath);
        oc.process();
    }

    /**
     * Internal process to be applied to each file.
     * @param navFile
     */
    public void internalProcess(File navFile) {
        try {
            calculate(navFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Applies the internalProcessing to only one file.
     * @param navFile
     */
    public void process(String navFile) {
        internalProcess(new File(navFile));
    }

    /**
     * This calculates depth, altitude and bathymetry using nearest neighbor
     * interpolation.
     * @param navFile
     * @throws IOException
     */
    private void calculate(File navFile) throws IOException {

        ////////////////////////////////////////////////////////////////////////
        // Read the navfile

        // read it as a comma delimited file.
        DelimitedFile navReader = null;
        try {
            navReader = new DelimitedFile(navFile);
            navReader.setCommentTag("#");                      // lines starting with # will be ignored
            navReader.setHasColumnNames(true);                 // The default is false
            navReader.setDelimiter(",");                       // Comma delimited file.
            navReader.read();                              // Read the file
        } catch (IOException e) {
            System.err.println("Unable to read " + navReader.getFile().getName());
            return;                                            // Skip
        }

        // Pull the data out and check that it contains the required data columns
        double[][] data = navReader.getData();              // Get reference to the data
        String[] columnNames = navReader.getColumnNames(); // Got column names
        List columnList = Arrays.asList(columnNames);   // Convert Array to a list

        // Find the headers that we want to include in the output file.
        columnIndices = new int[outputColumnNames.length];
        for (int i = 0; i < outputColumnNames.length; i++) {
            columnIndices[i] = columnList.indexOf(outputColumnNames[i]);
        }

        // A few columns that we need for processing
        int latColumn = columnList.indexOf("Lat");
        int lonColumn = columnList.indexOf("Lon");
        int pressColumn = columnList.indexOf("Pres");
        /** @todo This is currently set to look for the column Alti2 which is
         *  the new column created by matlab, if not found it uses Alti*/
        int altiColumn = columnList.indexOf("Alti2");
        if (altiColumn == -1) {
            altiColumn = columnList.indexOf("Alti");
        }
        /** @todo  The new files will use bottomDepth instead of BenthicZ. I'll
         *  need to change this line to look for the new header instead */
        int bathyColumn = columnList.indexOf("BottomDepth");
        if (bathyColumn == -1) {
            bathyColumn = columnList.indexOf("BenthicZ");
        }

        // Skip the file if it doens't have 3d coordinates
        if ((latColumn == -1) || (lonColumn == -1) || (pressColumn == -1)) {
            System.err.println(navReader.getFile().getName() + " is not an ROV " +
                    "navigation file...skipping");
            return;
        }

        ////////////////////////////////////////////////////////////////////////
        // Process the nav file

        // For calculation of altitude
        double pressure = 0;     // Decibars
        double[] depth = new double[navReader.getNumOfRows()];        // Depth, converted from pressure
        double[] altitude = new double[navReader.getNumOfRows()];     // ROV altitude above bottom. To be calculated.
        double[] benthicZ = new double[navReader.getNumOfRows()];     // Depth under ROV

        // For locating each ROV fix
        Point2D utmPoint = null; // For conversion of Geo to UTM
        Point2D geoPoint = null; // For storing the geo point
        Point2D navPoint = null; // Pointer to geo or utm fix,depending on isUtm()
        double latitude = 0;     // Decimal degrees
        double longitude = 0;    // Decimal degrees

        // Index of nearest neighbor of ROV fix on bathymetry grid
        int nearLatI = 0;        // Index of nearest Lat in the grid
        int nearLonI = 0;        // Index of nearest Lon in the grid

        // Reference the bathymetry grid data
        grid = reader.getGrid();
        float[][] bathymetry = grid.getZ();
        Rectangle2D rectangle = grid.getRectangle2D();

        boolean isFixInGrid = false;

        /*
           Loop through each row. Convert each ROV fix to UTM. Check to see if it
           has altitude. If not calculate altitude.
         */
        for (int i = 0; i < navReader.getNumOfRows(); i++) {


            // Pull ROV data needed for calculation
            pressure = data[i][pressColumn];
            latitude = data[i][latColumn];
            longitude = data[i][lonColumn];
            geoPoint = new Point2D.Double(longitude, latitude);

            // Convert pressure to depth using UNESCO 84
            depth[i] = OceanUtil.pressureToDepth(pressure, latitude);

            if (isUtm()) {
                // Convert geographic coordinates to utm
                utmPoint = GisUtil.geoToUtm(longitude, latitude, 10);
                navPoint = utmPoint;
            } else {
                navPoint = geoPoint;
            }

            // Assign NaN be default
            altitude[i] = Double.NaN;
            benthicZ[i] = Double.NaN;

            // Is the ROV fix within the bounds of the rectangle?
            if (rectangle.contains(navPoint)) {
                // Locate the nearest point on the bathygrid
                nearLatI = MathUtil.near(grid.getY(), navPoint.getY());
                nearLonI = MathUtil.near(grid.getX(), navPoint.getX());
                isFixInGrid = true;
            } else {
                nearLatI = -1;
                nearLonI = -1;
                isFixInGrid = false;
            }

            if ((overwrite) || (altiColumn == -1) || (Double.isNaN(data[i][altiColumn]))) {

                // Only calculate altitude if the fix is within the RasterGrid bounds
                if (isFixInGrid) {
                    // Calculate altitude in meters
                    try {
                        //altitude[i] = depth[i] - Math.abs((double) bathymetry[nearLatI][nearLonI]);
                        altitude[i] = Math.abs((double) bathymetry[nearLatI][nearLonI]) - depth[i];
                    } catch (ArrayIndexOutOfBoundsException e) {
                        System.out.println(" Error accessing grid[" + nearLatI + "][" +
                                nearLonI + "]");
                        e.printStackTrace();
                        System.exit(-1);
                    }
                }

            } else {
                altitude[i] = data[i][altiColumn];
            }

            // Get depth of bottom under ROV. Only get the bottom depth if it's
            // within the rasterGrid bounds
            if ((bathyColumn == -1) || (Double.isNaN(data[i][bathyColumn]))) {
                if (isFixInGrid) {
                    benthicZ[i] = Math.abs((double) bathymetry[nearLatI][nearLonI]);
                }
            } else {
                benthicZ[i] = Math.abs(data[i][bathyColumn]);
            }
        }

        // Write out the file.
        write(navReader, depth, altitude, benthicZ);


    }

    /**
     * Write out the new navigation file with the new data.
     * @param navFile
     * @param depth
     * @param altitude
     * @param benthicZ
     * @throws IOException
     */
    private void write(DelimitedFile navFile, double[] depth, double[] altitude,
            double[] benthicZ)
            throws IOException {

        double[][] data = navFile.getData();
        String delimiter = navFile.getDelimiter();
        // Get pointer to the file to create.
        File outNavFile = oc.getOutputFile(navFile.getFile());
        BufferedWriter out = new BufferedWriter(new FileWriter(
                outNavFile));

        // Need formatting objects
        NumberFormat nf = new DecimalFormat();
        nf.setGroupingUsed(false);
        nf.setMaximumFractionDigits(0);
        nf.setMinimumFractionDigits(0);
        nf.setMinimumIntegerDigits(1);




//        String[] existingColumns = {"Year", "Day", "Time", "Usec", "Lat", "Lon",
//                "East", "North", "Pres", "Head", "Alti", "Pitch", "Roll", "PosFlag",
//                "PresFlag", "HeadFlag", "AltiFlag", "AttitFlag"};
//        int[] precision = {0, 0, 0, 0, 6, 6, 0, 0, 2, 1, 2, 1, 1, 0, 0, 0, 0, 0};

        // Write out the line of comma seperated column headers
        for (int i = 0; i < outputColumnNames.length; i++) {
            out.write(outputColumnNames[i]);
            if (i < outputColumnNames.length - 1) {
                out.write(delimiter);
            }
        }
        out.newLine();

        // Write out rows of data
        for (int r = 0; r < navFile.getNumOfRows(); r++) {
            for (int c = 0; c < outputColumnNames.length; c++) {
                nf.setMaximumFractionDigits(precision[c]);
                nf.setMinimumFractionDigits(precision[c]);
                // Handle the 3 special case columns
                if (outputColumnNames[c].equalsIgnoreCase("Alti")) {
                    write(out, nf, altitude[r]);
                } else if (outputColumnNames[c].equalsIgnoreCase("Depth")) {
                    write(out, nf, depth[r]);
                } else if (outputColumnNames[c].equalsIgnoreCase("BottomDepth")) {
                    write(out, nf, benthicZ[r]);
                } else {
                    // If a column doesn't exist in the source file, fill
                    // it with NaN's in the new file.
                    if (columnIndices[c] != -1) {
                        write(out, nf, data[r][columnIndices[c]]);
                    } else {
                        write(out, nf, Double.NaN);
                    }
                }

                if (c < outputColumnNames.length - 1) {
                    out.write(delimiter);
                }
            }

            out.newLine();
        }

        out.flush();
        out.close();
    }

    /**
     * Overridden method from <code>RovNavFileAccessor</code>.
     * @param rovDataPath The root of the ROV nav file directory.
     */
    public void setRovDataPath(String rovDataPath) {
        this.rovDataPath = rovDataPath;
        oc = new RovNavFileOutputComponent(rovDataPath);
        try {
            oc.process();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Modification to write that checks for NaN's
     * @param out
     * @param nf
     * @param value
     * @throws IOException
     */
    private void write(Writer out, NumberFormat nf, double value) throws IOException {
        if (Double.isNaN(value)) {
            out.write("NaN");
        } else {
            out.write(nf.format(value));
        }
    }

    /**
     * Allow user to overwrite exisitn altitudes with new ones
     * @param overwrite default is false (don't overwrite).
     * True = overwrite any altitudes found in the file.
     */
    public void overWriteExistingAltitudes(boolean overwrite) {
        this.overwrite = overwrite;
    }

    /**
     * Reference to the RasterGrid within the AsciiRasterGridReader
     */
    private RasterGrid grid;

    /**
     * Component that creates the output directory tree (if needed) and supplies
     * the name of he file to write to.
     */
    RovNavFileOutputComponent oc;


    /**
     * Column names to be written in the outputfile
     */
    private String[] outputColumnNames = {"Year", "Day", "Time", "Usec", "Lat", "Lon",
                "Pres", "Head", "Alti", "Pitch", "Roll", "PosFlag",
                "PresFlag", "HeadFlag", "AltiFlag", "AttitFlag", "Depth", "BottomDepth"};

    /**
     * precision (actually the number of decimal places) of each colun
     *  represented in column names
     */
    private int[] precision = {0, 0, 0, 0, 6, 6, 2, 1, 2, 1, 1, 0, 0, 0, 0, 0, 2, 2};

    /**
     * Index of the columns in the original source file mapped to the format
     * specified in outputColumnNames
     */
    private int[] columnIndices;

    /**
     * Flag used to indicate if existing altitudes should be overwritten.
     * The default is false, don't overwrite the existing altitudes
     */
    private boolean overwrite = false;

}