import java.sql.*;
import java.io.*;
import uk.ac.ic.doc.automed.*;
import uk.ac.ic.doc.automed.reps.*;
import uk.ac.ic.doc.automed.wrappers.*;
import java.util.ArrayList;
import utils.AutoMedUtils;

/*
 * An application to integrate the three versions of the University
 * database held in the sql92 datamodel.
 * Try the following on uni_t2 (the first schema after uni_s3)
 * 
 * distinct [{x,y,z} | {i,x} <- <<person,name>> ; {i,y} <- <<person,dname>> ; {j,z} <- <<degree,dcode>>; {j,y} <- <<degree,dname>>]
 */
public class UniversityDatabaseIntegration {
  static String username="pjm";
  static String password=null;
  static String driver="org.postgresql.Driver";
  static String urlbase="jdbc:postgresql://localhost/";
  static Model sql92=null;
  static Construct table=null;
  static Construct column=null;
  static Construct pk=null;
  static Construct fk=null;
  static SQLWrapperFactory wf;
  static boolean extra=false;
  static String schemaChoice=null;

  public static Schema transforms1Tosg(Schema s1) 
    throws NotFoundException,TypeMismatchException,IntegrityException {
    // The `current' schema as we transform s1 into sg
    Schema cs=s1;
    Schema ns;

    // Rename the staff table to be "person"
    SchemaObject staff=cs.getSchemaObject("<<staff>>");
    //SchemaObject staffid=cs.getSchemaObject("<<staff,id>>");
   
    if (wf.isFeatureInUse(SQLWrapperFactory.PRIMARY_KEY)) {
      SchemaObject pk=cs.getSchemaObject("<<staff_pk,staff,<<staff,id>>>>");
      Object scheme[]=pk.getSchemeDefinition();
      scheme[0]="person_pk";

      cs=cs.applyRenameTransformation(pk,scheme);
    }

    if (wf.isFeatureInUse(SQLWrapperFactory.FOREIGN_KEY)) {
      SchemaObject fk=
	cs.getSchemaObject("<<staff_worksin_dept_fk,staff,"+
			   "<<staff,dname>>,dept,<<dept,dname>>>>");
      Object scheme[]=fk.getSchemeDefinition();
      scheme[0]="person_worksin_dept_fk";

      cs=cs.applyRenameTransformation(fk,scheme);
    }

    ns=cs.applyRenameTransformation(staff,new Object[]{"person"});
    SchemaObject person=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    person=(SchemaObject)person.getSchemeDefinition()[0];
    cs=ns;


    SchemaObject personsex=cs.getSchemaObject("<<person,sex>>");
    SchemaObject personid=cs.getSchemaObject("<<person,id>>");
    Object personidscheme[]=personid.getSchemeDefinition();

    if (personsex==null || personid==null) {
      System.out.println("Schema "+s1.getName()+" does not contain "+
			 "<<staff,sex>> and <<staff,id>>");
      return null;
    }

    // Create the next schema in the pathway (a "male" table) and
    // make a note of the schema object used to represent it
    ns=cs.applyAddTransformation(table,new Object[] {"male"},
      "[{x} | {x,'M'} <- <<person,sex>>]",null);
    SchemaObject male=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    // Now add an attribute to that table we just created
    personidscheme[0]=male;
    ns=cs.applyAddTransformation(column,personidscheme,
      "[{x,x} | {x} <- <<male>>]",null);
    SchemaObject maleid=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    // Do a similar pair of steps to create a female table
    ns=cs.applyAddTransformation(table,new Object[] {"female"},
       "[{x} | {x,'F'} <- <<person,sex>>]",null);
    SchemaObject female=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    personidscheme[0]=female;
    ns=cs.applyAddTransformation(column,personidscheme,
       "[{x,x} | {x} <- <<female>>]",null);
    SchemaObject femaleid=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    // Test the wrapper to see if we are modelling primary keys before
    // adding one for male
    if (wf.isFeatureInUse(SQLWrapperFactory.PRIMARY_KEY)) {
      cs=cs.applyAddTransformation(pk,
	new Object[]{"male_pk",male,maleid},null,null);
    }

    Object[] fks=new Object[]{"male_isa_person_fk",
			      male,maleid,person,personid};
    //for (int i=0;i<fks.length;i++)
    //  System.out.println("SO["+i+"]"+fks[i].getClass().getName()+" = "+
    //			 fks[i]);

    // and test to see if we are modelling foreign keys ...
    if (wf.isFeatureInUse(SQLWrapperFactory.FOREIGN_KEY)) {
      cs=cs.applyAddTransformation
	(fk,new Object[]{"male_isa_person_fk",male,maleid,person,personid},
	null,null);
    }

    // Similar to PK and FK on female table
    if (wf.isFeatureInUse(SQLWrapperFactory.PRIMARY_KEY)) {
      cs=cs.applyAddTransformation
	(pk,new Object[]{"female_pk",female,femaleid},null,null);
    }


    if (wf.isFeatureInUse(SQLWrapperFactory.FOREIGN_KEY)) {
      cs=cs.applyAddTransformation
	(fk,new Object[]{"female_isa_person_fk",
			 female,femaleid,person,personid},null,null);
    }

    // Finally delete the unwanted personsex attribute
    return cs.
      applyDeleteTransformation(personsex,
				"[{x,'M'} | {x} <- <<male>>] ++ "+
				"[{x,'F'} | {x} <- <<female>>]",
				null);
  }

  public static Schema transforms2Tosg(Schema s2) 
    throws NotFoundException,TypeMismatchException,IntegrityException {
    Schema cs=s2;

    // Check that we have the objects that we want to transform
    SchemaObject person=s2.getSchemaObject("<<person>>");
    SchemaObject persondname=
      s2.getSchemaObject("<<person,dname>>");
    Object persondnamescheme[]=persondname.getSchemeDefinition();
    Schema ns=cs.applyAddTransformation(table,
					new Object[] {"dept"},
					"[{x} | {y,x} <- <<person,dname>>]",
					null);
    SchemaObject dept=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    persondnamescheme[0]=dept;
    persondnamescheme[1]="dname";
    ns=cs.applyAddTransformation(column,persondnamescheme,
				 "[{x,x} | {x} <- <<dept>>]",
				 null);
    SchemaObject deptdname=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    if (wf.isFeatureInUse(SQLWrapperFactory.PRIMARY_KEY)) {

      cs=cs.applyAddTransformation(pk,new Object[] {"dept_pk",dept,deptdname},
      null,null);
    }

    if (wf.isFeatureInUse(SQLWrapperFactory.FOREIGN_KEY)) {
      cs=cs.applyAddTransformation(fk,
	new Object[] {"person_worksin_dept_fk",person,
		      persondname,dept,deptdname},null,null);
    }

    return cs;
  }

  public static Schema transforms3Tosg(Schema s3) 
    throws NotFoundException,TypeMismatchException,IntegrityException {

    // Check that we have the objects that we want to transform
    SchemaObject person=s3.getSchemaObject("<<person>>");
    SchemaObject degree=s3.getSchemaObject("<<degree>>");
    SchemaObject degreecode=s3.getSchemaObject("<<degree,dcode>>");
    SchemaObject degreetitle=s3.getSchemaObject("<<degree,title>>");
    SchemaObject degreedname=s3.getSchemaObject("<<degree,dname>>");
    Object[] personnamescheme=degreedname.getSchemeDefinition();
    personnamescheme[0]=person;
    personnamescheme[1]="name";
    Schema cs=s3.applyExtendTransformation(column,personnamescheme,null,null);
    cs=cs.applyContractTransformation(degreetitle,null,null);
    
    if (wf.isFeatureInUse(SQLWrapperFactory.PRIMARY_KEY))
      cs=cs.applyDeleteTransformation(
	s3.getSchemaObject("<<degree_pk,degree,<<degree,dcode>>>>"),null,null);


    if (wf.isFeatureInUse(SQLWrapperFactory.FOREIGN_KEY))
      cs=cs.applyDeleteTransformation(
	s3.getSchemaObject("<<degree_runby_dept_fk,degree,"+
			   "<<degree,dname>>,dept,<<dept,dname>>>>"),
	null,null);

    cs=cs.applyContractTransformation(degreecode,null,null);
    cs=cs.applyContractTransformation(degreedname,null,null);
    cs=cs.applyContractTransformation(degree,null,null);
    personnamescheme[0]=person;
    return cs;
  }

 static void printHelp() {
    System.out.println("Usage: UniversityDatabaseIntegration [ 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);
    }
  }

  private static void setConstructs(String source) throws Exception {
    // Will assume that all schemas have been wrapped using the same
    // sql model variant
    if (table!=null) return;

    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);
  }

  public static void main(String[] args) {
    readArgs(args);

    try {
      System.out.println("AutoMed Repository: "+
			 "Integration of University Databases");


      Schema sga=null;
      Schema sgb=null;
      Schema sgc=null;
      
      if (schemaChoice==null || "uni_s1".equals(schemaChoice)) {
	setConstructs("uni_s1_src");
	Schema s1=Schema.getSchema("uni_s1");
	sga=transforms1Tosg(s1);
	AutoMedUtils.banner(sga);
	sga.listSchema();
      }

      if (schemaChoice==null || "uni_s2".equals(schemaChoice)) {
	setConstructs("uni_s2_src");
	Schema s2=Schema.getSchema("uni_s2");
	sgb=transforms2Tosg(s2); 
	AutoMedUtils.banner(sgb);
	sgb.listSchema();
      }

      if (schemaChoice==null || "uni_s3".equals(schemaChoice)) {
	setConstructs("uni_s3_src");
	Schema s3=Schema.getSchema("uni_s3");
	sgc=transforms3Tosg(s3);
	AutoMedUtils.banner(sgc);
	sgc.listSchema();
      }

      Schema s4;
      if (schemaChoice==null || "uni_s4".equals(schemaChoice)) {
	setConstructs("uni_s4_src");
	s4=Schema.getSchema("uni_s4");
      }

      Schema s5=null;
      if (schemaChoice==null || "uni_s5".equals(schemaChoice)) {
	setConstructs("uni_s5_src");
	s5=Schema.getSchema("uni_s5");
      }

      //Schema[] su=s1.match(s2);
      //banner(su[0]);
      //su[0].listSchema();

      //TransformationPathway.findShortestPath(s1,su).write();
      //TransformationPathway.findShortestPath(s4,su).write();

      //if (su!=null) return;



      System.out.println("\n\n============== "+
			 "Attempting Integration of Schemas "+
			 "==============\n\n");

      if (sga!=null && sgb!=null)
	AutoMedUtils.integrateSchemas(sga,sgb);
      if (sga!=null && sgc!=null)
	AutoMedUtils.integrateSchemas(sga,sgc);
      if (sgb!=null && sgc!=null)
	AutoMedUtils.integrateSchemas(sgb,sgc);

      if (extra && sga!=null && s5!=null) {
	System.out.println("\n\n============== "+
			   "Attempting Union of s1s2s3 with s5 "+
			   "==============\n\n");  
	
	Schema s1s5=sga.union(s5);
	s1s5.listSchema();
      }
    }
    catch (Throwable t) {
      System.err.println(t.toString());
      t.printStackTrace();
    }
  }
}
