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

/*
 * An application to integrate the three versions of the University
 * database held in the sql92 datamodel.
 */

public class UniversityDatabaseIntegration {
  static Model sql92=null;
  static Construct table=null;
  static Construct field=null;
  static Construct pk=null;
  static Construct fk=null;

  public static Schema transforms1Tosu(Schema s1) 
    throws TypeMismatchException, IntegrityException, NotFoundException {

    return extends1s2s3tosu(s1);
  }


  public static Schema transforms2Tosu(Schema s2) 
    throws TypeMismatchException, IntegrityException, NotFoundException {

    // Check that we have the objects that we want to transform
    SchemaObject staffdname=s2.getSchemaObject("<<staff,dname>>");
    SchemaObject staff=s2.getSchemaObject("<<staff>>");
    SchemaObject male=s2.getSchemaObject("<<male>>");
    SchemaObject female=s2.getSchemaObject("<<female>>");
    SchemaObject male_id=s2.getSchemaObject("<<male,id>>");
    SchemaObject female_id=s2.getSchemaObject("<<female,id>>");
    SchemaObject male_pk=s2.getSchemaObject("<<male_pk,male,<<male,id>>>>");
    SchemaObject female_pk=
      s2.getSchemaObject("<<female_pk,female,<<female,id>>>>");
    SchemaObject male_fk=
      s2.getSchemaObject("<<male_isa_staff_fk,male,<<male,id>>,"+
			 "staff,<<staff,id>>>>");
    SchemaObject female_fk=
      s2.getSchemaObject("<<female_isa_staff_fk,female,<<female,id>>,"+
			 "staff,<<staff,id>>>>");

    Schema s2a=s2.applyAddTransformation(field,new Object[]{staff,"sex"},
    "[(x,'M') | x <-- <<male>>] ++ [(x,'F') | x <-- <<female>>]",null);
    Schema s2b=s2a.applyDeleteTransformation(male_pk,null,null);
    Schema s2c=s2b.applyDeleteTransformation(female_pk,null,null);
    Schema s2d=s2c.applyDeleteTransformation(male_fk,null,null);
    Schema s2e=s2d.applyDeleteTransformation(female_fk,null,null);
    Schema s2f=s2e.applyDeleteTransformation(male_id,
					 "[(x,x) | x <-- <<male>>]",
					 null);
    Schema s2g=s2f.applyDeleteTransformation(male,
      "[x | (x,'M') <-- <<staff,sex>>]",null,null);
    Schema s2h=s2g.applyDeleteTransformation(female_id,
					 "[(x,x) | x <-- <<female>>]",
					 null);
    Schema s2i=s2h.applyDeleteTransformation(female,
					  "[x | (x,'F') <-- <<staff,sex>>]",
					  null);
    Schema s2j=s2i.applyAddTransformation(table,
					 new Object[] {"dept"},
					 "[x | (_,x) <-- <<staff,dname>>]",
					 null);

    SchemaObject dept=
      Transformation.getTransformation(s2i,s2j).getSchemaObject(s2j);
    Schema s2k=s2j.applyAddTransformation(field,
					 new Object[] {dept,"dname"},
					 "[(x,x) | x <-- <<dept>>]",
					 null);
    SchemaObject deptdname=
      Transformation.getTransformation(s2j,s2k).getSchemaObject(s2k);
    Schema s2l=s2k.applyAddTransformation(pk,
					  new Object[] {"dept_pk",dept,deptdname},
					  null,
					  null);
    Schema s2m=
      s2l.applyAddTransformation(fk,
        new Object[] {"staff_worksin_dept_fk",staff,staffdname,dept,deptdname},
	null,null);

    return extends1s2s3tosu(s2m);
  }

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

    // Check that we have the objects that we want to transform
    SchemaObject person=s3.getSchemaObject("<<person>>");
    SchemaObject male=s3.getSchemaObject("<<male>>");
    SchemaObject female=s3.getSchemaObject("<<female>>");
    SchemaObject male_id=s3.getSchemaObject("<<male,id>>");
    SchemaObject female_id=s3.getSchemaObject("<<female,id>>");
    SchemaObject male_pk=s3.getSchemaObject("<<male_pk,male,<<male,id>>>>");
    SchemaObject female_pk=
      s3.getSchemaObject("<<female_pk,female,<<female,id>>>>");
    SchemaObject male_fk=
      s3.getSchemaObject("<<male_isa_person_fk,male,<<male,id>>,"+
			 "person,<<person,id>>>>");
    SchemaObject female_fk=
      s3.getSchemaObject("<<female_isa_person_fk,female,<<female,id>>,"+
			 "person,<<person,id>>>>");

    Schema s2a=s3.applyAddTransformation(field,new Object[]{person,"sex"},
    "[(x,'M') | x <-- <<male>>] ++ [(x,'F') | x <-- <<female>>]",null);
    Schema s2b=s2a.applyDeleteTransformation(male_pk,null,null);
    Schema s2c=s2b.applyDeleteTransformation(female_pk,null,null);
    Schema s2d=s2c.applyDeleteTransformation(male_fk,null,null);
    Schema s2e=s2d.applyDeleteTransformation(female_fk,null,null);
    Schema s2f=s2e.applyDeleteTransformation(male_id,
					 "[(x,x) | x <-- <<male>>]",
					 null);
    Schema s2g=s2f.applyDeleteTransformation(male,
      "[x | (x,'M') <-- <<person,sex>>]",null,null);
    Schema s2h=s2g.applyDeleteTransformation(female_id,
					 "[(x,x) | x <-- <<female>>]",
					 null);
    Schema s2i=s2h.applyDeleteTransformation(female,
					  "[x | (x,'F') <-- <<person,sex>>]",
					  null);
    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>>>>");

    Schema s3p1=s2i.applyRenameTransformation(person,new Object[]{"staff"});
    SchemaObject staff=s3p1.getSchemaObject("<<staff>>");
    SchemaObject staff_pk=
      s3p1.getSchemaObject("<<person_pk,staff,<<staff,id>>>>");
    SchemaObject staff_fk=s3p1.getSchemaObject("<<person_worksin_dept_fk,"+
      "staff,<<staff,dname>>,dept,<<dept,dname>>>>");
    SchemaObject staff_id=s3p1.getSchemaObject("<<staff,id>>");
    SchemaObject staff_dname=s3p1.getSchemaObject("<<staff,dname>>");
    SchemaObject dept=s3p1.getSchemaObject("<<dept>>");
    SchemaObject dept_dname=s3p1.getSchemaObject("<<dept,dname>>");
    Schema s3p2=s3p1.applyRenameTransformation(staff_pk,
      new Object[]{"staff_pk",staff,staff_id});
    Schema s3p3=s3p2.applyRenameTransformation(staff_fk,
      new Object[]{"staff_worksin_dept_fk",staff,staff_dname,dept,dept_dname});
    
    Transformation t3_1=
      Transformation.createContractTransformation(s3p3,degreetitle,null,null);
    Schema s3a=t3_1.getOtherSchema(s3p3);
    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(s3e,degree,null,null);
    Schema s3f=t3_6.getOtherSchema(s3e);
    Schema s3g=
      s3f.applyExtendTransformation(field,new Object[]{staff,"name"},
      null,null);
    return extends1s2s3tosu(s3g);
  }

  public static Schema transforms4Tosu(Schema s4) 
    throws TypeMismatchException, IntegrityException, NotFoundException {

    // Check that we have the objects that we want to transform
    SchemaObject student=s4.getSchemaObject("<<student>>");
    SchemaObject degree=s4.getSchemaObject("<<degree>>");
    SchemaObject degreecode=s4.getSchemaObject("<<degree,dcode>>");
    SchemaObject degreedname=s4.getSchemaObject("<<degree,dname>>");
    SchemaObject degreepk=s4.getSchemaObject("<<degree_pk,degree,<<degree,dcode>>>>");
    SchemaObject runby=
      s4.getSchemaObject("<<degree_runby_dept_fk,degree,<<degree,dname>>,dept,<<dept,dname>>>>");

    
    //Schema s4a=t3_1.getOtherSchema(s4);
    Transformation t3_2=
      Transformation.createContractTransformation(s4,degreecode,null,null);
    Schema s4b=t3_2.getOtherSchema(s4);
    Transformation t3_3=
      Transformation.createContractTransformation(s4b,degreepk,null,null);
    Schema s4c=t3_3.getOtherSchema(s4b);
    Transformation t3_4=
      Transformation.createContractTransformation(s4c,degreedname,null,null);
    Schema s4d=t3_4.getOtherSchema(s4c);
    Transformation t3_5=
      Transformation.createContractTransformation(s4d,runby,null,null);
    Schema s4e=t3_5.getOtherSchema(s4d);
    Transformation t3_6=
      Transformation.createContractTransformation(s4e,degree,null,null);
    Schema s4f=t3_6.getOtherSchema(s4e);
    return extends1s2s3s4tosu(s4f);
  }
 
  public static Schema extends1s2s3s4tosu(Schema sx) 
  throws TypeMismatchException, IntegrityException, NotFoundException {
    SchemaObject dept=sx.getSchemaObject("<<dept>>");

    Schema sxa=
      sx.applyExtendTransformation(table,new Object[] {"college"},null,null);
    SchemaObject college=
      Transformation.getTransformation(sx,sxa).getSchemaObject(sxa);
    Schema sxb=
      sxa.applyExtendTransformation(field,new Object[] {college,"cname"},
					     null,null);
    SchemaObject college_cname=
      Transformation.getTransformation(sxa,sxb).getSchemaObject(sxb);
    Schema sxc=
      sxb.applyExtendTransformation(field,new Object[] {dept,"street"},
      null,null);
    Schema sxd=
      sxc.applyExtendTransformation(field,new Object[] {dept,"cname"},
      null,null);
    SchemaObject dept_cname=
      Transformation.getTransformation(sxc,sxd).getSchemaObject(sxd);
    Schema sxe=sxd.applyExtendTransformation(pk,
      new Object[] {"college_pk",college,college_cname},null,null);
    Schema sxf=sxe.applyExtendTransformation(fk,
      new Object[] {"dept_partof_college_fk",dept,dept_cname,college,college_cname},
      null,null);

    return sxf;
  }

  public static Schema extends1s2s3tosu(Schema sx) 
  throws TypeMismatchException, IntegrityException, NotFoundException {
    SchemaObject dept=sx.getSchemaObject("<<dept>>");
    SchemaObject dept_dname=sx.getSchemaObject("<<dept,dname>>");

    Schema sxa=extends1s2s3s4tosu(sx);
    Schema sxaa=
      sxa.applyExtendTransformation(table,new Object[] {"student"},null,null);
    SchemaObject student=
      Transformation.getTransformation(sxa,sxaa).getSchemaObject(sxaa);
    Schema sxb=
      sxaa.applyExtendTransformation(field,new Object[] {student,"id"},
					     null,null);
    SchemaObject student_id=
      Transformation.getTransformation(sxaa,sxb).getSchemaObject(sxb);
    Schema sxc=
      sxb.applyExtendTransformation(field,new Object[] {student,"sex"},
      null,null);
    Schema sxd=
      sxc.applyExtendTransformation(field,new Object[] {student,"dname"},
      null,null);
    SchemaObject student_dname=
      Transformation.getTransformation(sxc,sxd).getSchemaObject(sxd);
    Schema sxe=sxd.applyExtendTransformation(pk,
      new Object[] {"student_pk",student,student_id},null,null);
    Schema sxf=sxe.applyExtendTransformation(fk,
      new Object[] {"student_studiesin_dept_fk",student,student_id,dept,dept_dname},
      null,null);

    return sxf;
  }

  // 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 name) 
  throws Exception {
    Schema s=Schema.getSchema(name);

    if (s==null) {
      System.out.println("Schema "+name+" missing from repository");
      bombOut=true;
    }
    else {
      banner(s);
      s.write();    
    }
    
    return s;
  }

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

  public static void main(String[] args) {
    try {
      System.out.println("AutoMed Repository: "+
			 "Integration of University Databases");

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

      Schema s1=verboseGetSchema("uni_s1");
      Schema s2=verboseGetSchema("uni_s2");
      Schema s3=verboseGetSchema("uni_s3");
      Schema s4=verboseGetSchema("uni_s4");
      Schema s5=verboseGetSchema("uni_s5");

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

      sql92=Model.getModel("sql92_6");
      table=sql92.getConstruct("table");
      field=sql92.getConstruct("field");
      pk=sql92.getConstruct("primary_key");
      fk=sql92.getConstruct("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=transforms1Tosu(s1);
      banner("s1-->su");
      TransformationPathway.findShortestPath(s1,sga).write();
      listSchema(sga);

      Schema sgb=transforms2Tosu(s2); 
      banner("s2-->su");
      TransformationPathway.findShortestPath(s2,sgb).write();
      listSchema(sgb);

      Schema sgc=transforms3Tosu(s3);
      banner("s3-->su");
      TransformationPathway.findShortestPath(s3,sgc).write();
      listSchema(sgc);

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







