package query_processing.hospital.update;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import uk.ac.bbk.dcs.automed.qproc.QProcLogger;
import uk.ac.bbk.dcs.automed.qproc.QueryProcessorConfiguration;
import uk.ac.bbk.dcs.automed.qproc.iql.ASG;
import uk.ac.bbk.dcs.automed.qproc.iql.Cell;
import uk.ac.bbk.dcs.automed.qproc.iql.ParseException;
import uk.ac.bbk.dcs.automed.qproc.iql.SchemeInfo;
import uk.ac.bbk.dcs.automed.qproc.reformulate.BuildViewException;
import uk.ac.bbk.dcs.automed.qproc.reformulate.CompositeViewMap;
import uk.ac.bbk.dcs.automed.qproc.reformulate.ViewKey;
import uk.ac.ic.doc.automed.NotFoundException;
import uk.ac.ic.doc.automed.reps.Schema;

/**
 * Responsible for reformulating an update request along AutoMed BAV pathways.
 * 
 * @author <a href="mailto:sandeep@dcs.bbk.ac.uk">Sandeep Mittal</a>
 */
public class UpdateReformulator {

	private final String _schemaPrefix = "$";

	protected Logger _logger = QProcLogger.getLogger();

	/**
	 * Constructs an instance of this class.
	 */
	public UpdateReformulator() {
		// Empty constructor
	}

	public void reformulate(String updmsg, Schema source, Schema target)
			throws UpdateReformulationException {
		// Does not work at the moment.
		//reformulateInternal(updmsg, source, target);
	}

	private void reformulateInternal(String updmsg, Schema source, Schema target)
			throws UpdateReformulationException {

		// Assert update message on the source schema
		// ReformulationUtility.updateSchemeInfo(query, source);
		// Reformulate
		o("Update Request: " + updmsg);
		convertUpdateToTarget(updmsg, source, target);
		o("Reformulated Update Request: " + updmsg);
	}

	private void convertUpdateToTarget(String updmsg, Schema source,
			Schema target) throws UpdateReformulationException {
		LinkedList stack = new LinkedList();
		stack.add(updmsg);
		Cell current;
		while (!stack.isEmpty()) {
			current = (Cell) stack.removeFirst();
			if (current.isScheme()) {
				// Get the definition of the construct from all the target
				// schemas for the particular source schema construct.
				convertSchemeToTarget(current, source, target);
			} else {
				if (current.l() != null)
					stack.add(current.l());
				if (current.r() != null)
					stack.add(current.r());
			}
		}
	}

	private void convertSchemeToTarget(Cell n, Schema source, Schema target)
			throws UpdateReformulationException {
		SchemeInfo si = (SchemeInfo) (n.data());
		ViewKey vk = new ViewKey(si);
		Map finalViewMap = new HashMap();

		// Get the binary view maps for source and target schema
		// combinations
		CompositeViewMap targetCompMap = getCompositeViewMap(source, target);
		Map targetMap = targetCompMap.getViewMap();
		Cell targetView = (Cell) targetMap.get(vk);
		finalViewMap.put(target.getName(), targetView);
//		Cell idc = getTargetsIdSemantics(integrationSemantics, source, targets);
//		replaceIdGraph(idc, finalViewMap);
//		n.mimicThisCell(idc);
	}

	private Schema getDualModelDataSource(Schema target) {
		Schema extSchema = target.getSubnetSchema();
		return extSchema;
	}

	private CompositeViewMap getCompositeViewMap(Schema source, Schema target)
			throws UpdateReformulationException {
		try {
			CompositeViewMap m = CompositeViewMap.getInstance(source, target, new QueryProcessorConfiguration());
			return m;
		} catch (BuildViewException e) {
			throw new UpdateReformulationException(
					"Error fetching composite view maps", e);

		}
	}

	private void replaceIdGraph(Cell idc, Map finalViewMap)
			throws UpdateReformulationException {
		if (idc == null) {
			return;
		}
		if (idc.isVarOrSysVar()) {
			String idcTarget = (String) idc.data();
			if (idcTarget.startsWith(_schemaPrefix)) {
				String targetSchemaName = idcTarget.substring(1);
				Cell targetView = (Cell) finalViewMap.get(targetSchemaName);
				if (targetView != null) {
					idc.mimicThisCell(targetView.copyOfGraph());
					return;
				} else {
					// problem reformulating. There should have been a view here
					// for every target.
					throw new UpdateReformulationException(
							"Problem while applying ID semantics. This should not occur.");
				}
			} else {
				throw new UpdateReformulationException(
						"Invalid target name in ID Semantics string. Target name should start with a "
								+ _schemaPrefix + " " + idcTarget);
			}
		} else {
			replaceIdGraph(idc.l(), finalViewMap);
			replaceIdGraph(idc.r(), finalViewMap);
		}
	}

	/**
	 * Semantics of how to combine the results from data sources. Given a set of
	 * data sources, e.g. DS1 , DS2 and DS3 , a user has the following ways of
	 * specifying the integration semantics to an instance of GQP:
	 * <ul>
	 * <li>Shorthand: The following four integration operators can be used as a
	 * shorthand notation to specify a generic integration semantics for all the
	 * target data sources, without specifying the names of the data sources.
	 * The GQP combines the results of all the target data sources using the
	 * same operator in the order the target data sources are specified in the
	 * input target data source array.
	 * <ul>
	 * <li>Choose semantics: values coming from one data source are re­ turned
	 * (choose DS1 DS2 ); which data source is chosen depends on the
	 * ChooseOptimiser (Section 3.6) or, if the ChooseOptimiser is not used, the
	 * choose IQL function (Appendix A).
	 * <li>Append semantics: all values from all data sources are returned
	 * ((++) DS1 DS2 ).
	 * <li>Intersect semantics: a value is returned only if it is present in
	 * all data sources (intersect DS1 DS2 ).
	 * <li>Union semantics: a value is returned if it is present in any data
	 * source (distinct ((++) DS1 DS2)).
	 * </ul>
	 * <li>Verbose: In verbose form, a user must specify an expression
	 * containing the names of each of the target datasources and joined
	 * together using any the operators described above or any of the set
	 * operators, such as, -- (monus). Data source name must be prefixed by a $
	 * symbol. For example, integration semantics can be as simple as DS1 ++ DS2 ++
	 * DS3 or arbitrarily complex, e.g. (DS1 intersect DS2 ) ++ (DS1 -- DS2)
	 * </ul>
	 * 
	 * @throws QueryReformulationException
	 */
	private Cell getTargetsIdSemantics(String integrationSemantics,
			Schema source, Schema[] targets)
			throws UpdateReformulationException {
		ASG idSemGraph = null;
		try {
			if (targets.length == 1) {
				// If there is only one target, integration semantics
				// have no meaning.
				idSemGraph = new ASG(_schemaPrefix + targets[0].getName());
			} else if (integrationSemantics != null) {
				// Handle user defined integration semantics.
				// User-defined integration semantics override those
				// provided by the integrator at the time of defining
				// the BAV pathways.
				if (integrationSemantics.equals("choose")
						|| integrationSemantics.equals("++")
						|| integrationSemantics.equals("distinct ++")
						|| integrationSemantics.equals("intersect")) {
					// A user may specify a single collection operation to be
					// applied
					// to all the datasources when integrating the results.
					String idSem = createIdQueryString(integrationSemantics,
							targets);
					idSemGraph = new ASG(idSem);
				} else {
					// Or the user may provide a more complex way of integrating
					// the results by providing integrating semantics using
					// schema names (prefixed with $) and using collection
					// operations
					// to integrate the results. For example,
					// $s1 ++ ($s2 -- $s3)
					idSemGraph = new ASG(integrationSemantics);
				}
			} else {
				// No integration semantics provided by user. Get from the
				// tranformation
				// pathways
				idSemGraph = new ASG(
						getIdSemanticsFromPathways(source, targets));
			}
		} catch (ParseException e) {
			throw new UpdateReformulationException(
					"Invalid target ID semantics", e);
		}
		o(idSemGraph.toString());
		return idSemGraph.root();
	}

	private String createIdQueryString(String integrationSemantics,
			Schema[] targets) {
		String idSem = "";
		for (int i = 0; i < targets.length; i++) {
			if (i == 0) {
				idSem = _schemaPrefix + targets[i].getName();
			} else {
				idSem = "((" + integrationSemantics + ") " + idSem + " "
						+ _schemaPrefix + targets[i].getName() + ")";
			}
		}
		return idSem;
	}

	private String getIdSemanticsFromPathways(Schema source, Schema[] targets) {
		// FIXME: Till the time we resolve how to get ID semantics from the
		// transformation pathways, we just pass back a default value.
		return createIdQueryString("++", targets);
	}

	private void changeCollectionSemantics(Cell c, String desiredColType) {
		if (c == null)
			return;
		else if (c.representsCollection()) {
			String s = "";
			if (desiredColType.equals("Bag"))
				s = "BCons";
			else if (desiredColType.equals("Set"))
				s = "SCons";
			else if (desiredColType.equals("List"))
				s = "Cons";

			Cell k = c;
			Cell l;

			while (!k.isCollectionNilConstructor()) {
				l = k.l().l();
				l.setData(s);
				k = k.r();
			}
		} else if (c.representsComprehension()) {
			String s = "";
			if (desiredColType.equals("Bag"))
				s = "BComp";
			else if (desiredColType.equals("Set"))
				s = "SComp";
			else if (desiredColType.equals("List"))
				s = "LComp";

			Cell k = c;
			Cell l;
			while (k.representsComprehension()) {
				l = k.l().l();
				l.setData(s);
				changeCollectionSemantics(k.l().r(), desiredColType);
				k = k.r();
			}
		} else {
			changeCollectionSemantics(c.l(), desiredColType);
			changeCollectionSemantics(c.r(), desiredColType);
		}
	}

	/**
	 * Logging functionality
	 * 
	 * @param s
	 * @param logLevel
	 */
	private void o(Object s, Level logLevel) {
		_logger.log(logLevel, s.toString());
	}

	private void o(Object s) {
		o(s, Level.FINEST);
	}

	// Test method
	public static void main(String[] args) throws Exception {
		QueryProcessorConfiguration aqpc = new QueryProcessorConfiguration();
		aqpc.setRangeSemantics(QueryProcessorConfiguration.RANGE_LOWER);
		aqpc
				.setReformulationSemantics(QueryProcessorConfiguration.REFORMULATION_GAV);
		UpdateReformulator reform = new UpdateReformulator();
		reform._logger = QProcLogger.getDebugLogger();
		// String q = "[{s}|{s,d}<-<<student,dcode>>; {d,dn}<- <<degree,dname>>;
		// dn='BSc Mathematics']";
		String q = "[{c}|{c,c}<-<<course,ccode>>]";
		String idSem = "++";
		ASG c = new ASG(q);
		Schema[] targets = new Schema[] { Schema.getSchema("s1"),
				Schema.getSchema("s2"), Schema.getSchema("s3") };
		// reform.reformulate(c, Schema.getSchema("s1sp"), targets, idSem,
		// aqpc);
	}
}
