package query_processing.hospital;

import uk.ac.ic.doc.automed.*;
import uk.ac.ic.doc.automed.reps.*;
import uk.ac.ic.doc.automed.wrappers.*;

/**
 * This application provides an example for the update and query processing in
 * AutoMed. This particular class wraps three Student databases which are
 * integrated using a public schema. The databases are described in the paper
 * 
 * <pre>
 *        [MP03] Peter McBrien and Alexandra Poulovassilis. Defining peer­to­peer 
 *        data integration using both as view rules. In DBISP2P, pages 91--107, 2003. 
 * </pre>
 * 
 * The exact definition of the schemas of the three databases is given in the
 * file HospitalSchema.txt in SQL92 format.
 * 
 * <p>
 * Running Instructions:
 * <p>
 * Following files should be run in the given order:
 * <ol>
 * <li>HospitalDBWrapping.java
 * <li>HospitalDBIntegration.java
 * <li>HospitalDBQuerying.java
 * </ol>
 * 
 * @author <a href="mailto:sandeep@dcs.bbk.ac.uk">Sandeep Mittal</a>
 * @version 1.0
 * 
 */
public class HospitalDBIntegration {

	static Model sql92 = null;

	static Construct table = null;

	static Construct column = null;

	static Construct pk = null;

	static Construct fk = null;

	static SQLWrapperFactory wf;

	static String schemaChoice = null;

	public static void main(String[] args) {
		readArgs(args);
		try {
			System.out
					.println("AutoMed: AutoMed Update and Query processing example: "
							+ "Integration of Hospital Databases");

			// printExpData();
			if (Schema.exists("pubh1"))
				Schema.remove("pubh1");
			Schema hs1 = Schema.getSchema("hs1");
			transformsHS1ToSP(hs1, "pubh1");
			Schema pubh1 = Schema.getSchema("pubh1");
			pubh1.listSchema();
			printPathway(hs1, pubh1);

			if (Schema.exists("pubh2"))
				Schema.remove("pubh2");
			Schema hs2 = Schema.getSchema("hs2");
			transformsHS2ToSP(hs2, "pubh2");
			Schema pubh2 = Schema.getSchema("pubh2");
			pubh2.listSchema();
			printPathway(hs2, pubh2);

			integrateSchemas(pubh1, pubh2, "(++)");

		} catch (AutoMedException ame) {
			ame.printStackTrace();
		} catch (Throwable t) {
			System.err.println(t.toString());
			t.printStackTrace();
		}
	}

	public static Schema transformsHS1ToSP(Schema s1,
			String transformedSchemaName) throws NotFoundException,
			TypeMismatchException, IntegrityException {

		// The `current' schema as we transform s1 into sp
		Schema cs = s1;
		setConstructs(cs.getName() + "_src");

		// HS1 - PUBH1

		// extend table allPatients
		cs = cs.applyExtendTransformation(table,
				new Object[] { "allPatients" },
				"Range [{ni}|{h,ni} <- <<hpatient,ni>>] Any", null);
		SchemaObject allPatients = cs.getSchemaObject("<<allPatients>>");

		// extend column allPatients.ni
		SchemaObject ni = cs.getSchemaObject("<<hpatient,ni>>");
		Object[] niScheme = ni.getSchemeDefinition();
		niScheme[0] = allPatients;
		cs = cs.applyExtendTransformation(column, niScheme,
				"Range [{ni,ni}|{ni}<-<<allPatients>>] Any", null);

		// extend column allPatients.name
		SchemaObject name = cs.getSchemaObject("<<hpatient,name>>");
		Object[] nameScheme = name.getSchemeDefinition();
		nameScheme[0] = allPatients;
        // TODO: Do we need to join with <<allPatients,ni>>??
		cs = cs
				.applyExtendTransformation(
						column,
						nameScheme,
						"Range [{ni,n}|{h,n}<-<<hpatient,name>>;{h,ni}<-<<hpatient,ni>>] Any",
						null);

		// extend column allPatients.sex
		SchemaObject sex = cs.getSchemaObject("<<hpatient,sex>>");
		Object[] sexScheme = sex.getSchemeDefinition();
		sexScheme[0] = allPatients;
		cs = cs
				.applyExtendTransformation(
						column,
						sexScheme,
						"Range [{ni,s}|{h,s}<-<<hpatient,sex>>;{h,ni}<-<<hpatient,ni>>] Any",
						null);

		// extend column allPatients.gp
		SchemaObject gp = cs.getSchemaObject("<<hpatient,gp>>");
		Object[] gpScheme = gp.getSchemeDefinition();
		gpScheme[0] = allPatients;
		cs = cs
				.applyExtendTransformation(
						column,
						gpScheme,
						"Range [{ni,g}|{h,g}<-<<hpatient,gp>>;{h,ni}<-<<hpatient,ni>>] Any",
						null);

		// add primary key allPatients_pk
		SchemaObject niPK = cs.getSchemaObject("<<allPatients,ni>>");
		cs = cs.applyAddTransformation(pk, new Object[] { "allPatients_pk",
				allPatients, niPK }, null, null);

		// extend table allTreatments
		cs = cs
				.applyExtendTransformation(
						table,
						new Object[] { "allTreatments" },
						"Range [{ni,dt}|{t,dt}<-<<treatment,date>>;" +
						"               {t,h}<-<<treatment,hid>>;" +
						"               {h,ni}<-<<hpatient,ni>>] Any",
						null);
		SchemaObject allTreatments = cs.getSchemaObject("<<allTreatments>>");

		// extend column allTreatments.ni
		SchemaObject tni = cs.getSchemaObject("<<hpatient,ni>>");
		Object[] tniScheme = tni.getSchemeDefinition();
		tniScheme[0] = allTreatments;
		cs = cs.applyExtendTransformation(column, tniScheme,
				"Range [{{ni,dt},ni}|{ni,dt}<-<<allTreatments>>] Any", null);

		// extend column allTreatments.date
		SchemaObject tdate = cs.getSchemaObject("<<treatment,date>>");
		Object[] tdateScheme = tdate.getSchemeDefinition();
		tdateScheme[0] = allTreatments;
		cs = cs.applyExtendTransformation(column, tdateScheme,
				"Range [{{ni,dt},dt}|{ni,dt}<-<<allTreatments>>] Any", null);

		// extend column allTreatments.description
		SchemaObject tdesc = cs.getSchemaObject("<<treatment,description>>");
		Object[] tdescScheme = tdesc.getSchemeDefinition();
		tdescScheme[0] = allTreatments;
		cs = cs
				.applyExtendTransformation(
						column,
						tdescScheme,
//						"Range [{{ni,dt},d}|{ni,dt}<-<<allTreatments>>;" +
//						"                   {h,ni}<-<<hpatient,ni>>;" +
//						"                   {t,h}<-<<treatment,hid>>;" +
//						"                   {t,d}<-<<treatment,description>>] Any",
                        "Range [{{ni,dt},d}| {h,ni}<-<<hpatient,ni>>;" +
                        "                    {t,h}<-<<treatment,hid>>;" +
                        "                    {t,dt}<-<<treatment,date>>;" +
                        "                    {t,d}<-<<treatment,description>>] Any",
						null);

		// add primary key allTreatments_pk
		SchemaObject tniPK = cs.getSchemaObject("<<allTreatments,ni>>");
		SchemaObject tdtPK = cs.getSchemaObject("<<allTreatments,date>>");
		cs = cs.applyAddTransformation(pk, new Object[] { "allTreatments_pk",
				allPatients, tniPK, tdtPK }, null, null);

		// delete primary key treatment_pk
		SchemaObject tPK = cs
				.getSchemaObject("<<treatment_pk,treatment,<<treatment,tid>>>>");
		cs = cs.applyDeleteTransformation(tPK, null, null);
		// delete FK treatment_hpatient_fk
		SchemaObject tFK = cs
				.getSchemaObject("<<treatment_hpatient_fk,treatment,<<treatment,hid>>,hpatient,<<hpatient,hid>>>>");
		cs = cs.applyDeleteTransformation(tFK, null, null);
		// delete primary key treatment_pk
		SchemaObject hPK = cs
				.getSchemaObject("<<hpatient_pk,hpatient,<<hpatient,hid>>>>");
		cs = cs.applyDeleteTransformation(hPK, null, null);

		// contract column treatment.date
		SchemaObject treatmentDate = cs.getSchemaObject("<<treatment,date>>");
		cs = cs
				.applyContractTransformation(
						treatmentDate,
						"Range Void [{t,dt}|{ni,dt}<-<<allTreatments>>;" +
						"                   {h,ni}<-<<hpatient,ni>>;" +
						"                   {t,h}<-<<treatment,hid>>]",
						null);
		// contract column treatment.description
		SchemaObject treatmentDesc = cs
				.getSchemaObject("<<treatment,description>>");
		cs = cs
				.applyContractTransformation(
						treatmentDesc,
						"Range Void [{t,d}|{{ni,dt},d}<-<<allTreatments,description>>;" +
						"                  {h,ni}<-<<hpatient,ni>>;" +
						"                  {t,h}<-<<treatment,hid>>]",
						null);
		// contract column treatment.consultant
		SchemaObject tConsultant = cs
				.getSchemaObject("<<treatment,consultant>>");
		cs = cs.applyDeleteTransformation(tConsultant, "Void", null);
		// contract column treatment.hid
		SchemaObject treatmentHid = cs.getSchemaObject("<<treatment,hid>>");
		cs = cs.applyDeleteTransformation(treatmentHid, "Void", null);
		// contract column treatment.tid
		SchemaObject treatmentTid = cs.getSchemaObject("<<treatment,tid>>");
		cs = cs.applyDeleteTransformation(treatmentTid,
				"Void", null);

		// contract column hpatient.age
		SchemaObject hpatientAge = cs.getSchemaObject("<<hpatient,age>>");
		cs = cs.applyDeleteTransformation(hpatientAge, "Void", null);
		// contract column hpatient.name
		SchemaObject hpatientName = cs.getSchemaObject("<<hpatient,name>>");
		cs = cs
				.applyContractTransformation(
						hpatientName,
						"Range Void [{h,n}|{ni,n}<-<<allPatients,name>>;{h,ni}<-<<hpatient,ni>>]",
						null);
		// contract column hpatient.sex
		SchemaObject hpatientSex = cs.getSchemaObject("<<hpatient,sex>>");
		cs = cs
				.applyContractTransformation(
						hpatientSex,
						"Range Void [{h,s}|{ni,s}<-<<allPatients,sex>>;{h,ni}<-<<hpatient,ni>>]",
						null);
		// contract column hpatient.gp
		SchemaObject hpatientGP = cs.getSchemaObject("<<hpatient,gp>>");
		cs = cs
				.applyContractTransformation(
						hpatientGP,
						"Range Void [{h,g}|{ni,g}<-<<allPatients,gp>>;{h,ni}<-<<hpatient,ni>>]",
						null);

		// contract column hpatient.ni
		SchemaObject hpatientNI = cs.getSchemaObject("<<hpatient,ni>>");
        //TODO: Revise this transformation
		cs = cs.applyContractTransformation(hpatientNI,
				"Range Void [{(Const1 ni),ni}|{ni}<-<<allPatients>>]",
				null);
		// contract column hpatient.hid
		SchemaObject hpatientHid = cs.getSchemaObject("<<hpatient,hid>>");
        // TODO: Can we use Const here.
		cs = cs.applyDeleteTransformation(hpatientHid,
				"Void", null);
		// contract table hpatient
		SchemaObject hpatient = cs.getSchemaObject("<<hpatient>>");
		cs = cs.applyDeleteTransformation(hpatient, "Void", null);
		// contract table treatment
		SchemaObject treatment = cs.getSchemaObject("<<treatment>>");
		cs = cs.applyDeleteTransformation(treatment, "Void", null);

		// That should form the pathways from HS1 to PUBH1.
		cs.setName(transformedSchemaName);
		return cs;
	}

	public static Schema transformsHS2ToSP(Schema s2,
			String transformedSchemaName) throws NotFoundException,
			TypeMismatchException, IntegrityException {
		// The `current' schema as we transform s2 into sp
		Schema cs = s2;
		setConstructs(cs.getName() + "_src");

		// extend table allPatients
		cs = cs.applyExtendTransformation(table,
				new Object[] { "allPatients" },
				"Range [{ni}|{ni}<-<<patient>>] Any", null);
		SchemaObject allPatients = cs.getSchemaObject("<<allPatients>>");

		// extend column allPatients.ni
		SchemaObject ni = cs.getSchemaObject("<<patient,ni>>");
		Object[] niScheme = ni.getSchemeDefinition();
		niScheme[0] = allPatients;
		cs = cs.applyExtendTransformation(column, niScheme,
				"Range [{ni,ni}|{ni}<-<<allPatients>>] Any", null);

		// extend column allPatients.name
		SchemaObject name = cs.getSchemaObject("<<patient,lname>>");
		Object[] nameScheme = name.getSchemeDefinition();
		nameScheme[0] = allPatients;
		nameScheme[1] = "name";
		cs = cs
				.applyExtendTransformation(
						column,
						nameScheme,
						//"Range [{ni1,concat [f,' ',l]}|{ni1,f}<-<<patient,fname>>;{ni2,l}<-<<patient,lname>>; ((=) ni1 ni2)] Any",
                        "Range [{ni,concat [f,' ',l]}|{ni,f}<-<<patient,fname>>;{ni,l}<-<<patient,lname>>] Any",
						null);

		// extend column allPatients.sex
		SchemaObject sex = cs.getSchemaObject("<<patient,sex>>");
		Object[] sexScheme = sex.getSchemeDefinition();
		sexScheme[0] = allPatients;
		cs = cs.applyExtendTransformation(column, sexScheme,
				"Range [{ni,s}|{ni,s}<-<<patient,sex>>] Any", null);

		// extend column allPatients.gp
		cs = cs.applyExtendTransformation(column, new Object[] { allPatients,
				"gp", "null", "varchar", "-1" },
				"Range [{ni,'Dr. Davies'}|{ni}<-<<patient>>] Any", null);

		// add primary key allPatients_pk
		SchemaObject niPK = cs.getSchemaObject("<<allPatients,ni>>");
		cs = cs.applyAddTransformation(pk, new Object[] { "allPatients_pk",
				allPatients, niPK }, null, null);

		// extend table allTreatments
		cs = cs.applyExtendTransformation(table,
				new Object[] { "allTreatments" },
				"Range [{ni,dt}|{ni,dt}<-<<event>>] Any", null);
		SchemaObject allTreatments = cs.getSchemaObject("<<allTreatments>>");

		// extend column allTreatments.ni
		SchemaObject eni = cs.getSchemaObject("<<event,ni>>");
		Object[] eniScheme = eni.getSchemeDefinition();
		eniScheme[0] = allTreatments;
		cs = cs.applyExtendTransformation(column, eniScheme,
				"Range [{{ni,dt},ni}|{ni,dt}<-<<allTreatments>>] Any", null);

		// extend column allTreatments.date
		SchemaObject edate = cs.getSchemaObject("<<event,date>>");
		Object[] edateScheme = edate.getSchemeDefinition();
		edateScheme[0] = allTreatments;
		cs = cs.applyExtendTransformation(column, edateScheme,
				"Range [{{ni,dt},dt}|{ni,dt}<-<<allTreatments>>] Any", null);

		// extend column allTreatments.description
		SchemaObject tdesc = cs.getSchemaObject("<<event,description>>");
		Object[] tdescScheme = tdesc.getSchemeDefinition();
		tdescScheme[0] = allTreatments;
		cs = cs.applyExtendTransformation(column, tdescScheme,
				"Range [{{ni,dt},d}|{{ni,dt},d}<-<<event,description>>] Any",
				null);

		// add primary key allTreatments_pk
		SchemaObject eniPK = cs.getSchemaObject("<<allTreatments,ni>>");
		SchemaObject edtPK = cs.getSchemaObject("<<allTreatments,date>>");
		cs = cs.applyAddTransformation(pk, new Object[] { "allTreatments_pk",
				allPatients, eniPK, edtPK }, null, null);

        // delete primary key event_pk
		SchemaObject ePK = cs
				.getSchemaObject("<<event_pk,event,<<event,ni>>,<<event,date>>>>");
		cs = cs.applyDeleteTransformation(ePK, null, null);
		// delete primary key patient_pk
		SchemaObject hPK = cs
				.getSchemaObject("<<patient_pk,patient,<<patient,ni>>>>");
		cs = cs.applyDeleteTransformation(hPK, null, null);

		// contract event.date
		SchemaObject eDate = cs.getSchemaObject("<<event,date>>");
		cs = cs.applyContractTransformation(eDate,
				"Range Void [{{ni,dt},dt}|{ni,dt}<-<<allTreatments>>]", null);
		// contract event.ni
		SchemaObject eventNI = cs.getSchemaObject("<<event,ni>>");
		cs = cs.applyContractTransformation(eventNI,
				"Range Void [{{ni,dt},ni}|{ni,dt}<-<<allTreatments>>]", null);
		// contract event.description
		SchemaObject eDesc = cs.getSchemaObject("<<event,description>>");
		cs = cs
				.applyContractTransformation(
						eDesc,
						"Range Void [{{ni,dt},d}|{{ni,dt},d}<-<<allTreatments,description>>]",
						null);

		// contract column patient.address
		SchemaObject patientAddress = cs.getSchemaObject("<<patient,address>>");
		cs = cs.applyDeleteTransformation(patientAddress, "Void", null);
		// contract column patient.fName
		SchemaObject patientFName = cs.getSchemaObject("<<patient,fname>>");
		cs = cs
				.applyContractTransformation(
						patientFName,
						"Range Void [{ni,(substring n 0 (indexOf n ' '))}|{ni,n}<-<<allPatients,name>>]",
						null);
		// contract column patient.lName
		SchemaObject patientLName = cs.getSchemaObject("<<patient,lname>>");
		cs = cs
				.applyContractTransformation(
						patientLName,
						"Range Void [{ni,(substring n ((indexOf n ' ')+1))}|{ni,n}<-<<allPatients,name>>]",
						null);
		// contract column patient.sex
		SchemaObject patientSex = cs.getSchemaObject("<<patient,sex>>");
		cs = cs.applyContractTransformation(patientSex,
				"Range Void [{ni,s}|{ni,s}<-<<allPatients,sex>>]", null);
		// contract column patient.ni
		SchemaObject patientNI = cs.getSchemaObject("<<patient,ni>>");
		cs = cs.applyContractTransformation(patientNI,
				"Range Void [{ni,ni}|{ni}<-<<allPatients>>]", null);
		// contract table patient
		SchemaObject patient = cs.getSchemaObject("<<patient>>");
		cs = cs.applyContractTransformation(patient,
				"Range Void [{ni}|{ni}<-<<allPatients>>]", null);

		// contract table event
		SchemaObject event = cs.getSchemaObject("<<event>>");
		cs = cs.applyContractTransformation(event,
				"Range Void [{ni,dt}|{ni,dt}<-<<allTreatments>>]", null);

		// That should form the pathways from S2 to SP.
		cs.setName(transformedSchemaName);
		return cs;
	}

	public static void integrateSchemas(Schema s1, Schema s2, String iqlFunction)
			throws AutoMedException {
		if (!iqlFunction.equals("choose") && !iqlFunction.equals("setUnion")
				&& !iqlFunction.equals("(++)")
				&& !iqlFunction.equals("intersect"))
			throw new AutoMedException(
					"Incorrect IQL function used for id transformation: "
							+ iqlFunction);

		System.out.print("Integration of " + s1.getName() + " and "
				+ s2.getName());

		try {
			Transformation t = Transformation.createIdentTransformation(s1, s2,
					null, null);
			t.setProperty(Transformation.PROPERTY_IQL_FUNCTION, iqlFunction);
			System.out.println(" succeeded");
		} catch (SchemasNotIdenticalException e) {
			System.out.println(" failed");
			System.out.print("*** Schema " + e.getFromSchema().getName());
			System.out.println("has following SchemaObjects not matched");
			SchemaObject[] fromSo = e.getFromObjectsUnmatched();
			for (int i = 0; i < fromSo.length; i++)
				System.out.println(fromSo[i]);
			System.out.print("*** Schema " + e.getToSchema().getName());
			System.out.println("has following SchemaObjects not matched");
			SchemaObject[] toSo = e.getToObjectsUnmatched();
			for (int i = 0; i < toSo.length; i++)
				System.out.println(toSo[i]);
		}
	}

	private static void printPathway(Schema source, Schema target)
			throws IntegrityException, NotFoundException {
		banner("Shortest Pathway from Pathway class");
		Pathway path = Pathway.findShortestPathway(source, target);
		Transformation[] t = path.getTransformations();
		printTransformations(t);
	}

	private static void setConstructs(String source) throws IntegrityException,
			NotFoundException {
		wf = new SQLWrapperFactory();
		wf.setFeatures(Schema.getSchema(source).getAccessMethods()[0]
				.getAutoMedWrapperFactoryFeatures());
		sql92 = wf.getAutoMedModel();
		table = sql92.getConstruct(SQLWrapperFactory.AUTOMED_SQL_MODEL_TABLE);
		column = sql92.getConstruct(SQLWrapperFactory.AUTOMED_SQL_MODEL_COLUMN);
		if (wf.isFeatureInUse(SQLWrapperFactory.PRIMARY_KEY))
			pk = sql92
					.getConstruct(SQLWrapperFactory.AUTOMED_SQL_MODEL_PRIMARY_KEY);
		if (wf.isFeatureInUse(SQLWrapperFactory.FOREIGN_KEY))
			fk = sql92
					.getConstruct(SQLWrapperFactory.AUTOMED_SQL_MODEL_FOREIGN_KEY);
	}

	static void printHelp() {
		System.out.println("Usage: HospitalDBIntegration [ options ]\n"
				+ "  -debug <0-10>         set debug level\n"
				+ "  -schema <schema name>\n");
	}

	public static void readArgs(String[] args) {
		int i = 0;
		try {
			while (i < args.length) {
				if (args[i].charAt(0) != '-') {
					System.out.println("Error at " + args[i] + "\n");
					printHelp();
					System.exit(10);
				} else if (args[i].startsWith("-debug"))
					STR.setDebugLevel(Integer.parseInt(args[++i]));
				else if (args[i].startsWith("-schema"))
					schemaChoice = args[++i];
				else if (args[i].startsWith("-help"))
					printHelp();
				++i;
			}

			if (STR.atDebugLevel(5))
				System.out.println("Finished reading arguments, "
						+ "about to read database");

		} catch (ArrayIndexOutOfBoundsException e) {
			System.out.println("You didn't supply an argument to the "
					+ args[i - 1] + " option!");
			printHelp();
			System.exit(10);
		}
	}

	public static void banner(String title) {
		int len = title.length();
		String stars = "**********************************************"
				+ "*********************************";
		int slen = stars.length();

		System.out.println(stars);
		if (len > slen - 4)
			System.out.println(title);
		else
			System.out.println(stars.substring(0, (slen - len) / 2 - 1) + " "
					+ title + (len % 2 == 0 ? " " : "  ")
					+ stars.substring(0, (slen - len) / 2));
		System.out.println(stars);
	}

	private static void printTransformations(Transformation[] t) {
		for (int i = 0; i < t.length; i++) {
			System.out.println(i + " " + t[i].toString());
		}
	}

} // end StudentDatabaseIntegration

