


package org.mbari.io;

import java.io.*;
import java.util.*;

/**
 * <p>Read a delimited text file into a double array. The text file should only
 * contain values that can be converted to double values (i.e no columns of
 * dates like 22-Sep-1968).</p><br>
 *
 * Use as:<br>
 *  // Construct it.<br>
 *  DelimitedFileReader reader = new DelimitedFileReader("c:\somedatafile.txt");<br><br>
 *
 * // ***Set optional arguments***<br>
 * //If it contains commented columns set the coommentTag<br>
 * reader.setCommentTag("#") // lines starting with # will be ignored<br><br>
 *
 * // Does it contain a row of strings specifying what each column name is<br>
 * // For example is the first non-commented row something like:<br>
 * // date, time, latitude, longitude<br>
 * // If so you need to let the reader know with<br>
 *  reader.setHasColumnNames(true); // The default is false<br><br>
 *
 * // Set the delimiter...the default is any whitespace (space or tabs)<br>
 * reader.setDelimiter(",")  // Comma delimited file.<br><br>
 *
 * // Read the data<br>
 * reader.readFile();<br>
 * double[] data = reader.getData();<br>
 * String[] columnNames = reader.getColumnNames();<br><br>
 *
 * // Some hints to use for looking at column names<br>
 * List names = Arrays.asList(columnNames);<br>
 * int timeColumn = name.indexOf("time") // -1 if missing, here it should be 1<br>
 * <br><hr>
 * Lines with the first charater of '#' or '%' are assumed to be comments<br><hr>
 *
 * 12 Jul 1999; Created FlatFileReader<br>
 * 27 Aug 1999; Fixed a bug (off by one type) in <i>this.c</i> which prevented
 * data with the correct number of lines from being read
 * 06 Dec 2001;<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: DelimitedFile.java,v $
 * Revision 1.1  2002/04/17 21:48:34  brian
 * Adding Vars Database support. Moving to IRovNavigationIterator as the common interface between disparate datasets
 *
 * Revision 1.9  2002/02/25 16:40:40  brian
 * Modified tools to ignore eastings and northings columns.
 *
 * Revision 1.8  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
 *
 * Revision 1.7  2002/02/08 00:55:54  brian
 * Wokring on creating a tool to add rov altitude. utm coords, bathymetry and depth to ROV navigation files. The tools can be used iteratively so that if the ROV data is outside a grid you, it will fill in what it can but allow the user to rerun the data on another grid. THen it just fills in values that are missing.
 *
 * Revision 1.6  2002/02/06 00:39:07  brian
 * no message
 *
 * Revision 1.5  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.1 $
 */
public class DelimitedFile {

    // ///////////////////////////////////////
    // Constructors

    /**
     * Create a DelimitedFileReader.
     *
     * @param filename  Name of file to read
     */
    public DelimitedFile(String filename) throws IOException {
        this(new File(filename));
    }

    /**
     * Create a DelimitedFileReader and intialize memory.
     *
     * @param file  File object representing the file to be read
     */
    public DelimitedFile(File file) throws IOException {
        this.file = file;
    }


    // ///////////////////////////////////////
    // Public Methods

    /**
     * This method does the actual reading of the data file. It tokenizes the
     * line by using white spaces
     *
     * @exception IOException
     * @deprecated 08 Feb 2002; use read() instead
     */
    public void readFile() throws IOException {
        init();
        parse();
    }

    /**
     * This method does the actual reading of the data file. It tokenizes the
     * line by using white spaces
     *
     * @exception IOException
     */
    public void read() throws IOException {
        init();
        parse();
    }



    // ///////////////////////////////////////
    // Accessors

    /**
     * Accesor method to access the data in the flat file
     *
     * @return data  A double array of the data contained in the flat file
     */
    public double[][] getData() {
        return data;
    }


    /**
     * Accesor method to get the filename of read by the FlatFileReader
     *
     * @return filename  The name of the file used to construct a FlatFileReader
     * instance.
     * @deprecated 07 Feb 2002; use getFile() instead
     */
    public String getFilename() {
        return file.getName();
    }

    /**
     * Accesor method to get the file read by the FlatFileReader
     *
     * @return file  The the file used to construct a FlatFileReader
     * instance.
     */
    public File getFile() {
        return file;
    }



    /**
     * Return a specific column of data.
     *
     * @param column The column of data to fetch.
     * @return The column of data specified by <i>c</i>
     * @exception ArrayIndexOutOfBoundsException
     */
//	public double[] getColumn(int column) {
//		double[] out = new double[this.countRows()];
//
//		for (int row = 0; row < this.countRows(); row++) {
//			out[row] = this.data[row][column];
//		}
//
//		return out;
//	}

    /**
     * Method to get the number of rows in the data array
     *
     * @return The number of rows of data read.
     */
    public int getNumOfRows() {
        return numOfRows;
    }

    /**
     * Method to get the number of columns in the data array
     *
     * @return The number of columns of data read.
     */
    public int getNumOfColumns() {
        return numOfColumns;
    }

    // ///////////////////////////////////////
    // Protected Methods

    // ///////////////////////////////////////
    // Private Methods

    /**
     * Sets up the correct size array, called by the constructor
     */
    private void init() throws IOException {
        if ((!file.exists()) || (!file.canRead())) {
            throw new IOException("Unable to read " + file.getAbsolutePath());
        }
        final BufferedReader in = new BufferedReader(new FileReader(file));


        boolean readHeaders = false; // False = don't read header
        if (hasColumnNames) {
            readHeaders = true; // True = read a header row
        }
        String	line;
        while ((line = in.readLine()) != null) {

            if ((commentTag != null) && (line.startsWith(commentTag))) {
                continue; // Skip lines that start with a commentTag
            }

            // Skip reading the header until we know how many columns. We're
            // assuming its the first non-commented row that we read.
            if (readHeaders) {
                readHeaders = false;
                continue;
            }

            // Count the columns
            if (numOfColumns == 0) {
                final StringTokenizer st = new StringTokenizer(line, delimiter);
                numOfColumns = st.countTokens();
            }

            numOfRows++;   // Count the rows

        }
        data = new double[numOfRows][numOfColumns];

        in.close();
    }

    private void parse() throws IOException {
        final BufferedReader in = new BufferedReader(new FileReader(this.file));
        String line;
        StringTokenizer	st;
        int	row = 0;
        int column = 0;
        int good = 0;
        int bad = 0;

        // To read headers or not to read headers? That is the question.
        boolean readHeaders = false; // False = don't read header
        if (hasColumnNames) {
            readHeaders = true; // True = read a header row
        }

        // Parse the file in this loop
        while ((line = in.readLine()) != null) {

            // Skip lines that start with a commentTag
            if ((commentTag != null) && (line.startsWith(commentTag))) {
                continue;
            }

            // Read the column headers if specified
            if (readHeaders) {
                st = new StringTokenizer(line, delimiter);
                if (st.countTokens() != numOfColumns) {
                    in.close();
                    throw new IOException("Number of column headers (" +
                            st.countTokens() + ") does not " +
                            "match the number of columns (" + numOfColumns +
                            ") in the data.");
                }
                int i = 0;
                columnNames = new String[numOfColumns];
                while (st.hasMoreTokens()) {
                    columnNames[i] = st.nextToken();
                    i++;
                }
                readHeaders = false;
                continue;
            }

            // Parse a line of data
            st = new StringTokenizer(line, delimiter);
            column = 0;

            while (st.hasMoreTokens()) {
                //System.out.println("Reading row " + row + ", column " + column);
                try {
                    this.data[row][column] =  Double.parseDouble(st.nextToken());
                } catch (NumberFormatException e) {
                    this.data[row][column] = Double.NaN;
                    //System.err.println("Bad value found:" + e.getMessage());
                }
                column++;
            }
            row++;

        }
        in.close();
    }

    public void setHasColumnNames(boolean newHasColumnNames) {
        hasColumnNames = newHasColumnNames;
    }
    public boolean getHasColumnNames() {
        return hasColumnNames;
    }
    public void setDelimiter(String newDelimiter) {
        delimiter = newDelimiter;
    }
    public String getDelimiter() {
        return delimiter;
    }
    public String[] getColumnNames() {
        return columnNames;
    }
    public void setCommentTag(String newCommentTag) {
        commentTag = newCommentTag;
    }
    public String getCommentTag() {
        return commentTag;
    }

    /**
     * For debugging
     */
    static public void main(String[] args) {
        try {
            DelimitedFile reader = new DelimitedFile(args[0]);
            reader.setDelimiter(",");
            reader.setHasColumnNames(true);
            reader.read();
            double[][] data = reader.getData();
            System.out.println("Read " + data.length + " rows and " + data[0].length +
                    " columns from " + reader.getFile().getName());
            System.out.println("Last data point in row " + (data.length-2) + " is " +
                    data[data.length-3][data[0].length-1]);
            System.out.println("Last data point in row " + (data.length-1) + " is " +
                    data[data.length-2][data[0].length-1]);
            System.out.println("Last data point in row " + data.length + " is " +
                    data[data.length-1][data[0].length-1]);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // ///////////////////////////////////////
    // Class Variables
    File file;
    protected double[][] data;
    private int	numOfColumns = 0;
    private int numOfRows = 0;
    private boolean hasColumnNames = false;
    private String delimiter = " \t\n\r\f";
    private String commentTag;
    protected String[] columnNames; // Stores the names of the columns if a header row is present
}


