package basic;

import java.sql.*;
import java.io.*;
import uk.ac.ic.doc.automed.*;
import uk.ac.ic.doc.automed.modelmanagement.modeldef.SQLModelDef;
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.
 * 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="automed";
  static String password="automed";
  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 SQLModelDef wf;
  static boolean extra=false;
  static String schemaChoice=null;
  static String cfgFile=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(SQLModelDef.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(SQLModelDef.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(SQLModelDef.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(SQLModelDef.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(SQLModelDef.PRIMARY_KEY)) {
      cs=cs.applyAddTransformation
        (pk,new Object[]{"female_pk",female,femaleid},null,null);
    }
    
    
    if (wf.isFeatureInUse(SQLModelDef.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(SQLModelDef.PRIMARY_KEY)) {
      
      cs=cs.applyAddTransformation(pk,new Object[] {"dept_pk",dept,deptdname},
        null,null);
    }
    
    if (wf.isFeatureInUse(SQLModelDef.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(SQLModelDef.PRIMARY_KEY))
      cs=cs.applyDeleteTransformation(
        s3.getSchemaObject("<<degree_pk,degree,<<degree,dcode>>>>"),null,null);
    
    
    if (wf.isFeatureInUse(SQLModelDef.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"+
      "  -cfg <path to reps cfg file>\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(0);
        } 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();
          System.exit(0);
        } else if (args[i].startsWith("-cfg"))
          cfgFile=args[++i];
        
        ++i;
      }
      
      if (cfgFile!=null)
	DSRConfiguration.setDefaultConfigFileName(cfgFile);
      
      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 SQLModelDef();
    wf.setFeatures(Schema.getSchema(source).getAccessMethods()[0].
      getAutoMedWrapperFactoryFeatures());
    String autoMedModelName = wf.getAutoMedModelName();
    sql92=Model.getModel(autoMedModelName);
    table=sql92.getConstruct(SQLModelDef.AUTOMED_SQL_MODEL_TABLE);
    column=sql92.getConstruct(SQLModelDef.AUTOMED_SQL_MODEL_COLUMN);
    if (wf.isFeatureInUse(SQLModelDef.PRIMARY_KEY))
      pk=sql92.getConstruct(SQLModelDef.AUTOMED_SQL_MODEL_PRIMARY_KEY);
    if (wf.isFeatureInUse(SQLModelDef.FOREIGN_KEY))
      fk=sql92.getConstruct(SQLModelDef.AUTOMED_SQL_MODEL_FOREIGN_KEY);
  }
  
  private static void close() {
    try {
      DSR.close();
    } catch (DSRException ex) {
      ex.printStackTrace();
    }
  }

  private static Schema gs=null;

  private static void mergeToGlobal(Schema s) throws AutoMedException {
    if (s==null)
      return;

    if (gs==null) {
      gs=s.copySchema(UniversityDatabaseWrapping.GLOBAL_SCHEMA_NAME);
      Transformation.createIdentTransformation(s,gs,"choose",null);
      gs.setXPos(s.getXPos());
    } else 
      AutoMedInANutshell.integrateSchemas(s,gs);

    gs.setYPos(Math.max(gs.getYPos(),s.getYPos()+Schema.NEW_SCHEMA_Y_OFFSET));
  }
  
  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);
        AutoMedInANutshell.banner(sga);
        sga.listSchema();
      }
      
      if (schemaChoice==null || "uni_s2".equals(schemaChoice)) {
        setConstructs("uni_s2_src");        
        Schema s2=Schema.getSchema("uni_s2");
        sgb=transforms2Tosg(s2);
        AutoMedInANutshell.banner(sgb);
        sgb.listSchema();
      }
      
      if (schemaChoice==null || "uni_s3".equals(schemaChoice)) {
	setConstructs("uni_s3_src");
	Schema s3=Schema.getSchema("uni_s3");
	sgc=transforms3Tosg(s3);
	AutoMedInANutshell.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");
      
      mergeToGlobal(sga);
      mergeToGlobal(sgb);
      mergeToGlobal(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();
    } finally {
      close();
      System.exit(0);
    }
  }
}
