package basic;


import uk.ac.ic.doc.automed.*;
import uk.ac.ic.doc.automed.modelmanagement.modeldef.SQLModelDef;
import uk.ac.ic.doc.automed.reps.*;

/*
 * An application to integrate the two versions of the Bank
 * database held in the sql92 datamodel.
 */
public class BankDatabaseIntegration {
  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 = System.getProperty("user.home") +
  System.getProperty("file.separator") +
  ".automed"+System.getProperty("file.separator") +"data_source_repository.cfg";


  // Generate a subclass from a superclass property
  public static Schema 
  generateSubclass(Schema cs,
      SchemaObject key,
      SchemaObject property,String subclassname,String filter) 
  throws TypeMismatchException, NotFoundException, IntegrityException {
    Schema ns;
    Object[] keyscheme=key.getSchemeDefinition();
    SchemaObject superclass=(SchemaObject)keyscheme[0];
    String superclassname=(String)superclass.getSchemeDefinition()[0];

    ns=cs.applyAddTransformation(table,new Object[] {subclassname},
        "[{x} | {x,"+filter+"} <- "+property+"]",null);
    SchemaObject subclass=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    // Now add an column to that table we just created
    keyscheme=key.getSchemeDefinition();
    keyscheme[0]=subclass;
    ns=cs.applyAddTransformation(column,keyscheme,
        "[{x,x} | {x} <- "+subclass+"]",null);
    SchemaObject subclasskey=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    // Test the wrapper to see if we are modelling primary keys before
    // adding one for subclass
    if (wf.isFeatureInUse(SQLModelDef.PRIMARY_KEY)) {
      cs=cs.applyAddTransformation(pk,
          new Object[]{subclassname+"_pk",subclass,subclasskey},null,null);
    }

    // and test to see if we are modelling foreign keys ...
    if (wf.isFeatureInUse(SQLModelDef.FOREIGN_KEY)) {
      cs=cs.applyAddTransformation
      (fk,new Object[]{subclassname+"_isa_"+superclassname+"_fk",
          subclass,subclasskey,superclass,key},
          null,null);
    }

    return cs;
  }


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

    // ATTRIBUTE TO GENERALISATION
    // Divide account into its subclasses, current_account, depositor_account
    // and business
    // ***********************************************************************
    SchemaObject accounttype=cs.getSchemaObject("<<account,type>>");
    SchemaObject accountid=cs.getSchemaObject("<<account,no>>");
    Object accountidscheme[]=accountid.getSchemeDefinition();
    SchemaObject account=(SchemaObject)accountidscheme[0];

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

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

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

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

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

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

    // and test to see if we are modelling foreign keys ...
    if (wf.isFeatureInUse(SQLModelDef.FOREIGN_KEY)) {
      cs=cs.applyAddTransformation
      (fk,new Object[]{"current_account_isa_account_fk",current_account,current_accountid,account,accountid},
          null,null);
    }

    // Similar to PK and FK on deposit_account table
    if (wf.isFeatureInUse(SQLModelDef.PRIMARY_KEY)) {
      cs=cs.applyAddTransformation
      (pk,new Object[]{"deposit_account_pk",deposit_account,deposit_accountid},null,null);
    }

    if (wf.isFeatureInUse(SQLModelDef.FOREIGN_KEY)) {
      cs=cs.applyAddTransformation
      (fk,new Object[]{"deposit_account_isa_account_fk",
          deposit_account,deposit_accountid,account,accountid},null,null);
    }

    cs=generateSubclass(cs,accountid,accounttype,"premium_account","'premium'");

    // Finally delete the unwanted accounttype attribute
    cs=cs.
    applyDeleteTransformation(accounttype,
        "[{x,'current'} | {x} <- <<current_account>>] ++ "+
        "[{x,'deposit'} | {x} <- <<deposit_account>>] ++ "+
        "[{x,'premium'} | {x} <- <<premium_account>>]",
        null);

    // SYNONYM REMOVAL
    // Rename the account table to be "current_account"
    // ***********************************************************************
    SchemaObject branchbname=cs.getSchemaObject("<<branch,bname>>");
    Object branchbnamescheme[]=branchbname.getSchemeDefinition();
    SchemaObject branch=(SchemaObject)branchbnamescheme[0];
    branchbnamescheme[1]="bname";

    cs=cs.applyRenameTransformation(branchbname,branchbnamescheme);

    // ATTRIBUTE TO ENTITY
    // Convert cname attribute of account into a separate table called 
    // customer
    // *********************************************************************

    // Check that we have the objects that we want to transform
    SchemaObject accountcname=s1.getSchemaObject("<<account,cname>>");
    Object accountcnamescheme[]=accountcname.getSchemeDefinition();
    ns=cs.applyAddTransformation(table,
        new Object[] {"customer"},"distinct [{x} | {y,x} <- <<account,cname>>]",null);
    SchemaObject customer=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

    accountcnamescheme[0]=customer;
    accountcnamescheme[1]="cname";
    ns=cs.applyAddTransformation(column,accountcnamescheme,
        "[{x,x} | {x} <- <<customer>>]",null);
    SchemaObject customercname=
      Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    cs=ns;

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

      cs=cs.applyAddTransformation(pk,
          new Object[] {"customer_pk",customer,customercname},null,null);
    }

    if (wf.isFeatureInUse(SQLModelDef.FOREIGN_KEY)) {
      cs=cs.applyAddTransformation(fk,
          new Object[] {"account_for_customer_fk",account,
          accountcname,customer,customercname},null,null);
    }

    // ATTRIBUTE PROCESSING
    // Prefix all sortcode values with 55-66-
    // *********************************************************************
    // SchemaObject branchsortcode=cs.getSchemaObject("<<branch,sortcode>>");
    // Object branchscscheme[]=branchsortcode.getSchemeDefinition();
    // branchscscheme[1]="sortcode_ext";

    // ns=cs.applyAddTransformation(column,branchscscheme,
    //       "[{x,mergeString '55-66-' y} | {x,y} <- <<branch,sortcode>>]",null);
    // SchemaObject temp_sc=
    //   Transformation.getTransformation(cs,ns).getSchemaObject(ns);
    // cs=ns;
    // cs=cs.applyDeleteTransformation(branchsortcode,
    //       "[{x,rightString y 2} | {x,y} <- <<branch,sortcode_ext>>]",null);
    // branchscscheme[1]="sortcode";
    // cs=cs.applyRenameTransformation(temp_sc,branchscscheme);

    return cs;
  }

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

    // HOMONYM REMOVAL
    // Rename the account table to be "current_account"
    // ***********************************************************************
    SchemaObject account=cs.getSchemaObject("<<account>>");

    if (wf.isFeatureInUse(SQLModelDef.PRIMARY_KEY)) {
      SchemaObject pk=cs.getSchemaObject("<<account_pk,account,<<account,no>>>>");
      Object scheme[]=pk.getSchemeDefinition();
      scheme[0]="current_account_pk";

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

    if (wf.isFeatureInUse(SQLModelDef.FOREIGN_KEY)) {
      SchemaObject fk=
        cs.getSchemaObject("<<account_customer_fk,account,"+
        "<<account,cname>>,customer,<<customer,cname>>>>");
      Object scheme[]=fk.getSchemeDefinition();
      scheme[0]="current_account_for_customer_fk";

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

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

    return cs;
  }


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

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

  public static Schema deleteSchemaObjectWithConstraints(Schema cs,
      SchemaObject so,
      String extQuery,
      String condQuery)
  throws IntegrityException, TypeMismatchException {
    SchemaObject deps[]=so.getDependentSchemaObjects();

    for (int i=0;i<deps.length;i++) {
      if (deps[i].getConstruct().getConstructClass()!=Construct.CLASS_CONSTRAINT)
        throw new IntegrityException("Cannot delete SchemaObject "+so+
            " since it has non-constraint dependent SchemaObject "+
            deps[i],"BankDatabaseIntegration",
            IntegrityException.DELETE,so.getOBID(),deps[i].getOBID());
    }

    for (int i=0;i<deps.length;i++) 
      cs=cs.applyDeleteTransformation(deps[i],null,null);


    return cs.applyDeleteTransformation(so,extQuery,condQuery);
  }

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

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

      Schema sga=null;
      Schema sgb=null;

      if (schemaChoice==null || "branch".equals(schemaChoice)) {
        setConstructs("branch_src");
        Schema s1=Schema.getSchema("branch");
        sga=transforms1Tosg(s1);
        AutoMedInANutshell.banner(sga);
        sga.listSchema();
      }

      if (schemaChoice==null || "customer".equals(schemaChoice)) {
        setConstructs("customer_src");        
        Schema s2=Schema.getSchema("customer");
        sgb=transforms2Tosg(s2);
        AutoMedInANutshell.banner(sgb);
        sgb.listSchema();
      }


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

      if (sga!=null && sgb!=null) {
        Schema su=sga.union(sgb,"bank");

        su=deleteSchemaObjectWithConstraints(su,
            su.getSchemaObject("<<current_account,cname>>"),
            "[{a,n} | {a} <- <<current_account>>; "+
            "{a,n} <- <<account,cname>>]",null);

        su=deleteSchemaObjectWithConstraints(su,
            su.getSchemaObject("<<current_account,no>>"),
            "[{a,n} | {a} <- <<current_account>>; "+
            "{a,n} <- <<account,no>>]",null);
      }
    } catch (Throwable t) {
      System.err.println(t.toString());
      t.printStackTrace();
    } finally {
      close();
      System.exit(0);
    }
  }
}
