/*
 * Copyright (C) 2011 Openismus GmbH
 *
 * This file is part of GWT-Glom.
 *
 * GWT-Glom 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 3 of the License, or (at your
 * option) any later version.
 *
 * GWT-Glom 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 GWT-Glom.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.glom.web.server.database;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.glom.web.server.Log;
import org.glom.web.server.SqlUtils;
import org.glom.web.server.libglom.Document;
import org.glom.web.shared.NavigationRecord;
import org.glom.web.shared.TypedDataItem;
import org.glom.web.shared.libglom.Field;
import org.glom.web.shared.libglom.Field.GlomFieldType;
import org.glom.web.shared.libglom.layout.LayoutItemField;
import org.glom.web.shared.libglom.layout.LayoutItemPortal;
import org.glom.web.shared.libglom.layout.TableToViewDetails;

import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 *
 */
public class RelatedListNavigation extends DBAccess {

	private LayoutItemPortal portal;

	public RelatedListNavigation(final Document document, final String documentID, final ComboPooledDataSource cpds,
			final String tableName, final LayoutItemPortal portal) {
		super(document, documentID, cpds, tableName);

		if (portal == null) {
			Log.error(documentID, tableName, "portal is null in table \""
					+ tableName + "\". " + "Cannot retrieve data for the related list.");
			return;
		}

		this.portal = portal;
	}

	/*
	 * Gets a NavigationRecord for the related list given the primaryKeyValue.
	 * 
	 * This code was ported from Glom: Box_Data_Portal::get_suitable_record_to_view_details()
	 */
	public NavigationRecord getNavigationRecord(final TypedDataItem primaryKeyValue) {

		if (portal == null) {
			Log.error(documentID, tableName,
					"The related list navigation cannot be determined because the LayoutItemPortal has not been found.");
			return null;
		}
		
		if (primaryKeyValue == null) {
			Log.error(documentID, tableName,
					"The related list navigation cannot be determined because the primaryKeyValue is null.");
			return null;
		}

		//TODO: For some reason, LayoutItemPortal.navigationTable is null after using deepCopy(),
		//so we can't use our cache:
		//final TableToViewDetails navigationTable = portal.getNavigationTable();
		final TableToViewDetails navigationTable = document.getPortalSuitableTableToViewDetails(portal);
		if (navigationTable == null) {
			Log.error(documentID, tableName,
					"The related list navigation cannot cannot be determined because the navigation table details are empty.");
			return null;
		}
		
		if (StringUtils.isEmpty(navigationTable.tableName)) {
			Log.error(documentID, tableName,
					"The related list navigation cannot cannot be determined because the navigation table name is empty.");
			return null;
		}

		// Get the primary key of that table:
		final Field navigationTablePrimaryKey = getPrimaryKeyField(navigationTable.tableName);

		// Build a layout item to get the field's value:
		final LayoutItemField navigationRelationshipItem = new LayoutItemField();
		navigationRelationshipItem.setName(navigationTablePrimaryKey.getName());
		navigationRelationshipItem.setFullFieldDetails(navigationTablePrimaryKey);
		if(navigationTable.usesRelationship != null) {
			navigationRelationshipItem.setRelationship(navigationTable.usesRelationship.getRelationship());
			navigationRelationshipItem.setRelatedRelationship(navigationTable.usesRelationship.getRelatedRelationship());
		}

		// Get the value of the navigation related primary key:
		final List<LayoutItemField> fieldsToGet = new ArrayList<LayoutItemField>();
		fieldsToGet.add(navigationRelationshipItem);

		// For instance "invoice_line_id" if this is a portal to an "invoice_lines" table:
		final String relatedTableName = portal.getTableUsed("" /* not relevant */);
		final Field primaryKeyField = getPrimaryKeyField(relatedTableName);

		final NavigationRecord navigationRecord = new NavigationRecord();
		String query = null;
		Connection conn = null;
		Statement st = null;
		ResultSet rs = null;
		try {
			// Setup the JDBC driver and get the query.
			conn = cpds.getConnection();
			st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);

			if (primaryKeyValue != null) {

				query = SqlUtils.buildSqlSelectWithKey(relatedTableName, fieldsToGet, primaryKeyField,
						primaryKeyValue);

				rs = st.executeQuery(query);

				// Set the output parameters:
				navigationRecord.setTableName(navigationTable.tableName);

				rs.next();
				final TypedDataItem navigationTablePrimaryKeyValue = new TypedDataItem();
				final ResultSetMetaData rsMetaData = rs.getMetaData();
				final int queryReturnValueType = rsMetaData.getColumnType(1);
				switch (navigationTablePrimaryKey.getGlomType()) {
				case TYPE_NUMERIC:
					if (queryReturnValueType == java.sql.Types.NUMERIC) {
						navigationTablePrimaryKeyValue.setNumber(rs.getDouble(1));
					} else {
						logNavigationTablePrimaryKeyTypeMismatchError(Field.GlomFieldType.TYPE_NUMERIC,
								rsMetaData.getColumnTypeName(1));
					}
					break;
				case TYPE_TEXT:
					if (queryReturnValueType == java.sql.Types.VARCHAR) {
						navigationTablePrimaryKeyValue.setText(rs.getString(1));
					} else {
						logNavigationTablePrimaryKeyTypeMismatchError(Field.GlomFieldType.TYPE_TEXT,
								rsMetaData.getColumnTypeName(1));
					}
					break;
				default:
					Log.error(documentID, tableName, "Unsupported java.sql.Type: " + rsMetaData.getColumnTypeName(1));
					Log.error(documentID, tableName,
							"The navigation table primary key value will not be created. This is a bug.");
					break;
				}

				// The value is empty when there there is no record to match the key in the related table:
				// For instance, if an invoice lines record mentions a product id, but the product does not exist in the
				// products table.
				if (navigationTablePrimaryKeyValue.isEmpty()) {
					Log.info(documentID, tableName, "SQL query returned empty primary key for navigation to the "
							+ navigationTable.tableName + "table. Navigation may not work correctly");
					navigationRecord.setPrimaryKeyValue(null);
				} else {
					navigationRecord.setPrimaryKeyValue(navigationTablePrimaryKeyValue);
				}
			}
		} catch (final SQLException e) {
			Log.error(documentID, tableName, "Error executing database query: " + query, e);
			// TODO: somehow notify user of problem
			return null;
		} finally {
			// cleanup everything that has been used
			try {
				if (rs != null)
					rs.close();
				if (st != null)
					st.close();
				if (conn != null)
					conn.close();
			} catch (final Exception e) {
				Log.error(documentID, tableName,
						"Error closing database resources. Subsequent database queries may not work.", e);
			}
		}

		return navigationRecord;
	}

	private void logNavigationTablePrimaryKeyTypeMismatchError(final GlomFieldType glomType,
			final String queryReturnValueTypeName) {
		Log.error(documentID, tableName, "The expected type from the Glom document: " + glomType
				+ " doesn't match the type returned by the SQL query: " + queryReturnValueTypeName + ".");
		Log.error(documentID, tableName, "The navigation table primary key value will not be created. This is a bug.");

	}
}
