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

/**
 * An application to demonstrate how to create schemas in existing models
 */
public class AutoMedInANutshell {
  public static Schema createMDRERSchema(boolean force)
    throws NotFoundException,TypeMismatchException,IntegrityException {
    if (force)
      Schema.retract("mdr");
    Schema s=Schema.createSchema("mdr",true);
    Model er=Model.getModel("er");

    Construct ent=er.getConstruct("entity");
    Construct att=er.getConstruct("attribute");
    Construct rel=er.getConstruct("relationship");

    // Now build the schema
    SchemaObject model=s.createSchemaObject(ent,new Object[]{"model"});
    s.createSchemaObject(att,new Object[] {model,"mid","key"});
    s.createSchemaObject(att,new Object[] {model,"name","notnull"});

    SchemaObject construct=s.createSchemaObject(ent,new Object[]{"construct"});
    s.createSchemaObject(att,new Object[] {construct,"cid","key"});
    s.createSchemaObject(att,new Object[] {construct,"name","notnull"});

    SchemaObject scheme=s.createSchemaObject(ent,new Object[]{"scheme"});

    s.createSchemaObject(rel,new Object[]{"",construct,model,"1:1","0:N"});
    s.createSchemaObject(rel,new Object[]{"",scheme,construct,"1:1","1:N"});

    return s;
  }

  public static Schema wrapUniversity() 
    throws IntegrityException, DataSourceException {
    AutoMedWrapperFactory wf=new TransactSQLWrapperFactory();
    wf.setFeatures(SQLWrapperFactory.PRIMARY_KEY);
    wf.setFeatures(SQLWrapperFactory.FOREIGN_KEY);
    wf.setFeatures(SQLWrapperFactory.DATA_TYPE);
    wf.setFeatures(SQLWrapperFactory.DATA_SIZE);
    wf.setFeatures(SQLWrapperFactory.NULL_CONSTRAINT);

    Schema.retract("uni_s1_src");
    AutoMedWrapper sw=AutoMedWrapper.
      selectNewAutoMedWrapper("lab","lab",null,
	"com.microsoft.jdbc.sqlserver.SQLServerDriver",
	"jdbc:microsoft:sqlserver://db-ms.doc.ic.ac.uk\\;"+
			      "databaseName=pjm_university1","uni_s1_src",wf);

    return sw.getSchema("uni_s1_src");
  }

  public static Schema wrapBankBranch() 
    throws IntegrityException, DataSourceException, NotFoundException {
    TransactSQLWrapper sw=(TransactSQLWrapper)TransactSQLWrapper.
      newAutoMedWrapper("lab","lab",
	"com.microsoft.jdbc.sqlserver.SQLServerDriver",
	"jdbc:microsoft:sqlserver://db-ms.doc.ic.ac.uk\\;databaseName=lab_bank_branch",null,null);
    TransactSQLWrapperFactory wf=(TransactSQLWrapperFactory)
      sw.getAutoMedWrapperFactory();
    wf.setFeatures(SQLWrapperFactory.PRIMARY_KEY);
    wf.setFeatures(SQLWrapperFactory.FOREIGN_KEY);
    wf.setFeatures(SQLWrapperFactory.DATA_TYPE);
    wf.setFeatures(SQLWrapperFactory.DATA_SIZE);
    wf.setFeatures(SQLWrapperFactory.NULL_CONSTRAINT);

    Schema.retract("branch_src");
    sw.getSchema("branch_src");
    return wf.createAutoMedSchema(sw,"branch");
  }

  public static Schema wrapBankBranchPostgres() 
    throws IntegrityException, DataSourceException, NotFoundException {
    SQLWrapper sw=(SQLWrapper)AutoMedWrapper.
      selectNewAutoMedWrapper("lab","lab",null,null,
	"jdbc:postgresql://localhost/lab_bank_branch");
    SQLWrapperFactory wf=(SQLWrapperFactory)sw.getAutoMedWrapperFactory();
    wf.setFeatures(wf.PRIMARY_KEY | wf.FOREIGN_KEY | wf.DATA_TYPE |
		   wf.DATA_SIZE | wf.NULL_CONSTRAINT | wf.SCHEMA_AWARE |
		   wf.SCHEMA_MERGING);
    sw.setOption(sw.OPTION_SCHEMA_NAME,"university1");

    Schema.retract("branch_src");
    sw.getSchema("branch_src");

    return wf.createAutoMedSchema(sw,"branch");
  }

  public static Schema wrapBiomapPostgres() 
    throws IntegrityException, DataSourceException, NotFoundException {
    AutoMedWrapper sw=AutoMedWrapper.
      selectNewAutoMedWrapper("lab","secret",null,null,
	"jdbc:postgresql://localhost/biomap");
    SQLWrapperFactory wf=(SQLWrapperFactory)sw.getAutoMedWrapperFactory();
    wf.setFeatures(wf.PRIMARY_KEY | wf.FOREIGN_KEY | wf.DATA_TYPE |
		   wf.DATA_SIZE | wf.NULL_CONSTRAINT);

    Schema.retract("biomap_src");
    sw.getSchema("biomap_src");

    return wf.createAutoMedSchema(sw,"biomap");
  }

  public static void wrapHalevy() 
    throws IntegrityException, DataSourceException, NotFoundException {
    AutoMedWrapper sw=AutoMedWrapper.
      selectNewAutoMedWrapper("lab","lab",null,null,
	"jdbc:postgresql://localhost/automed");
    SQLWrapperFactory wf=(SQLWrapperFactory)sw.getAutoMedWrapperFactory();
    wf.setFeatures(SQLWrapperFactory.PRIMARY_KEY);
    wf.setFeatures(SQLWrapperFactory.FOREIGN_KEY);
    //wf.setFeatures(SQLWrapperFactory.DATA_TYPE);
    //wf.setFeatures(SQLWrapperFactory.DATA_SIZE);
    //wf.setFeatures(SQLWrapperFactory.NULL_CONSTRAINT);
    //wf.setFeatures(SQLWrapperFactory.SCHEMA_AWARE);
    //wf.setFeatures(SQLWrapperFactory.SCHEMA_MERGING);
    sw.setOption(SQLWrapper.OPTION_SCHEMA_NAME,"sc_s4");
    //System.out.println("Set "+SQLWrapper.OPTION_SCHEMA_NAME+"+staff");
    Schema.retract("sc_s4_src");
    sw.getSchema("sc_s4_src");
    wf.createAutoMedSchema(sw,"sc_s4");

    sw=AutoMedWrapper.
      selectNewAutoMedWrapper("lab","lab",null,null,
	"jdbc:postgresql://localhost/automed",null,wf); 

    sw.setOption(SQLWrapper.OPTION_SCHEMA_NAME,"sc_s3");
    Schema.retract("sc_s3_src");
    sw.getSchema("sc_s3_src");
    wf.createAutoMedSchema(sw,"sc_s3");

    sw=AutoMedWrapper.
      selectNewAutoMedWrapper("lab","lab",null,null,
	"jdbc:postgresql://localhost/automed",null,wf); 
    sw.setOption(SQLWrapper.OPTION_SCHEMA_NAME,"sc_s2");
    Schema.retract("sc_s2_src");
    sw.getSchema("sc_s2_src");
    wf.createAutoMedSchema(sw,"sc_s2");

    sw=AutoMedWrapper.
      selectNewAutoMedWrapper("lab","lab",null,null,
	"jdbc:postgresql://localhost/automed",null,wf); 
    sw.setOption(SQLWrapper.OPTION_SCHEMA_NAME,"sc_s1");
    Schema.retract("sc_s1_src");
    sw.getSchema("sc_s1_src");
    wf.createAutoMedSchema(sw,"sc_s1");
 }


  public static Schema wrapOracle() 
    throws IntegrityException, DataSourceException, NotFoundException {
    AutoMedWrapper sw=AutoMedWrapper.
      selectNewAutoMedWrapper("PETER","secret",null,null,
	"jdbc:oracle:thin:@hermes.dcs.bbk.ac.uk:1521:teach");
    SQLWrapperFactory wf=(SQLWrapperFactory)sw.getAutoMedWrapperFactory();
    wf.setFeatures(SQLWrapperFactory.PRIMARY_KEY);
    wf.setFeatures(SQLWrapperFactory.FOREIGN_KEY);
    wf.setFeatures(SQLWrapperFactory.DATA_TYPE);
    wf.setFeatures(SQLWrapperFactory.DATA_SIZE);
    wf.setFeatures(SQLWrapperFactory.NULL_CONSTRAINT);
    wf.setFeatures(SQLWrapperFactory.SCHEMA_AWARE);
    wf.setFeatures(SQLWrapperFactory.SCHEMA_MERGING);
    sw.setOption(SQLWrapper.OPTION_SCHEMA_NAME,"NMARTIN");

    Schema.retract("bbk_src");
    sw.getSchema("bbk_src");
    return wf.createAutoMedSchema(sw,"bbk");
  }

  public static Schema wrapAnsitest() 
    throws IntegrityException, DataSourceException, NotFoundException {
    AutoMedWrapper sw=AutoMedWrapper.
      selectNewAutoMedWrapper("lab","lab",null,null,
	"jdbc:postgresql://localhost/ansitest");
    SQLWrapperFactory wf=(SQLWrapperFactory)sw.getAutoMedWrapperFactory();
    //wf.setFeatures(SQLWrapperFactory.PRIMARY_KEY);
    //wf.setFeatures(SQLWrapperFactory.FOREIGN_KEY);
    wf.setFeatures(SQLWrapperFactory.DATA_TYPE);
    wf.setFeatures(SQLWrapperFactory.NULL_CONSTRAINT);

    Schema.retract("ansitest_src");
    sw.getSchema("ansitest_src");
    return wf.createAutoMedSchema(sw,"ansitest");
  }

  public static void transformUniversity() {
  }

  public static Schema extendIntoNewTable(Schema s,String newTableName,
					  String newTablePrimaryKeyName,
					  SchemaObject oldTable,
					  HashMap limitIQL,
					  boolean isExact) 
  throws NotFoundException, TypeMismatchException, IntegrityException {
    Construct table=oldTable.getConstruct();
    Construct column=
      table.getModel().getConstruct(SQLWrapperFactory.
				    AUTOMED_SQL_MODEL_COLUMN);
    Construct primarykey=
      table.getModel().getConstruct(SQLWrapperFactory.
				    AUTOMED_SQL_MODEL_PRIMARY_KEY);
    Construct foreignkey=
      table.getModel().getConstruct(SQLWrapperFactory.
				    AUTOMED_SQL_MODEL_FOREIGN_KEY);

      
    Object[] scheme=oldTable.getSchemeDefinition();
    scheme[0]=newTableName;
    Schema ns=s.applyExtendTransformation(table,scheme,
                oldTable.toString(),null);
    SchemaObject newTable=
      Transformation.getTransformation(s,ns).getSchemaObject(ns);

    SchemaObject[] oldColumns=s.getDependentSchemaObjects(oldTable,column);
    SchemaObject[] newColumns=new SchemaObject[oldColumns.length];
    HashMap newToOld=new HashMap();
    HashMap oldToNew=new HashMap();

    for (int i=0;i<oldColumns.length;i++) {
      Schema os=ns;
      scheme=oldColumns[i].getSchemeDefinition();
      scheme[0]=newTable;
      ns=ns.applyExtendTransformation(column,scheme,
				      oldColumns[i].toString(),null);
      newColumns[i]=
	Transformation.getTransformation(os,ns).getSchemaObject(ns);
      oldToNew.put(oldColumns[i],newColumns[i]);
      newToOld.put(newColumns[i],oldColumns[i]);
    }

    // Now build the restriction (if any) on newTable that gives us the
    // old table. As a side effect, some more columns may be added to newTable
    String restriction=newTable.toString();
    if (limitIQL!=null && limitIQL.size()>0) {
      restriction="[k | k <- "+restriction;
      Iterator i=limitIQL.keySet().iterator();
      int j=0;

      while (i.hasNext()) {
	Object key=i.next();
	String filter=(String)limitIQL.get(key);
	SchemaObject so=(SchemaObject)oldToNew.get(key);
	String attVar="a"+j++;

	// key might instead be a column name, so have to find that!
	if (so==null) {
	  scheme[1]=key;
	  Schema os=ns;
	  ns=ns.applyExtendTransformation(column,scheme,"Void",null);
	  so=Transformation.getTransformation(os,ns).getSchemaObject(ns);
	}
	restriction+="; {k,"+attVar+"} <- "+so+"; "+attVar+filter;
      }

      restriction+="]";
    }

    if (!isExact)
      restriction="Range(Void,"+restriction+")";

    // remove old PK if there
    SchemaObject[] oldPK=s.getDependentSchemaObjects(oldTable,primarykey);
    if (oldPK.length>0) {
      scheme=oldPK[0].getSchemeDefinition();

      if (newTablePrimaryKeyName!=null) {
	scheme[0]=newTablePrimaryKeyName;
	scheme[1]=newTable;
	for (int i=2;i<scheme.length;i++)
	  scheme[i]=(SchemaObject)oldToNew.get(scheme[i]);
	ns=ns.applyAddTransformation(primarykey,scheme,null,null);
      }

      ns=ns.applyDeleteTransformation(oldPK[0],null,null);
    }

    // New table complete, now delete old table

    SchemaObject[] oldFKs=s.getDependentSchemaObjects(oldTable,foreignkey);
    for (int i=0;i<oldFKs.length;i++) 
      ns=ns.applyContractTransformation(oldFKs[i],null,null);

    for (int i=0;i<newColumns.length;i++) {
      String query="[{k,a} | k <- "+oldTable+"; {k,a} <- "+newColumns[i]+"]";
      ns=ns.applyContractTransformation(oldColumns[i],query,null);
    }

    ns=ns.applyDeleteTransformation(oldTable,restriction,null);

    return ns;
  }

  /**
   * Change which columns are used to identify a table.
   */
  public static Schema changePrimaryKey(Schema s,
					SchemaObject table,
					SchemaObject[] newKeyColumns) 
  throws NotFoundException, TypeMismatchException, IntegrityException {
    Construct primarykeytype=
      table.getConstruct().getModel().getConstruct(SQLWrapperFactory.
				    AUTOMED_SQL_MODEL_PRIMARY_KEY);
    SchemaObject[] primarykey=
      s.getDependentSchemaObjects(table,primarykeytype);

    return s;
  }


  /**
   * Split a table into two tables.
   */
  public static Schema splitTable(Schema s,
				  SchemaObject oldTable,
				  String newTableName1,
				  SchemaObject[] newKeyColumns1,
				  SchemaObject[] newFDColumns1,
				  String newTableName2,
				  SchemaObject[] newKeyColumns2,
				  SchemaObject[] newFDColumns2) 
  throws NotFoundException, TypeMismatchException, IntegrityException {
    Construct table=oldTable.getConstruct();
    Construct primarykey=
      table.getModel().getConstruct(SQLWrapperFactory.
				    AUTOMED_SQL_MODEL_PRIMARY_KEY);
    Construct column=
      table.getModel().getConstruct(SQLWrapperFactory.
				    AUTOMED_SQL_MODEL_COLUMN);
    SchemaObject[] pk=s.getDependentSchemaObjects(oldTable,primarykey);

    if (pk.length==0)
      throw new NotFoundException("Table "+oldTable+
				  " must have a primary key");

    s=s.applyDeleteTransformation(pk[0],null,null);
    
    DeriveTable dt1=new DeriveTable(oldTable,newKeyColumns1,newFDColumns1);
    s=dt1.generateNewTable(s,newTableName1,newTableName1+"_pk");
    DeriveTable dt2=new DeriveTable(oldTable,newKeyColumns2,newFDColumns2);
    s=dt2.generateNewTable(s,newTableName2,newTableName2+"_pk");

    HashSet commonKeys=new HashSet(dt1.pkColSet);
    commonKeys.retainAll(dt2.pkColSet);

    String pkVars=null;
    String pkQuery="";
    Object[] scheme=pk[0].getSchemeDefinition();

    HashSet oldPKSet=new HashSet();
    for (int i=2;i<scheme.length;i++) {
      oldPKSet.add(scheme[i]);
      String kv="k"+(i-2);

      pkVars=(pkVars==null ? kv : pkVars+","+kv);

      SchemaObject so=(SchemaObject)dt1.newCols.get(scheme[i]);
      if (so!=null)
	pkQuery+=(pkQuery==null ? "" : "; ")+"{kt1,"+kv+"} <- "+so;

      so=(SchemaObject)dt2.newCols.get(scheme[i]);
      if (so!=null)
	pkQuery+=(pkQuery==null ? "" : "; ")+"{kt2,"+kv+"} <- "+so;
    }

    String tableVars="{"+pkVars+"}";
    if (scheme.length>3)
      pkVars="{"+pkVars+"}";

    SchemaObject[] cols=s.getDependentSchemaObjects(oldTable,column);

    for (int i=0;i<cols.length;i++) {
      if (!oldPKSet.contains(cols[i])) {
	String query="[{"+pkVars+",a}] | "+pkQuery;

	SchemaObject so=(SchemaObject)dt1.newCols.get(cols[i]);
	if (so!=null)
	  query+="; {kt1,a} <- "+so;
      
	so=(SchemaObject)dt2.newCols.get(cols[i]);
	if (so!=null)
	  query+="; {kt2,a} <- "+so;

	s=s.applyDeleteTransformation(cols[i],query+"]",null,null);
      }
    }
			  
    

    for (int i=2;i<scheme.length;i++) 
      s=s.applyDeleteTransformation((SchemaObject)scheme[i],
				    "[{"+pkVars+",k"+(i-2)+"} | "+
				    tableVars+"<-"+oldTable+"]",null,null);

    s=s.applyDeleteTransformation(oldTable,"["+tableVars+" | "+pkQuery+"]",
				  null,null);

    return s;
  }



  /**
   * Convert a table into Third Normal Form (3NF). If a table breaks
   * 3NF by having some `fdcols' columns fuctionally depend on non-key
   * 'pkcols' columns, we can normalise the schema by placing those
   * functionally dependent columns in a new table, which shares with
   * the old table the 'pkcols'
   * 
   * 
   */
  public static Schema normaliseTable(Schema s,String newTableName,
				      String newTablePrimaryKeyName,
				      String oldTableForeignKeyName,
				      SchemaObject[] pkcols,
				      SchemaObject[] fdcols) 
  throws NotFoundException, TypeMismatchException, IntegrityException {
    return deriveTable(s,newTableName,newTablePrimaryKeyName,
		       oldTableForeignKeyName,pkcols,fdcols,fdcols);
  }

  /**
   * Derive a new table from the contents of an old table. The
   * new table will have some `fdcols' columns fuctionally depend 
   * 'pkcols' columns.
   * 
   * 
   */
  public static Schema deriveTable(Schema s,String newTableName,
				   String newTablePrimaryKeyName,
				   String oldTableForeignKeyName,
				   SchemaObject[] pkcols,
				   SchemaObject[] fdcols,
				   SchemaObject[] delcols) 
  throws NotFoundException, TypeMismatchException, IntegrityException {

    Object[] scheme=pkcols[0].getSchemeDefinition();
    SchemaObject oldTable=(SchemaObject)scheme[0];
    Construct table=oldTable.getConstruct();
    Construct column=((SchemaObject)pkcols[0]).getConstruct();
    Construct primarykey=
      table.getModel().getConstruct(SQLWrapperFactory.
				    AUTOMED_SQL_MODEL_PRIMARY_KEY);
    Construct foreignkey=
      table.getModel().getConstruct(SQLWrapperFactory.
				    AUTOMED_SQL_MODEL_FOREIGN_KEY);

    //SchemaObject[] newpkcols=new SchemaObject[pkcols.length];
    //SchemaObject[] newfdcols=new SchemaObject[fdcols.length];
    HashMap newCols=new HashMap();

    String keyVars="k0";
    String keyQuery="{k,k0} <- "+pkcols[0];
    HashSet pkcolset=new HashSet();

    for (int i=1;i<pkcols.length;i++) {
      keyVars+=",k"+i;
      keyQuery+="; {k,k"+i+"} <- "+pkcols[i];
      pkcolset.add(pkcols[i]);
    }

    Schema ns=s.applyAddTransformation(table,new Object[]{newTableName},
                "distinct [{"+keyVars+"} | "+keyQuery+"]",null);
    SchemaObject newTable=
      Transformation.getTransformation(s,ns).getSchemaObject(ns);
    HashSet joinCols=new HashSet();

    // Something to hold the scheme of the new table's primary key
    Object[] newpk=new Object[2+pkcols.length];
    newpk[0]=newTablePrimaryKeyName;
    newpk[1]=newTable;
    Object[] newfk=new Object[3+(pkcols.length)*2];
    newfk[0]=oldTableForeignKeyName;
    newfk[1]=oldTable;
    newfk[2+pkcols.length]=newTable;
    
    if (pkcols.length>1)
      keyVars="{"+keyVars+"}";
	       
    for (int i=0;i<pkcols.length;i++) {
      Schema os=ns;
      scheme=pkcols[i].getSchemeDefinition();
      scheme[0]=newTable;
      ns=ns.applyAddTransformation(column,scheme,
	"distinct [{"+keyVars+",k"+i+"} | "+keyQuery+"]",null);
      SchemaObject nc=
	Transformation.getTransformation(os,ns).getSchemaObject(ns);
      newCols.put(pkcols[i],nc);
      newpk[i+2]=nc;
      newfk[i+2]=pkcols[i];
      newfk[newpk.length+1+i]=nc;
      joinCols.add(pkcols[i]);
    }

    if (newTablePrimaryKeyName!=null)
      ns=ns.applyAddTransformation(primarykey,newpk,null,null);

    System.out.println("Construct "+foreignkey);
    for (int i=0;i<newfk.length;i++) 
      System.out.println("newfk["+i+"]="+newfk[i]);

    if (oldTableForeignKeyName!=null)
      ns=ns.applyAddTransformation(foreignkey,newfk,null,null);
							   
    for (int i=0;i<fdcols.length;i++) {
      Schema os=ns;
      scheme=fdcols[i].getSchemeDefinition();
      scheme[0]=newTable;
      ns=ns.applyAddTransformation(column,scheme,
	"distinct [{"+keyVars+",a} | "+keyQuery+"; {k,a} <- "+
			      fdcols[i]+"]",null);
      newCols.put(fdcols[i],
		  Transformation.getTransformation(os,ns).getSchemaObject(ns));
      joinCols.add(fdcols[i]);
    }

    HashSet pkdelcols=new HashSet();
    HashSet fddelcols=new HashSet();
    HashSet condelcols=new HashSet();

    for (int i=0;i<delcols.length;i++) {
      boolean done=false;

      for (int j=0;j<pkcols.length;j++)
	if (delcols[i]==pkcols[j]) {
	  pkdelcols.add(delcols[i]);
	  pkcolset.remove(pkcols[j]);
	  done=true;
	}

      for (int j=0;j<fdcols.length;j++)
	if (delcols[i]==fdcols[j]) {
	  fddelcols.add(delcols[i]);
	  done=true;
	}

      if (!done)
	condelcols.add(delcols[i]);
    }

    // New table now complete, remove columns from old table

    // First just contract those columns not appearing in the new table
    // but which we have been told to delete.
    Iterator i=condelcols.iterator();

    while (i.hasNext())
      ns=ns.applyContractTransformation((SchemaObject)i.next(),null,null);
     
    // Now build a new query that joins the two tables on those columns
    // they still have in common 
    String join="";
    i=joinCols.iterator();
    int j=0;

    while (i.hasNext()) {
      SchemaObject oldpk=(SchemaObject)i.next();
      if (!fddelcols.contains(oldpk)) {
	join+="{ko,k"+j+"} <- "+oldpk+"; {kn,k"+j+"} <- "+
	  newCols.get(oldpk)+";";
	j++;
      }
    }

    // !!! If deleting attributes from old table PK, need to "downsize"
    // the PK now!

    // Now step through the fd cols, and delete them
    i=fddelcols.iterator();
    while (i.hasNext()) {
      SchemaObject delc=(SchemaObject)i.next();

      ns=ns.applyDeleteTransformation(delc,
       "[{ko,a} | "+join+" {kn,a} <- "+newCols.get(delc)+"]",null);
    }

    i=pkdelcols.iterator();
    while (i.hasNext()) {
      SchemaObject delc=(SchemaObject)i.next();

      ns=ns.applyDeleteTransformation(delc,
       "[{ko,a} | "+join+" {kn,a} <- "+newCols.get(delc)+"]",null);
    }

    return ns;
  }

  public static void main(String[] args) 
  throws Exception {
    try {
      System.out.println("AutoMed In A Nutshell: Examples");

      //wrapAnsitest();
    
      wrapHalevy();

      //OutputStream os=
      //	new FileOutputStream(new File("data/sc_global_test.atm"));
      //Schema s1=Schema.getSchema("uni_s1");
      //s1.write(os);

      Schema.retract("sc_sg");
      InputStream is=new FileInputStream(new File("data/sc_global_sql_6.atm"));
      Schema sc_sg=Schema.read(is,"sc_sg");

      Schema sc_s1=Schema.getSchema("sc_s1");
      SchemaObject coursecnumber=sc_s1.getSchemaObject("<<studies,cnumber>>");
      SchemaObject coursetitle=sc_s1.getSchemaObject("<<studies,title>>");
      sc_s1=normaliseTable(sc_s1,"somecourse","course_pk","course_fk",
		     new SchemaObject[]{coursecnumber},
		     new SchemaObject[]{coursetitle});
      SchemaObject somecourse=sc_s1.getSchemaObject("<<somecourse>>");
      SchemaObject somecoursenumber=
	sc_s1.getSchemaObject("<<somecourse,cnumber>>");
      HashMap filter=new HashMap();
      filter.put(somecoursenumber,"<=500");
      sc_s1=extendIntoNewTable(sc_s1,"course","course_pk",somecourse,
			       filter,false);
      SchemaObject studsname=sc_s1.getSchemaObject("<<studies,sname>>");
      sc_s1=normaliseTable(sc_s1,"somestudent","student_pk","student_fk",
		     new SchemaObject[]{studsname},
		     new SchemaObject[]{});
      SchemaObject somestudent=sc_s1.getSchemaObject("<<somestudent>>");
      sc_s1=extendIntoNewTable(sc_s1,"student","student_pk",somestudent,
			       null,false);
      SchemaObject studies=sc_s1.getSchemaObject("<<studies>>");
      SchemaObject studiesquarter=sc_s1.getSchemaObject("<<studies,quarter>>");
      filter=new HashMap();
      filter.put(coursecnumber,">=500");
      filter.put(studiesquarter,">='1998-2'");
      sc_s1=extendIntoNewTable(sc_s1,"registered","registered_pk",studies,
			       filter,false);
      sc_s1=sc_s1.extendToMatch(sc_sg);
      Transformation.createIdentTransformation(sc_s1,sc_sg,null,null);


      Schema sc_s2=Schema.getSchema("sc_s2");
      SchemaObject teaching=sc_s2.getSchemaObject("<<teaching>>");
      SchemaObject teachingsname=sc_s2.getSchemaObject("<<teaching,sname>>");
      SchemaObject teachingcnumber=
	sc_s2.getSchemaObject("<<teaching,cnumber>>");
      SchemaObject teachingquarter=
	sc_s2.getSchemaObject("<<teaching,quarter>>");
      SchemaObject teachingpname=
	sc_s2.getSchemaObject("<<teaching,pname>>");
      sc_s2=splitTable(sc_s2,teaching,"someteaches",
	 new SchemaObject[]{teachingpname,teachingcnumber,teachingquarter},
         new SchemaObject[]{},
	 "someregistered",
	 new SchemaObject[]{teachingsname,teachingcnumber,teachingquarter},
         new SchemaObject[]{});
      SchemaObject someteaches=sc_s2.getSchemaObject("<<someteaches>>");
      SchemaObject someregistered=sc_s2.getSchemaObject("<<someregistered>>");
      sc_s2=extendIntoNewTable(sc_s2,"teaches","teaches_pk",someteaches,
			       null,false);
      sc_s2=extendIntoNewTable(sc_s2,"registered","registered_pk",
			       someregistered,null,false);

      sc_s2=sc_s2.extendToMatch(sc_sg);
      Transformation.createIdentTransformation(sc_s2,sc_sg,null,null);

      Schema sc_s4=Schema.getSchema("sc_s4");
      SchemaObject profpname=sc_s4.getSchemaObject("<<staff,pname>>");
      sc_s4=normaliseTable(sc_s4,"someprof","prof_pk","studies_fk",
		     new SchemaObject[]{profpname},
		     new SchemaObject[]{});
      SchemaObject someprof=sc_s4.getSchemaObject("<<someprof>>");
      sc_s4=extendIntoNewTable(sc_s4,"prof","prof_pk",someprof,null,false);

      SchemaObject staffcnumber=sc_s4.getSchemaObject("<<staff,cnumber>>");
      SchemaObject stafftitle=sc_s4.getSchemaObject("<<staff,title>>");
      sc_s4=normaliseTable(sc_s4,"somecourse","course_pk",null,
		     new SchemaObject[]{staffcnumber},
		     new SchemaObject[]{stafftitle});
      somecourse=sc_s4.getSchemaObject("<<somecourse>>");
      sc_s4=extendIntoNewTable(sc_s4,"course","course_pk",somecourse,
			       null,false);

      SchemaObject staff=sc_s4.getSchemaObject("<<staff>>");
      SchemaObject staffquarter=sc_s4.getSchemaObject("<<staff,quarter>>");
      filter=new HashMap();
      filter.put(staffquarter,"<='1997-2'");
      sc_s4=extendIntoNewTable(sc_s4,"teaches","teaches_pk",staff,
			       filter,false);
      sc_s4=sc_s4.extendToMatch(sc_sg);
      Transformation.createIdentTransformation(sc_s4,sc_sg,null,null);


      Schema sc_s3=Schema.getSchema("sc_s3");
      studsname=sc_s3.getSchemaObject("<<registration,sname>>");
      sc_s3=normaliseTable(sc_s3,"somestudent","student_pk","student_fk",
		     new SchemaObject[]{studsname},
		     new SchemaObject[]{});
      somestudent=sc_s3.getSchemaObject("<<somestudent>>");
      sc_s3=extendIntoNewTable(sc_s3,"student","student_pk",somestudent,
			       null,false);
      coursecnumber=
	sc_s3.getSchemaObject("<<registration,cnumber>>");
      sc_s3=normaliseTable(sc_s3,"somecourse","course_pk","course_fk",
		     new SchemaObject[]{coursecnumber},
		     new SchemaObject[]{});
      somecourse=sc_s3.getSchemaObject("<<somecourse>>");
      sc_s3=extendIntoNewTable(sc_s3,"course","course_pk",somecourse,
			       null,false);
      SchemaObject registration=sc_s3.getSchemaObject("<<registration>>");
      filter=new HashMap();
      filter.put("quarter","<='1994-2'");
      sc_s3=extendIntoNewTable(sc_s3,"registered",null,registration,
			       filter,false);
      sc_s3=sc_s3.extendToMatch(sc_sg);
      Transformation.createIdentTransformation(sc_s3,sc_sg,null,null);
      
      //Schema u1=Schema.read(is,"sc_sg");
      //u1.listSchema();

      //Schema uni=wrapUniversity();
      //uni.listSchema();

      //Schema bb=wrapBankBranch();
      //bb.listSchema();

      //Schema bbk=wrapOracle();
      //bbk.listSchema();

      //Schema mdr=createMDRERSchema(true);
      //mdr.listSchema();
    }
    catch (AutoMedException ame) {
      ame.printStackTrace();
    }
  }
}

class DeriveTable {
  public SchemaObject oldTable;
  SchemaObject[] pkCols;
  HashSet pkColSet=new HashSet();
  SchemaObject[] fdCols;
  public SchemaObject newTable;
  public HashMap newCols=new HashMap(); // map old columns to new columns
  public HashMap oldCols=new HashMap(); // map new columns to old columns
  Construct table;
  Construct column;
  Construct primarykey;
  Construct foreignkey;

  public DeriveTable(SchemaObject oldTable,
		     SchemaObject[] pkCols,SchemaObject[] fdCols) 
    throws NotFoundException {
    this.oldTable=oldTable;
    this.pkCols=pkCols;
    this.fdCols=fdCols;
    table=oldTable.getConstruct();
    column=((SchemaObject)pkCols[0]).getConstruct();
    primarykey=
      table.getModel().getConstruct(SQLWrapperFactory.
				    AUTOMED_SQL_MODEL_PRIMARY_KEY);
    foreignkey=
      table.getModel().getConstruct(SQLWrapperFactory.
				    AUTOMED_SQL_MODEL_FOREIGN_KEY);
  }

  public Schema generateNewTable(Schema baseSchema,
				 String newTableName, String newPKName) 
    throws NotFoundException, TypeMismatchException, IntegrityException {
    String keyVars="k0";
    String keyQuery="{k,k0} <- "+pkCols[0];

    for (int i=1;i<pkCols.length;i++) {
      keyVars+=",k"+i;
      keyQuery+="; {k,k"+i+"} <- "+pkCols[i];
      pkColSet.add(pkCols[i]);
    }
      
    Schema ns=baseSchema.applyAddTransformation(table,
      new Object[]{newTableName},
      "distinct [{"+keyVars+"} | "+keyQuery+"]",null);
    SchemaObject newTable=
      Transformation.getTransformation(baseSchema,ns).getSchemaObject(ns);

    Object[] newpk=new Object[2+pkCols.length];
    newpk[0]=newPKName;
    newpk[1]=newTable;
      
    if (pkCols.length>1)
      keyVars="{"+keyVars+"}";
      
    for (int i=0;i<pkCols.length;i++) {
      Schema os=ns;
      Object[] scheme=pkCols[i].getSchemeDefinition();
      scheme[0]=newTable;
      ns=ns.applyAddTransformation(column,scheme,
				   "distinct [{"+keyVars+",k"+i+"} | "+keyQuery+"]",null);
      SchemaObject nc=
	Transformation.getTransformation(os,ns).getSchemaObject(ns);
      newCols.put(pkCols[i],nc);
      oldCols.put(nc,pkCols[i]);
      newpk[i+2]=nc;
    }
   
    // Might not have an explicit primary key construct ...
    if (newPKName!=null && primarykey!=null) 
      ns=ns.applyAddTransformation(primarykey,newpk,null,null);

    for (int i=0;i<fdCols.length;i++) {
      Schema os=ns;
      Object[] scheme=fdCols[i].getSchemeDefinition();
      scheme[0]=newTable;
      ns=ns.applyAddTransformation(column,scheme,
				   "distinct [{"+keyVars+",a} | "+keyQuery+"; {k,a} <- "+
				   fdCols[i]+"]",null);

      SchemaObject nc=
	Transformation.getTransformation(os,ns).getSchemaObject(ns);

      newCols.put(fdCols[i],nc);
      oldCols.put(nc,fdCols[i]);		
    }

    return ns;
  }
}
