/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */
package shared;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.zip.DataFormatException;

import ilog.views.chart.data.IlvDataReader;
import ilog.views.chart.data.IlvDataSet;
import ilog.views.chart.data.IlvDefaultDataSet;
import ilog.views.chart.util.IlvDoubleArray;

/**
 * A reader that reads quote values in CSV format.
 */
public class CSVDataReader implements IlvDataReader {
  private boolean reverse;

  static class Properties {
    String dataFile;
    String dateFomat;
    boolean transposed;
  }

  /**
   * Initializes a new reader.
   */
  public CSVDataReader() {
    this(false);
  }

  /**
   * Initializes a new reader, specifying whether the read values should be
   * reversed.
   */
  public CSVDataReader(boolean reverse) {
    this.reverse = reverse;
  }

  static int QUOTE_CHAR = '"';

  private StreamTokenizer createTokenizer(Reader reader) {
    StreamTokenizer st = new StreamTokenizer(reader);
    st.quoteChar(QUOTE_CHAR);
    st.ordinaryChar(',');
    st.wordChars('-', '-');
    st.eolIsSignificant(true);
    st.whitespaceChars(0, 32);
    return st;
  }

  /**
   * Returns a <code>Reader</code> object on the specified
   * <code>InputStream</code>.
   */
  protected Reader makeReader(InputStream in) throws MalformedURLException, IOException {
    Reader res = new InputStreamReader(in);
    if (!(in instanceof BufferedInputStream))
      res = new BufferedReader(res);
    return res;
  }

  /**
   * Loads data from the given <code>InputStream</code>.
   * 
   * @return An array containing the data sets.
   * @throws ParseException
   *           Parsing a date failed.
   * @throws NumberFormatException
   *           Parsing a date failed.
   * @throws DataFormatException
   *           The file is not a valid Yahoo CSV format; this exception is
   *           thrown when a line is being parsed and something other than a
   *           number has been read.
   * @throws IOException
   *           Opening (in the case of a URL) or reading the input stream
   *           failed.
   */
  Override
  public IlvDataSet[] read(InputStream in)
      throws ParseException, NumberFormatException, DataFormatException, IOException {
    Reader reader = makeReader(in);

    StreamTokenizer st = createTokenizer(reader);
    boolean xDateSeries = false;
    List<String> seriesNames = readColumnHeaders(st);
    IlvDoubleArray[] values = new IlvDoubleArray[seriesNames.size()];
    int seriesCount = values.length;

    for (int i = 0; i < seriesCount; ++i) {
      values[i] = new IlvDoubleArray();
    }

    int i = 0;
    // == Recover the date.
    xDateSeries = true;
    DateFormat dateFormat = new SimpleDateFormat("dd-MMMM-yy", Locale.ENGLISH);
    Date date, minDate = new Date(0), maxDate = new Date(Long.MAX_VALUE);
    List<Date> dates = new ArrayList<Date>();
    // Skip column names.
    int tok = st.nextToken();
    StringBuffer dateBuffer = new StringBuffer();
    while (tok != StreamTokenizer.TT_EOF) {
      if (tok == ',') {
        ++i;
      } else if (tok == StreamTokenizer.TT_EOL) {
        i = 0;
      } else {
        if ((i == 0) && xDateSeries) {
          if (tok == StreamTokenizer.TT_NUMBER) {
            dateBuffer.append(Integer.toString((int) st.nval));
            dateBuffer.append("-");
          } else if (tok == StreamTokenizer.TT_WORD) {
            dateBuffer.append(st.sval);
          }
          if (dateBuffer.length() > 3) {
            date = dateFormat.parse(dateBuffer.toString());
            dates.add(date);
            values[0].add(date.getTime());
            if (date.before(minDate))
              minDate = date;
            else if (date.after(maxDate))
              maxDate = date;
            dateBuffer.delete(0, 9);
          }
        } else if (tok == StreamTokenizer.TT_NUMBER) {
          values[i].add(st.nval);
        } else
          throw new DataFormatException("Expected number at line " + st.lineno());
      }
      tok = st.nextToken();
    }
    String[] names = new String[seriesCount - 1];
    double[][] data = new double[seriesCount][];
    for (int j = 0; j < seriesCount; ++j) {
      if (this.reverse)
        values[j].reverse();
      values[j].trim();
      data[j] = values[j].data();
      if (j > 0)
        names[j - 1] = seriesNames.get(j);
    }
    return IlvDefaultDataSet.create(data, 0, names, null);
  }

  /**
   * Loads data from the given URL.
   */
  Override
  public IlvDataSet[] read(String url) throws ParseException, NumberFormatException, DataFormatException, IOException {
    InputStream in = new BufferedInputStream(new URL(url).openStream());
    IlvDataSet[] dataSets = read(in);
    in.close();
    return dataSets;
  }

  private List<String> readColumnHeaders(StreamTokenizer st) throws IOException, DataFormatException {
    List<String> names = new ArrayList<String>();
    while (st.nextToken() != StreamTokenizer.TT_EOL) {
      if (st.ttype == StreamTokenizer.TT_WORD) {
        names.add(st.sval);
        if (st.nextToken() == StreamTokenizer.TT_EOL)
          break;
      } else
        throw new DataFormatException("Expected word at line : " + st.lineno());
    }
    return names;
  }

}