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;

/*
 * An application to integrate the three versions of the University
 * database held in the sql92 datamodel.
 */
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 field=null;
  static Construct pk=null;
  static Construct fk=null;
  static SQLWrapperFactory wf;

  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)) {
      String pk="<<staff_pk,staff,<<staff,id>>>>";
      SchemaObject staffpk=cs.getSchemaObject(pk);
      if (staffpk==null) {
	System.out.println("Schema "+s1.getName()+" does not contain "+pk);
	return null;
      }
      cs=cs.applyRenameTransformation(staffpk,
				      new Object[]{"person_pk",staff,staffid});
    }

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


    SchemaObject personsex=cs.getSchemaObject("<<person,sex>>");
    SchemaObject personid=cs.getSchemaObject("<<person,id>>");

    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,y) <- <<person,sex>>;y='M']",null);
    SchemaObject male=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    // Now add an attribute to that table we just created
    ns=cs.applyAddTransformation(field,new Object[]{male,"id"},
      "[(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,y) <- <<person,sex>>; y='F']",null);
    SchemaObject female=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    ns=cs.applyAddTransformation(field,new Object[] {female,"id"},
       "[(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);
    }

    // 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,y) | x <- <<male>>; y='M'] ++ "+
					"[(x,y) | x <- <<female>>; y='F']",
					 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>>");

    Schema ns=cs.applyAddTransformation(table,
					new Object[] {"dept"},
					"[x | (y,x) <- <<person,dname>>]",
					null);
    SchemaObject dept=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    ns=cs.applyAddTransformation(field,
				 new Object[] {dept,"dname"},
				 "[(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>>");
    SchemaObject degreepk=
      s3.getSchemaObject("<<degree_pk,degree,<<degree,dcode>>>>");
    //SchemaObject runby=
    //  s3.getSchemaObject("<<degree_runby_dept_fk,degree,"+
    //			       "<<degree,dname>>,dept,<<dept,dname>>>>");

    Transformation t3_1=
      Transformation.createContractTransformation(s3,degreetitle,null,null);
    Schema s3a=t3_1.getOtherSchema(s3);
    Transformation t3_2=
      Transformation.createContractTransformation(s3a,degreecode,null,null);
    Schema s3b=t3_2.getOtherSchema(s3a);
    Transformation t3_3=
      Transformation.createContractTransformation(s3b,degreepk,null,null);
    Schema s3c=t3_3.getOtherSchema(s3b);
    Transformation t3_4=
      Transformation.createContractTransformation(s3c,degreedname,null,null);
    Schema s3d=t3_4.getOtherSchema(s3c);
    //Transformation t3_5=
    //  Transformation.createContractTransformation(s3d,runby,null,null);
    //Schema s3e=t3_5.getOtherSchema(s3d);
    Transformation t3_6=
      Transformation.createContractTransformation(s3d,degree,null,null);
    Schema s3f=t3_6.getOtherSchema(s3d);
    Transformation t3_7=
      Transformation.createExtendTransformation(s3f,field,
						new Object[]{person,
							     "name"},
						null,null);
    return t3_7.getOtherSchema(s3f);
  }


  // This is a fairly standard check to see if two schemas can be
  // "integrated" via ID transformations
  public static void integrateSchemas(Schema s1,Schema s2)  
    throws AutoMedException,SQLException,IOException {
      SchemaObject[] aLessB=s1.differenceByName(s2);
      SchemaObject[] bLessA=s2.differenceByName(s1);
      boolean integrationOK=true;

      if (aLessB.length>0) {
	System.out.println(s1.getName()+"-"+s2.getName()+"="+aLessB);
	integrationOK=false;
      }
	  
      if (bLessA.length>0) {
	System.out.println(s2.getName()+"-"+s1.getName()+"="+bLessA);
	integrationOK=false;
      }

      if (integrationOK) {	
	try {
	  Transformation.createIdentTransformations(s1,s2,null,null);
	  System.out.println("Integration of "+s1.getName()+" and "+
			     s2.getName()+" succeeded");
	}
	catch (SchemasNotIdenticalException e) {
	  System.out.println(e.toString());
	  e.printStackTrace(System.out);
	  System.out.println("================");
	  System.out.println("*** Schema " + e.getFromSchema().getName());
	  System.out.println(e.getFromSchema().getAllSchemeNamesString(true, -1, -1));
	  //	  s1.listSchema();
	  System.out.println("----------------");
	  System.out.println("*** Objects not matched");
	  SchemaObject[] fromSo = e.getFromObjectsUnmatched();
	  for (int i=0; i<fromSo.length; i++)
	    System.out.println(fromSo[i].getSchemeNamesString(-1, -1));
	  System.out.println("================");
	  System.out.println("*** Schema " + e.getToSchema().getName());
	  System.out.println(e.getToSchema().getAllSchemeNamesString(true, -1, -1));
	  //	  s2.listSchema();
	  System.out.println("----------------");
	  System.out.println("*** Objects not matched");
	  SchemaObject[] toSo = e.getToObjectsUnmatched();
	  for (int i=0; i<toSo.length; i++)
	    System.out.println(toSo[i].getSchemeNamesString(-1, -1));

	  System.out.println("================");
	}
	catch (Throwable t) {
	  System.out.println("================");
	  System.out.println("*** Schema " + s1.getName());
	  System.out.println(s1.getAllSchemeNamesString(true, -1, -1));
	  System.out.println("----------------");
	  System.out.println("*** Schema " + s2.getName());
	  System.out.println(s2.getAllSchemeNamesString(true, -1, -1));
	  System.out.println("================");
	  System.out.println(t.toString());
	  t.printStackTrace(System.out);
	}
      }
      else
	System.out.println("Integration failed, since it is "+
			   "only allowed if schemas identical");
  }

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

  public static void banner(Schema s) {
    banner(s.getName());
  }

  private static boolean bombOut=false;

  private static Schema verboseGetSchema(String db,String name) 
  throws Exception {
    // Ensure we are clear of all the schemas we are to create
    String sourceName=name+"_src";
    if (Schema.exists(name))
	Schema.getSchema(name).retract();
    if (Schema.exists(sourceName))
	Schema.getSchema(sourceName).retract();

    // Get the Source oriented schema
    SQLWrapper sw=
      wf.build(username,password,driver,urlbase+db,sourceName);
    Schema sourceSchema=sw.getSchema();
    banner(sourceSchema);
    sourceSchema.listSchema();

    // Generate an AutoMed oriented schema
    Schema s=wf.createAutoMedSchema(name);
    banner(s);
    s.listSchema();    
    
    return s;
  }

  static void printHelp() {
    System.out.println("Usage: UniversityDatabaseIntegration [ options ]\n"+
      "  -debug <0-10>         set debug level\n" +
      "  -url <dbURL>          URL where DB located\n" +
      "  -driver <jdbcDriverClass>\n" +
      "  -user <user>\n"+
      "  -password <password>\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("-url")) 
	  urlbase=args[++i];
	else if (args[i].startsWith("-help")) 
	  printHelp();
	else if (args[i].startsWith("-driver")) 
	  driver=args[++i];
	else if (args[i].startsWith("-user")) 
	  username=args[++i];
	else if (args[i].startsWith("-password")) 
	  password=args[++i];
	
	++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 main(String[] args) {
    readArgs(args);

    try {
      System.out.println("AutoMed Repository: "+
			 "Integration of University Databases");
      wf=new PostgresWrapperFactory();
      wf.setFeatures(SQLWrapperFactory.PRIMARY_KEY);

      // level 5 prints all the old debug information, 0 makes it silent
      STR.setDebugLevel(0);

      Schema s1=verboseGetSchema("university1","uni_s1");
      Schema s2=verboseGetSchema("university2","uni_s2");
      Schema s3=verboseGetSchema("university3","uni_s3");
      Schema s4=verboseGetSchema("university4","uni_s4");
      Schema s5=verboseGetSchema("university5","uni_s5");

      if (bombOut) {
	System.out.println("Unable to perform integration "+
			   "due to missing schemas");
	System.exit(0);
      }

      sql92=wf.getAutoMedModel();
      table=sql92.getConstruct(SQLWrapperFactory.AUTOMED_SQL_MODEL_TABLE);
      field=sql92.getConstruct(SQLWrapperFactory.AUTOMED_SQL_MODEL_FIELD);
      pk=sql92.getConstruct(SQLWrapperFactory.AUTOMED_SQL_MODEL_PRIMARY_KEY);
      if (wf.isFeatureInUse(SQLWrapperFactory.FOREIGN_KEY))
        fk=sql92.getConstruct(SQLWrapperFactory.AUTOMED_SQL_MODEL_FOREIGN_KEY);

      //Schema su=s1.union(s4);
      //banner(su);
      //su.listSchema();

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

      //if (su!=null) return;

      Schema sga=transforms1Tosg(s1);
      banner(sga);
      sga.listSchema();

      Schema sgb=transforms2Tosg(s2); 
      banner(sgb);
      sgb.listSchema();

      Schema sgc=transforms3Tosg(s3);
      banner(sgc);
      sgc.listSchema();


      System.out.println("\n\n============== "+
			 "Attempting Integration of Schemas "+
			 "==============\n\n");
      integrateSchemas(sga,sgb);
      integrateSchemas(sga,sgc);
      integrateSchemas(sgb,sgc); 
    }
    catch (SQLException se) { 
      se.printStackTrace();
    }
    catch (IOException ie) {
      ie.printStackTrace();
    }
    catch (AutoMedException ame) {
      ame.printStackTrace();
    }
    catch (Throwable t) {
      System.err.println(t.toString());
      t.printStackTrace();
    }
  }
}
