package de.lcag.common;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.management.OperationsException;

import de.lcag.common.SelectStatement.Condition;
import de.lcag.common.SelectStatement.Join;

/**
 * Supports in-memory tables. Those with a name can be registered to keep them
 * in memory within livetime of the JVM. Only registered table can be used
 * Table.select calls. You have to delete a registered table explicitly to
 * cleanup its memory. Memeory allcoation of unregistered tables is done by
 * theGarbage Collector.
 * 
 * @author U401455
 *
 */
public interface Table {
	public interface TableColumn {
		public void addValue(Object v);

		public <V> V getValue(int i);

		public void setValue(int rowNo, Object v);

		public int length();

		public int findRow(Object pValue);

		public String getStyle();

		public void setStyle(String string);

		public String asCSV(String pSeparator);

		public int shift();

		public int getPosition();

		public Table getTable();

		public void init(TableColumn clientColumn);

		public void init(Object pValue);
	}

	public static final String OPTION_ADD_SOURCE_COLUMN = "ADD_SOURCE_COLUMN";
	public static final String OPTION_USE_MAPPING = "USE_MAPPING";
	public static final String OPTION_NUMBER_ROWS = "NUMBER_ROWS";
	public static final String OPTION_LIMIT_NUMBER_ROWS = "LIMIT_NUMBER_ROWS";

	public static final String KEY_COLUMN = "_Key_";
	public static final String ROW_NUMBER_COLUMN = "_RowNo_";
	static Map<String, Table> tables = new HashMap<>();

	TableColumn addColumn(String aName);

	void addColumns(String... aNameList);

	void addColumns(Collection<String> pNameList);

	String getSeparator();

	String getName();

	boolean hasColumn(String pName);

	/**
	 * Return a list of column names.
	 * 
	 * @return
	 */
	Set<String> getColumnNames();

	public int getColumnPositionByLabel(String pLabel);

	public int getColumnPositionByName(String pName);

	public String getColumnNameByPosition(int pPos);

	public TableColumn getColumnByName(String colName);

	public TableColumn getColumnByLabel(String pFormattedColName);

	public int getNoColumns();

	/**
	 * Return a list of column labels (User-defined).
	 * 
	 * @return
	 */
	Collection<String> getColumnLabels();

	void setCellValue(int row, String colName, Object v);

	void setCellValues(int pMyRow, Table pSrcTable, Integer pSrcRow);

	<V> V getCellValue(int row, String colName);

	public String getKeyValue(int pRow);

	/*
	 * Calculate and return a unique key for the table
	 */
	public String setUniqueKeyValue(int pRow);

	/*
	 * Calculate and return a key for the table. The key can be non-unique
	 */
	public String setKeyValue(int pRow);

	public Integer getRowByKey(String pKey);

	/**
	 * Find the first row number where the given column equals the value.
	 * 
	 * @param colName - Name of column to search
	 * @param v       - Value to search for
	 * @return -1 if not found else row number of first match in that column
	 */
	int findRow(String colName, Object v);

	public void integrateDiff(TableComparisonResult pDiff) throws IOException;

	/**
	 * Create a new table as the result of a full table join between this table and
	 * the one passed.
	 * 
	 * @param pTable    - table to join with
	 * @param pFieldMap - Join condition: The Map defines the mapping of ALL column
	 *                  names in pTable to those of this table that must match to
	 *                  join rows.
	 * @return - new table with all rows and columns that matched
	 */
	Table joinTable(Table pTable, Map<String, String> pFieldMap);

	/**
	 * Create a new table as the result of a full table join between this table and
	 * the one passed.
	 * 
	 * @param ptargetTable - empty table store result (will be cleared before)
	 * @param pTableToJoin - table to join with
	 * @param pFieldMap    - Join condition: The Map defines the mapping of ALL
	 *                     column names in pTable to those of this table that must
	 *                     match to join rows.
	 */
	void joinTable(Table pTargetTable, Table pTableToJoin, Map<String, String> pFieldMap);

	/**
	 * Create a CSV list of all string values in the given column.
	 * 
	 * @param pColName   - label or name of column to aggregate
	 * @param pSeparator - separator to use
	 * @return
	 */
	String asCSV(String pColName, String pSeparator);

	void init();

	int newRow();

	String asHTML();

	int lenght();

	String asCsvData();

	/**
	 * Print CSV as String with the upper cased column names. If you want the lables
	 * instead, then use toSring()
	 * 
	 * @return
	 */
	String asCSV();

	public Path getFilePath();

	public static Table get(String pTableName) {
		return tables.get(pTableName.toUpperCase());
	}

	public static void register(String pTableName, Table pTable) throws OperationsException {
		if (pTableName == null || pTable == null)
			return;

		if (Table.get(pTableName) == null) {
			tables.put(pTableName.toUpperCase(), pTable);
		} else {
			throw new OperationsException(String.format("Cannot register Table '%s' (already registered)", pTableName));
		}
	}

	public static Table delete(String pTableName) {
		return tables.remove(pTableName.toUpperCase());
	}

	public static void deleteAll() {
		for (String s : tables.keySet())
			tables.remove(s);
	}

	public static Table selectFromTable(AbstractTable pSrcTable, String pSelectStatement) {
		HashTable result = new HashTable();
		SelectStatement stmt = new SelectStatement(pSelectStatement);
		String wantedTableName = stmt.getFromTable();
		List<Condition> whereClause = stmt.getWhereClause();
		List<Join> joinOn = stmt.getJoinOn();
		String srcTableName = pSrcTable.getName();
		Map<String, String> columns = stmt.getColumns();

		if (whereClause != null && !whereClause.isEmpty()) {
			throw new UnsupportedOperationException(
					String.format("WHERE clause not yet supported in %s", pSelectStatement));
		}

		if (joinOn != null && !joinOn.isEmpty()) {
			throw new UnsupportedOperationException(
					String.format("JOIN operation not yet supported in %s", pSelectStatement));
		}

		if (srcTableName != null && !srcTableName.toUpperCase().equals(wantedTableName.toUpperCase())) {
			return null;
		}

		for (String colName : columns.keySet()) {
			String srcColName = columns.get(colName);
			final AbstractTable.TableColumn newColumn = pSrcTable.cloneColumn(srcColName);

			result.addColumn(colName, newColumn);
		}

		return result;
	}

	public static Table select(String pSelectStatement) {
		SelectStatement stmt = new SelectStatement(pSelectStatement);
		String srcTableName = stmt.getFromTable();
		HashTable srcTable = (HashTable) Table.get(srcTableName);

		AbstractTable result = (AbstractTable) Table.selectFromTable(srcTable, pSelectStatement);

		return result;
	}

	int getFirstRowNo();

}
