/*
 * This file is a part of EpnTAPClient.
 * This program aims to provide EPN-TAP support for software clients, like CASSIS spectrum analyzer.
 * See draft specifications: https://voparis-confluence.obspm.fr/pages/viewpage.action?pageId=559861
 * Copyright (C) 2016 Institut de Recherche en Astrophysique et Planétologie.
 *
 * This program is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or (at your option) any later
 * version. This program 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 General Public License for more details. You should have received a copy of
 * the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */

package eu.omp.irap.vespa.votable.votabledata;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import eu.omp.irap.vespa.votable.votable.VOTableException.CanNotParseDataException;

/**
 * @author N. Jourdane
 */
public class VOTableData {

	/** The name of the columns. `columnNames` length is equals to `data` length. */
	private String[] columnsNames;

	/**
	 * A list of arrays, representing data stored in the VOTable. Each element is a VOTable row,
	 * where arrays elements are in the same order as `columnNames`.
	 */
	private List<Object[]> data;

	/** The title of the data set. */
	private String title;


	/**
	 * Constructor of VOTableData.
	 * 
	 * @param title The title of the data set.
	 */
	public VOTableData(String title) {
		this.title = title;
		columnsNames = new String[0];
		data = new ArrayList<>();
	}

	/**
	 * Constructor of VOTableData.
	 *
	 * @param columnsNames An array of columns names.
	 * @param title The title of the data set.
	 */
	public VOTableData(String title, String[] columnsNames) {
		this.title = title;
		this.columnsNames = columnsNames;
		data = new ArrayList<>();
	}

	/**
	 * Constructor of VOTableData.
	 *
	 * @param title The title of the data set.
	 * @param columnsName An array of columns names.
	 * @param data A list of rows where each row is an array of value sorted at the same order as
	 *            the column names returned by {@link #getColumnsName()}.
	 */
	public VOTableData(String title, String[] columnsName, List<Object[]> data) {
		this.title = title;
		columnsNames = columnsName;
		this.data = data;
	}

	/**
	 * Append a VOTable data to the current VOTable data.
	 *
	 * @param dataToAppend The voTable data to append.
	 */
	public void append(VOTableData dataToAppend) {
		data.addAll(dataToAppend.getData());
	}

	/**
	 * Get the cell value at the specified row and column.
	 *
	 * @param rowIndex The index of the row to select.
	 * @param columnIndex The index of the column to select.
	 * @return The value of the returned cell.
	 */
	public Object getCell(int rowIndex, int columnIndex) {
		return data.get(rowIndex)[columnIndex];
	}

	/**
	 * Get the cell value at the specified row and column.
	 *
	 * @param rowIndex The index of the row to select.
	 * @param columnName The name of the column to select.
	 * @return The value of the returned cell.
	 * @throws CanNotParseDataException The column name was not found in the list.
	 */
	public Object getCell(int rowIndex, String columnName) throws CanNotParseDataException {
		return data.get(rowIndex)[getColumnIndex(columnName)];
	}

	/**
	 * Get a column by its index.
	 *
	 * @param columnIndex The index of the column to get.
	 * @return A list of objects corresponding to all the rows values at the specified column.
	 */
	public List<Object> getColumn(int columnIndex) {
		List<Object> column = new ArrayList<>();
		for (int rowId = 0; rowId < data.size(); rowId++) {
			column.add(data.get(rowId)[columnIndex]);
		}
		return column;
	}

	/**
	 * Get a column by its name.
	 *
	 * @param columnName The name of the column to get.
	 * @return A list of objects corresponding to all the rows values at the specified column.
	 * @throws CanNotParseDataException The column name was not found in the list.
	 */
	public List<Object> getColumn(String columnName) throws CanNotParseDataException {
		return getColumn(getColumnIndex(columnName));
	}

	/**
	 * @param columnName The name of the column to get.
	 * @return The index of the first columns who match with columnName
	 * @throws CanNotParseDataException The column name was not found in the list.
	 * @throws IllegalArgumentException Column name not found in the table.
	 */
	public int getColumnIndex(String columnName) throws CanNotParseDataException {
		for (int colId = 0; colId < columnsNames.length; colId++) {
			if (columnName.equals(columnsNames[colId])) {
				return colId;
			}
		}
		throw new CanNotParseDataException(
				"The column " + columnName + " was not found in the table.");
	}

	/**
	 * @return An array of all the columns names of the data set.
	 */
	public String[] getColumnsName() {
		return columnsNames;
	}

	/**
	 * @return The data of the dataset, which is a list of rows where each row is an array of value
	 *         sorted at the same order as the column names returned by {@link #getColumnsName()}.
	 */
	public List<Object[]> getData() {
		return data;
	}

	/**
	 * @return A 2D array corresponding to the data, which is an array of rows where each row is an
	 *         array of value sorted at the same order as the column names returned by
	 *         {@link #getColumnsName()}.
	 */
	public Object[][] getDataArray() {
		return data.toArray(new Object[data.size()][]);
	}

	/**
	 * @return the number of columns in the data set.
	 */
	public int getNbColumns() {
		return columnsNames.length;
	}

	/**
	 * @return The number of rows in the data set.
	 */
	public int getNbRows() {
		return data == null ? 0 : data.size();
	}

	/**
	 * @param rowIndex The index of the row to get.
	 * @return The Table row at the specified index.
	 */
	public Object[] getRow(int rowIndex) {
		return data.get(rowIndex);
	}

	/**
	 * Search a row in the data, whose the specified value match at the specified column.
	 *
	 * @param columnIndex The index of a unique column to identify a row.
	 * @param value The value of the cell at the specified column name.
	 * @return An array of objects corresponding to the row who match with the filter.
	 * @throws IndexOutOfBoundsException If the value is not found at the specified column.
	 */
	public Object[] getRowByValue(int columnIndex, Object value) {
		for (Object[] row : data) {
			if (value.equals(row[columnIndex])) {
				return row;
			}
		}
		throw new IndexOutOfBoundsException(
				"The value " + value + " is not found on the table at the column " + columnIndex);
	}

	/**
	 * Search a row in the data, whose the specified value match at the specified column. Ie,
	 * getRowMapByValue("foo", "bar") will returns the row where the value at the column named "foo"
	 * is equals to the value "bar". The column name must be unique, and the all values in the data
	 * set at the specified column must be also uniques.
	 *
	 * @param columnName The name of a unique column to identify a row.
	 * @param value The value of the cell at the specified column name.
	 * @return An array of objects corresponding to the row who match with the filter.
	 * @throws CanNotParseDataException The column name was not found in the list.
	 */
	public Object[] getRowByValue(String columnName, Object value) throws CanNotParseDataException {
		return getRowByValue(getColumnIndex(columnName), value);
	}

	/**
	 * Search a row in the data, whose the specified value match at the specified column. Ie,
	 * getRowMapByValue("foo", "bar") will returns the row index where the value at the column named
	 * "foo" is equals to the value "bar". The column name must be unique, and the all values in the
	 * data set at the specified column must be also uniques.
	 *
	 * @param columnName The name of a unique column to identify a row.
	 * @param value The value of the cell at the specified column name.
	 * @return The index of the row who match with the filter.
	 * @throws CanNotParseDataException The column name was not found in the list.
	 */
	public int getRowIndex(String columnName, Object value) throws CanNotParseDataException {
		int columnId = getColumnIndex(columnName);
		for (int rowId = 0; rowId < data.size(); rowId++) {
			if (value.equals(data.get(rowId)[columnId])) {
				return rowId;
			}
		}
		throw new IndexOutOfBoundsException(
				"The value " + value + " is not found on the table at the column " + columnName);
	}

	/**
	 * Get a row at the specified index.
	 *
	 * @param rowIndex The index of the row to get.
	 * @return A map representing the row, as <column name, cell value>.
	 */
	public Map<String, Object> getRowMap(int rowIndex) {
		Map<String, Object> row = new HashMap<>();
		for (int i = 0; i < columnsNames.length; i++) {
			row.put(columnsNames[i], data.get(rowIndex)[i]);
		}
		return row;
	}

	/**
	 * Search a row in the data, whose the specified value match at the specified column. Ie,
	 * getRowMapByValue("foo", "bar") will returns the row where the value at the column named "foo"
	 * is equals to the value "bar". The column name must be unique, and the all values in the data
	 * set at the specified column must be also uniques.
	 *
	 * @param columnName The name of a unique column to identify a row.
	 * @param value The value of the cell at the specified column name.
	 * @return A map representing the row, as <column name, cell value> who match with the filter.
	 * @throws CanNotParseDataException The column name was not found in the list.
	 */
	public Map<String, Object> getRowMapByValue(String columnName, Object value)
			throws CanNotParseDataException {
		return getRowMap(getRowIndex(columnName, value));
	}

	/**
	 * @return the title of the data set
	 */
	public String getTitle() {
		return title;
	}

	/**
	 * Tell if a column contains the specified value.
	 *
	 * @param columnName The name of the concerned column.
	 * @param value The value to looks for.
	 * @return true if the specified column contains the specified value.
	 * @throws CanNotParseDataException The column name was not found in the list.
	 */
	public boolean isColumnContainingValue(String columnName, Object value)
			throws CanNotParseDataException {
		int rowId = getColumnIndex(columnName);
		for (Object[] row : data) {
			if (value.equals(row[rowId])) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Tell if the columns of the data set contains the specified column.
	 *
	 * @param columnName the name of the column to search.
	 * @return true if the columns of the data set contains the specified column.
	 */
	public boolean isContainingColumnName(String columnName) {
		for (String element : columnsNames) {
			if (columnName.equals(element)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Set the data of the data set.
	 *
	 * @param data A list of rows where each row is an array of value sorted at the same order as
	 *            the column names returned by {@link #getColumnsName()}.
	 */
	public void setData(List<Object[]> data) {
		this.data = data;
	}

}
