/*
 * DataTable.java
 *
 * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
 *
 * This file is part of BEAST.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership and licensing.
 *
 * BEAST is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 *  BEAST is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with BEAST; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

package dr.util;

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

/**
 * @author Andrew Rambaut
 * @version $Id$
 */
public interface DataTable<T> {
    int getColumnCount();

    int getRowCount();

    String[] getColumnLabels();
    String[] getRowLabels();

    T getColumn(int columnIndex);
    T getRow(int rowIndex);

    T[] getData();

    public class Double implements DataTable<double[]> {

        private Double(Reader source, boolean hasColumnLabels, boolean hasRowLabels) throws IOException {
            BufferedReader reader = new BufferedReader(source);

            String line = reader.readLine();
            if (line == null) {
                throw new IllegalArgumentException("Empty file");
            }

            int columnCount = -1;

            if (hasColumnLabels) {
                List<String> columnLabels = new ArrayList<String>();

                StringTokenizer tokenizer = new StringTokenizer(line, "\t");

                if (hasRowLabels && !line.startsWith("\t")) {
                    // potentially the first token is the name of the row labels
                    String name = tokenizer.nextToken();
                }

                while (tokenizer.hasMoreTokens()) {
                    String label = tokenizer.nextToken();
                    columnLabels.add(label);
                }

                this.columnLabels = new String[columnLabels.size()];
                columnLabels.toArray(this.columnLabels);

                columnCount = columnLabels.size();

                line = reader.readLine();
            }

            List<String> rowLabels = new ArrayList<String>();
            List<double[]> rows = new ArrayList<double[]>();

            int rowIndex = 1;

            while (line != null) {
                StringTokenizer tokenizer = new StringTokenizer(line, "\t");

                if (columnCount == -1) {
                    columnCount = tokenizer.countTokens();
                    if (hasRowLabels) {
                        columnCount --;
                    }
                }

                if (hasRowLabels) {
                    String label = tokenizer.nextToken();
                    rowLabels.add(label);
                }

                double[] row = new double[columnCount];

                int columnIndex = 0;
                while (tokenizer.hasMoreTokens()) {
                    String value = tokenizer.nextToken();
                    try {
                        row[columnIndex] = java.lang.Double.valueOf(value);
                    } catch (NumberFormatException nfe) {
                        row[columnIndex] = java.lang.Double.NaN;
//                        throw new IllegalArgumentException("Non numerical value at row " + (rowIndex + 1) +
//                                ", column " + (columnIndex + 1));
                    }

                    columnIndex ++;
                }
                if (columnIndex != columnCount) {
                    throw new IllegalArgumentException("Wrong number of values on row " + (rowIndex + 1) +
                            ", expecting " + columnCount + " but actually " + columnIndex);
                }

                rows.add(row);

                line = reader.readLine();
                rowIndex++;
            }

            if (hasRowLabels) {
                this.rowLabels = new String[rowLabels.size()];
                rowLabels.toArray(this.rowLabels);
            }

            data = new double[rows.size()][];
            rows.toArray(data);

        }

        public int getColumnCount() {
            return columnLabels.length;
        }

        public int getRowCount() {
            return rowLabels.length;
        }

        public String[] getColumnLabels() {
            return columnLabels;
        }

        public String[] getRowLabels() {
            return rowLabels;
        }

        public double[] getColumn(final int columnIndex) {
            double[] column = new double[data.length];
            for (int i = 0; i < data.length; i++) {
                column[i] = data[i][columnIndex];
            }
            return column;
        }

        public double[] getRow(final int rowIndex) {
            return data[rowIndex];
        }

        public double[][] getData() {
            return data;
        }

        private String[] columnLabels;
        private String[] rowLabels;

        private double[][] data;

        public static DataTable<double []> parse(Reader source) throws IOException {
            return new DataTable.Double(source, true, true);
        }

        public static DataTable<double []> parse(Reader source, boolean columnLabels, boolean rowLabels) throws IOException {
            return new DataTable.Double(source, columnLabels, rowLabels);
        }
    }

    class Text implements DataTable<String[]> {

        private Text(Reader source, boolean hasColumnLabels, boolean hasRowLabels) throws IOException {
            BufferedReader reader = new BufferedReader(source);

            String line = reader.readLine();
            if (line == null) {
                throw new IllegalArgumentException("Empty file");
            }

            int columnCount = -1;

            if (hasColumnLabels) {
                List<String> columnLabels = new ArrayList<String>();

                StringTokenizer tokenizer = new StringTokenizer(line, "\t");

                if (hasRowLabels && !line.startsWith("\t")) {
                    // potentially the first token is the name of the row labels
                    String name = tokenizer.nextToken();
                }

                while (tokenizer.hasMoreTokens()) {
                    String label = tokenizer.nextToken();
                    columnLabels.add(label);
                }

                this.columnLabels = new String[columnLabels.size()];
                columnLabels.toArray(this.columnLabels);

                columnCount = columnLabels.size();

                line = reader.readLine();
            }

            List<String> rowLabels = new ArrayList<String>();
            List<String[]> rows = new ArrayList<String[]>();
            int rowIndex = 1;

            while (line != null) {
                StringTokenizer tokenizer = new StringTokenizer(line, "\t");

                if (columnCount == -1) {
                    columnCount = tokenizer.countTokens();
                    if (hasRowLabels) {
                        columnCount --;
                    }
                }

                if (hasRowLabels) {
                    String label = tokenizer.nextToken();
                    rowLabels.add(label);
                }

                String[] row = new String [columnCount];

                int columnIndex = 0;
                while (tokenizer.hasMoreTokens()) {
                    row[columnIndex] = tokenizer.nextToken().trim();

                    columnIndex ++;
                }
                if (columnIndex != columnCount) {
                    throw new IllegalArgumentException("Wrong number of values on row " + (rowIndex + 1) +
                            ", expecting " + columnCount + " but actually " + columnIndex);
                }

                rows.add(row);

                line = reader.readLine();
                rowIndex++;
            }

            if (hasRowLabels) {
                this.rowLabels = new String[rowLabels.size()];
                rowLabels.toArray(this.rowLabels);
            }

            data = new String[rows.size()][];
            rows.toArray(data);

        }

        public int getColumnCount() {
            return data[0].length;
        }

        public int getRowCount() {
            return data.length;
        }

        public String[] getColumnLabels() {
            return columnLabels;
        }

        public String[] getRowLabels() {
            return rowLabels;
        }

        public String[] getColumn(final int columnIndex) {
            String[] column = new String[data.length];
            for (int i = 0; i < data.length; i++) {
                column[i] = data[i][columnIndex];
            }
            return column;
        }

        public String[] getRow(final int rowIndex) {
            return data[rowIndex];
        }

        public String[][] getData() {
            return data;
        }

        private String[] columnLabels;
        private String[] rowLabels;

        private String[][] data;

        public static DataTable<String []> parse(Reader source) throws IOException {
            return new DataTable.Text(source, true, true);
        }

        public static DataTable<String []> parse(Reader source, boolean columnLabels, boolean rowLabels) throws IOException {
            return new DataTable.Text(source, columnLabels, rowLabels);
        }
    }


}
