/*******************************************************************************
 * Copyright (c) 2004 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *  Actuate Corporation  - initial API and implementation
 *******************************************************************************/

package org.eclipse.birt.tests.data.engine.acceptance;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.birt.core.archive.FileArchiveReader;
import org.eclipse.birt.core.archive.FileArchiveWriter;
import org.eclipse.birt.core.data.DataType;
import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.data.engine.api.DataEngine;
import org.eclipse.birt.data.engine.api.DataEngineContext;
import org.eclipse.birt.data.engine.api.IBaseExpression;
import org.eclipse.birt.data.engine.api.IQueryResults;
import org.eclipse.birt.data.engine.api.IResultIterator;
import org.eclipse.birt.data.engine.api.IResultMetaData;
import org.eclipse.birt.data.engine.api.querydefn.BaseDataSetDesign;
import org.eclipse.birt.data.engine.api.querydefn.BaseQueryDefinition;
import org.eclipse.birt.data.engine.api.querydefn.ComputedColumn;
import org.eclipse.birt.data.engine.api.querydefn.QueryDefinition;
import org.eclipse.birt.data.engine.api.querydefn.ScriptExpression;
import org.eclipse.birt.data.engine.core.DataException;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptableObject;

import testutil.APITestCase;
import testutil.ConfigText;

/**
 * Test the feature of report document. A life cycle of report document can be
 * divided into two parts, one is generation and the other is presentation. Once
 * a report can be generated once, it can be presented many times. In generation
 * time, query results ID and expression id needs to be stored. These ID are
 * automatically generated by DtE implemenation. In presentation time, query
 * results ID is first used to retrieve the query result, and then expression ID
 * can be used to retrieve the detailed data.
 */
public class ComputedColumnTest extends APITestCase {
	// instance of DataEngine
	private DataEngine myDataEngine;

	private ScriptableObject scope;

	private ScriptableObject subScope;

	private ScriptableObject subOfSubScope;

	// id to store
	private String queryResultID;

	private String[] rowExprName;

	private String[] ccName;

	private String[] ccExpr;

	// value to compare
	private List expectedValue;

	private FileArchiveWriter archiveWriter;

	private FileArchiveReader archiveReader;

	/*
	 * @see org.eclipse.birt.data.engine.api.APITestCase#getDataSourceInfo()
	 */
	protected DataSourceInfo getDataSourceInfo() {
		return new DataSourceInfo(ConfigText
				.getString("Api.TestData.TableName"), ConfigText
				.getString("Api.TestData.TableSQL"), ConfigText
				.getString("Api.TestData.TestDataFileName"));
	}

	/**
	 * Check that in output column page of edit data set dialog, Computed
	 * column(Such as integer_type, string_type,DateTime_type,Float_type) has
	 * been added to data set
	 */
	public void setUp() throws Exception {
		super.setUp();

		ccName = new String[] { "test", "testInteger", "testDateTime",
				"testFloat" };
		ccExpr = new String[] { "row.COUNTRY", "row.AMOUNT*100",
				"row.SALE_DATE", "row.AMOUNT-13.95" };

		for (int i = 0; i < 4; i++) {
			ComputedColumn computedColumn = null;
			switch (i) {
			case 0:
				computedColumn = new ComputedColumn(ccName[i], ccExpr[i],
						DataType.STRING_TYPE);
				break;
			case 1:
				computedColumn = new ComputedColumn(ccName[i], ccExpr[i],
						DataType.INTEGER_TYPE);
				break;
			case 2:
				computedColumn = new ComputedColumn(ccName[i], ccExpr[i],
						DataType.DATE_TYPE);
				break;
			case 3:
				computedColumn = new ComputedColumn(ccName[i], ccExpr[i],
						DataType.DOUBLE_TYPE);
				break;

			}
			((BaseDataSetDesign) this.dataSet)
					.addComputedColumn(computedColumn);
		}

		DataEngineContext deContext = newContext(DataEngineContext.MODE_GENERATION);
		myDataEngine = DataEngine.newDataEngine(deContext);

		myDataEngine.defineDataSource(this.dataSource);
		myDataEngine.defineDataSet(this.dataSet);

		expectedValue = new ArrayList();

		Context context = Context.enter();

		scope = context.initStandardObjects();

		subScope = context.initStandardObjects();
		subScope.setPrototype(scope);

		subOfSubScope = context.initStandardObjects();
		subOfSubScope.setPrototype(subScope);

		Context.exit();
	}

	/**
	 * Basic report document test without sub query, but it has aggregation.
	 * 
	 * @throws BirtException
	 */
	public void testBasic() throws BirtException {
		this.genBasic();
		this.closeArchiveWriter();
		this.preBasic();
		this.closeArchiveReader();

	}

	/**
	 * @throws BirtException
	 */
	private void genBasic() throws BirtException {
		QueryDefinition qd = newReportQuery();

		// prepare
		IBaseExpression[] rowBeArray = getRowExpr();
		prepareExprNameAndQuery(rowBeArray, qd);

		// generation
		IQueryResults qr = myDataEngine.prepare(qd).execute(scope);

		IResultMetaData md = qr.getResultMetaData();
		for (int i = 1; i < 11; i++) {
			System.out.println(md.getColumnName(i));
		}

		// important step
		saveForPresentation(qr, rowBeArray);

		IResultIterator ri = qr.getResultIterator();
		while (ri.next()) {
			for (int i = 0; i < rowBeArray.length; i++)
				expectedValue.add(ri.getValue(rowBeArray[i].toString()));

		}

		ri.close();
		ri.close();
		qr.close();
		qr.close();
		myDataEngine.shutdown();
	}

	/**
	 * @throws BirtException
	 */
	private void preBasic() throws BirtException {
		DataEngineContext deContext = newContext(DataEngineContext.MODE_PRESENTATION);
		myDataEngine = DataEngine.newDataEngine(deContext);
		IQueryResults qr = myDataEngine.getQueryResults(queryResultID);
		assert (qr.getResultMetaData() != null);

		IResultIterator ri = qr.getResultIterator();
		assert (ri.getResultMetaData() != null);

		checkResult(ri);

		ri.close();
		myDataEngine.shutdown();
	}

	/**
	 * @param type
	 * @return context
	 * @throws BirtException
	 */
	private DataEngineContext newContext(int type) throws BirtException {
		switch (type) {
		case DataEngineContext.MODE_GENERATION: {
			archiveWriter = null;
			try {
				archiveWriter = new FileArchiveWriter("d:/test2");
				archiveWriter.initialize();
			} catch (IOException e) {
				throw new IllegalArgumentException(e.getMessage());
			}
			return DataEngineContext.newInstance(
					DataEngineContext.MODE_GENERATION, null, null,
					archiveWriter);
		}
		case DataEngineContext.MODE_PRESENTATION: {
			archiveReader = null;
			try {
				archiveReader = new FileArchiveReader("d:/test2");
				archiveReader.open();
			} catch (IOException e) {
				throw new IllegalArgumentException(e.getMessage());
			}

			return DataEngineContext.newInstance(
					DataEngineContext.MODE_PRESENTATION, null, archiveReader,
					null);
		}
		default:
			throw new IllegalArgumentException("" + type);
		}
	}

	/**
	 * @return row expression array
	 */
	private IBaseExpression[] getRowExpr() {
		// row test
		int num = 8;
		IBaseExpression[] rowBeArray = new IBaseExpression[num];
		rowBeArray[0] = new ScriptExpression("row.COUNTRY");
		rowBeArray[1] = new ScriptExpression("row.CITY");
		rowBeArray[2] = new ScriptExpression("row.SALE_DATE");
		rowBeArray[3] = new ScriptExpression("row.AMOUNT");
		for (int i = 0; i < ccName.length; i++) {
			rowBeArray[i + 4] = new ScriptExpression("row." + this.ccName[i]);
		}

		final int expectedLen = 15;
		String metaData = "";
		metaData += formatStr("COUNTRY", expectedLen);
		metaData += formatStr("CITY", expectedLen);
		metaData += formatStr("SALE_DATE", expectedLen);
		metaData += formatStr("SALE_DATE", expectedLen);
		for (int i = 0; i < ccName.length; i++) {
			metaData += formatStr(ccName[i], expectedLen);
		}
		this.testPrintln(metaData);

		return rowBeArray;
	}

	/**
	 * @param rowBeArray
	 * @param totalBeArray
	 * @param qd
	 */
	private void prepareExprNameAndQuery(IBaseExpression[] rowBeArray,
			BaseQueryDefinition qd) {
		int num = rowBeArray.length;

		rowExprName = new String[num];

		for (int i = 0; i < num; i++)
			qd.addResultSetExpression(rowBeArray[i].toString(), rowBeArray[i]);
	}

	/**
	 * @param rowBeArray
	 * @param totalBeArray
	 */
	private void saveForPresentation(IQueryResults queryResuls,
			IBaseExpression[] rowBeArray) {
		queryResultID = queryResuls.getID();

		int num = rowBeArray.length;
		rowExprName = new String[num];

		for (int i = 0; i < num; i++)
			rowExprName[i] = rowBeArray[i].toString();
	}

	/**
	 * Only check the result of the expectedValue of the result set
	 * 
	 * @param it
	 * @param ri
	 * @throws DataException
	 * @throws BirtException
	 */
	private void checkResult(IResultIterator ri) throws BirtException {

		Iterator it = this.expectedValue.iterator();

		while (ri.next()) {
			String str = "";
			for (int i = 0; i < rowExprName.length; i++) {
				Object ob1 = it.next();
				Object ob2 = ri.getValue(newExpression(rowExprName[i])
						.toString());
				assertEquals(ob1, ob2);
				str += " " + ob2.toString();
			}

			System.out.println("row result set: " + str);
		}
	}

	/**
	 * New an expression for retrieving expression value
	 * 
	 * @param exprID
	 * @return expression instance
	 */
	private static IBaseExpression newExpression(final String exprID) {
		return new IBaseExpression() {
			/*
			 * @see org.eclipse.birt.data.engine.api.IBaseExpression#getID()
			 */
			public String getID() {
				return exprID;
			}

			/*
			 * @see org.eclipse.birt.data.engine.api.IBaseExpression#setID(java.lang.String)
			 */
			public void setID(String exprID) {
			}

			/*
			 * @see org.eclipse.birt.data.engine.api.IBaseExpression#getDataType()
			 */
			public int getDataType() {
				return 0;
			}

			/*
			 * @see org.eclipse.birt.data.engine.api.IBaseExpression#getHandle()
			 */
			public Object getHandle() {
				return null;
			}

			/*
			 * @see org.eclipse.birt.data.engine.api.IBaseExpression#setHandle(java.lang.Object)
			 */
			public void setHandle(Object handle) {
			}

			public String getGroupName() {
				// TODO Auto-generated method stub
				return null;
			}

			public void setGroupName(String name) {
				// TODO Auto-generated method stub

			}
		};
	}

	private static String formatStr(String inputStr, int length) {
		if (inputStr == null)
			return null;

		int inputLen = inputStr.length();
		if (inputLen >= length)
			return inputStr;

		int appendLen = length - inputLen;
		char[] appendChar = new char[appendLen];
		for (int i = 0; i < appendLen; i++) {
			appendChar[i] = ' ';
		}
		String result = inputStr + new String(appendChar);

		return result;
	}

	private void closeArchiveWriter() throws DataException {
		if (archiveWriter != null)
			try {
				archiveWriter.finish();
			} catch (IOException e) {
				throw new DataException("error", e);
			}
	}

	/**
	 * @throws DataException
	 */
	private void closeArchiveReader() throws DataException {
		if (archiveReader != null)
			try {
				archiveReader.close();
			} catch (IOException e) {
				throw new DataException("error", e);
			}
	}

}
