package org.mbari.seepsearch.grid;

// For converting between coordinate systems
import org.mbari.gis.util.GisUtil;
import java.awt.geom.Point2D;

// Standard imports
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;

// For Reading navigation files
import org.mbari.io.*;
import org.mbari.seepsearch.io.*;
import org.mbari.seepsearch.util.*;
import org.mbari.seepsearch.core.*;

// Useful math functions, especially MathUtil.near();
import org.mbari.util.MathUtil;

// Needed for establishing acceptable altitude limits
import org.mbari.seepsearch.core.RovAltitudeFilter;

/**
 * <p>Utilites for working with RasterGrids</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: GridUtil.java,v $
 * Revision 1.16  2002/09/10 18:17:26  brian
 * Added a method for estimating the depth of an RovFix based on position.
 *
 * Revision 1.15  2002/04/22 18:56:40  brian
 * Modified to use IRovNavigationIterator interface
 *
 * Revision 1.14  2002/04/17 21:48:54  brian
 * Adding Vars Database support. Moving to IRovNavigationIterator as the common interface between disparate datasets
 *
 * Revision 1.13  2002/02/27 23:55:28  brian
 * Modified GridUtil to ignore rov fixes equal to 0 pressure. Other minor bug fixes.
 *
 * Revision 1.12  2002/02/25 22:58:16  brian
 * Bug fixes.
 *
 * Revision 1.11  2002/02/25 16:40:40  brian
 * Modified tools to ignore eastings and northings columns.
 *
 * Revision 1.10  2002/01/30 00:23:56  brian
 * Added comments to all classes and methods. Tested the accuracy of AsciiRasterGridReader and AsciiRasterGridWriter. Also checked the the GridUtil was populating the spatial grids correctly and accurately. The data files generated matched up perfectly with the orginal text files. Also implemented an RovAltitudeFilter class to encapsulate different filtering methods.
 *<br><hr><br>
 *
 * @author  : $Author: brian $
 * @version : $Revision: 1.16 $
 */
public class GridUtil {

    private GridUtil() {
    }

    public final static ArrayList getDepthFromGrid(RasterGrid grid,
            IRovNavigationIterator iterator, int utmZone) throws IOException {

         ////////////////////////////////////////////////////////////////////////
        // Read and process the RasterGrid

        ArrayList list = new ArrayList(); // For storing RovFixes

        // Get references to data in the RasterGrid
        double[] x = grid.getX();
        double[] y = grid.getY();
        float[][] z = grid.getZ();

        // Need to account for hemisphere. MathUtil.near() which is used later
        // requres that the array argument is sorted. In N-hemsisphere latitude
        // is in inverse order (i.e. 37 degrees is north of 36 degrees)
        boolean isXInverted = false;
        double minX = x[0];
        double maxX = x[x.length - 1];
        double[] xSorted = null;
        if (minX > maxX) {
            isXInverted = true;
            xSorted = new double[x.length];
            for (int i = 0; i < x.length; i++) {
                xSorted[i] = x[x.length - i - 1];
            }
        } else {
            xSorted = x;
        }

        boolean isYInverted = false;
        double minY = y[0];
        double maxY = y[y.length - 1];
        double[] ySorted = null;
        if (minY > maxY) {
            isYInverted = true;
            ySorted = new double[y.length];
            for (int i = 0; i < y.length; i++) {
                ySorted[i] = y[y.length - i - 1];
            }
        } else {
            ySorted = y;
        }

        ////////////////////////////////////////////////////////////////////////
        // Loop through each position in the navigation file increment the
        // pixel containing it in the grid.
        double longitude; // Geo decimal degrees
        double latitude; // Geo decimal degrees
        double altitude; // meters
        double depth; // meters
        boolean acceptRovFix = false;
        int goodLat;
        int goodLon;
        Point2D navPoint = null;    // Storage for eastings and northings
        RovFix fix = null;
        while(iterator.hasNext()) {

            fix = iterator.getNextRovFix();

            if (fix == null) {
                continue;
            }

            // Get the ROV fix in geographic coordinates
            longitude = fix.getLongitude();
            latitude = fix.getLatitude();
            altitude = fix.getAltitude();
            depth = fix.getDepth();



            // Convert navigation points to the correct grid coordinate system
            if (utmZone != -1) {  // Use UTM coordinates. Convert each geographic fix to utm
                navPoint = GisUtil.geoToUtm(longitude, latitude, utmZone);
            } else {    // Use geographic coordinates
                navPoint = new Point2D.Double(longitude, latitude);
            }

            // If an altitude filter is used apply it
            acceptRovFix = true;


            if (acceptRovFix) {
                goodLon = MathUtil.near(xSorted, navPoint.getX());
                goodLat = MathUtil.near(ySorted, navPoint.getY());

                // Skip if it's a bad value
                if ((goodLat == -1) || (goodLon == -1)) {
                    continue;
                }

                if (isXInverted) {
                    goodLon = x.length - goodLon - 1;
                }

                if (isYInverted) {
                    goodLat = y.length - goodLat - 1;
                }

                try {
                    fix.setDepth(z[goodLat][goodLon]);
                    list.add(fix);
                } catch (ArrayIndexOutOfBoundsException e) {
                    System.err.println("Invalid Index: z[" + goodLat + "][" +
                        goodLon + "] is outside boudnaries of z[" + z.length + "][" +
                        z[0].length);
                }
            }
        }
        return list;

    }

    /**
     * Populate a geographic RasterGrid with Geographic coordinate data from
     * an ROV navigation file. The z data in the RasterGrid will be updated by
     * this method so no return value is needed. Filtering by only accepting
     * points withing +/- a specifed altitude off the bottom is implemented.
     * @param grid Grid that the data will be writtien into
     * @param navFile The name of the file containing the ROV navigation data.
     * @param altitudeOffset Acceptable altitude off the bottom. If a point
     * is not within this range it is ignored. If this is set to NaN, the use of
     * altitudeOffset is ignored and all points in the nav file are counted
     * @param utmZone use -1 if the grid data is in geographic coordinates
     */

     /**
      *  Populate a geographic RasterGrid with Geographic coordinate data from
      * an ROV navigation file. The z data in the RasterGrid will be updated by
      * this method so no return value is needed. Filtering by only accepting
      * points withing +/- a specifed altitude off the bottom is implemented.
      * @param grid Grid that the data will be written into
      * @param navFile The name of the file containing the ROV navigation data.
      * @param minAltitude The minimum altitude that will be accepted. Use Double.NaN
      * if all data below maxAltitude is acceptable.
      * @param maxAltitude The maximum altitude that will be accepted. Use Double.NaN
      * if all data is acceptable
      * @param utmZone The utm zone to convert the geographic coordinates too.
      * The utm zone used here should match the zone of the <b>grid</b>. If the
      * grid is in geographic coordintates use -1 as the utm zone so that no conversion
      * occurs.
      * @throws IOException
      */
    public final static void populateGeoGridWithGeoNavData(RasterGrid grid,
            File navFile, RovAltitudeFilter filter, int utmZone)
            throws IOException {

        RovNavigationFile f = new RovNavigationFile(navFile);
        f.read();

        populateGeoGridWithGeoNavData(grid, f, filter, utmZone);


    }

     /**
      *  Populate a geographic RasterGrid with Geographic coordinate data from
      * an ROV navigation file. The z data in the RasterGrid will be updated by
      * this method so no return value is needed. Filtering by only accepting
      * points withing +/- a specifed altitude off the bottom is implemented.
      * @param grid Grid that the data will be written into.
      * @param navFile The name of the file containing the ROV navigation data.
      * @param minAltitude The minimum altitude that will be accepted. Use Double.NaN
      * if all data below maxAltitude is acceptable.
      * @param maxAltitude The maximum altitude that will be accepted. Use Double.NaN
      * if all data is acceptable
      * @throws IOException
      */
    public final static void populateGeoGridWithGeoNavData(RasterGrid grid,
            File navFile, RovAltitudeFilter filter)
            throws IOException {

        populateGeoGridWithGeoNavData(grid, navFile, filter, -1);

    }

    public final static void populateGeoGridWithGeoNavData(RasterGrid grid,
            IRovNavigationIterator iterator, RovAltitudeFilter filter, int utmZone)
            throws IOException {

        ////////////////////////////////////////////////////////////////////////
        // Read and process the RasterGrid

        // Get references to data in the RasterGrid
        double[] x = grid.getX();
        double[] y = grid.getY();
        float[][] z = grid.getZ();

        // Need to account for hemisphere. MathUtil.near() which is used later
        // requres that the array argument is sorted. In N-hemsisphere latitude
        // is in inverse order (i.e. 37 degrees is north of 36 degrees)
        boolean isXInverted = false;
        double minX = x[0];
        double maxX = x[x.length - 1];
        double[] xSorted = null;
        if (minX > maxX) {
            isXInverted = true;
            xSorted = new double[x.length];
            for (int i = 0; i < x.length; i++) {
                xSorted[i] = x[x.length - i - 1];
            }
        } else {
            xSorted = x;
        }

        boolean isYInverted = false;
        double minY = y[0];
        double maxY = y[y.length - 1];
        double[] ySorted = null;
        if (minY > maxY) {
            isYInverted = true;
            ySorted = new double[y.length];
            for (int i = 0; i < y.length; i++) {
                ySorted[i] = y[y.length - i - 1];
            }
        } else {
            ySorted = y;
        }

        ////////////////////////////////////////////////////////////////////////
        // Loop through each position in the navigation file increment the
        // pixel containing it in the grid.
        double longitude; // Geo decimal degrees
        double latitude; // Geo decimal degrees
        double altitude; // meters
        double depth; // meters
        boolean acceptRovFix = false;
        int goodLat;
        int goodLon;
        Point2D navPoint = null;    // Storage for eastings and northings
        RovFix fix = null;
        while(iterator.hasNext()) {

            fix = iterator.getNextRovFix();

            if (fix == null) {
                continue;
            }

            // Get the ROV fix in geographic coordinates
            longitude = fix.getLongitude();
            latitude = fix.getLatitude();
            altitude = fix.getAltitude();
            depth = fix.getDepth();

            // Ignore all surface data
            if (depth == 0) {
                continue;
            }

            // Convert navigation points to the correct grid coordinate system
            if (utmZone != -1) {  // Use UTM coordinates. Convert each geographic fix to utm
                navPoint = GisUtil.geoToUtm(longitude, latitude, utmZone);
            } else {    // Use geographic coordinates
                navPoint = new Point2D.Double(longitude, latitude);
            }

            // If an altitude filter is used apply it
            if (filter != null) {
                acceptRovFix = filter.accept(altitude);
            } else {
                acceptRovFix = true;
            }

            if (acceptRovFix) {
                goodLon = MathUtil.near(xSorted, navPoint.getX());
                goodLat = MathUtil.near(ySorted, navPoint.getY());

                // Skip if it's a bad value
                if ((goodLat == -1) || (goodLon == -1)) {
                    continue;
                }

                if (isXInverted) {
                    goodLon = x.length - goodLon - 1;
                }

                if (isYInverted) {
                    goodLat = y.length - goodLat - 1;
                }

                try {
                    z[goodLat][goodLon]++; // increment the appropriate pixel
                } catch (ArrayIndexOutOfBoundsException e) {
                    System.err.println("Invalid Index: z[" + goodLat + "][" +
                        goodLon + "] is outside boudnaries of z[" + z.length + "][" +
                        z[0].length);
                }
            }
        }
    }


}