  /*
   * This program is free software; you can redistribute it and/or modify it under the
   * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
   * Foundation.
   *
   * You should have received a copy of the GNU Lesser General Public License along with this
   * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
   * or from the Free Software Foundation, Inc.,
   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
   * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   * See the GNU Lesser General Public License for more details.
   *
   * Copyright (c) 2011 Pentaho Corporation..  All rights reserved.
   *
   * @author Ezequiel Cuellar
   */

  package org.pentaho.agilebi.modeler.util;

  import org.apache.commons.lang.StringUtils;
  import org.pentaho.agilebi.modeler.*;
  import org.pentaho.agilebi.modeler.geo.GeoContext;
  import org.pentaho.agilebi.modeler.models.JoinRelationshipModel;
  import org.pentaho.agilebi.modeler.models.SchemaModel;
  import org.pentaho.agilebi.modeler.strategy.MultiTableAutoModelStrategy;
  import org.pentaho.agilebi.modeler.strategy.SimpleAutoModelStrategy;
  import org.pentaho.agilebi.modeler.strategy.StarSchemaAutoModelStrategy;
  import org.pentaho.di.core.database.DatabaseMeta;
  import org.pentaho.metadata.automodel.SchemaTable;
  import org.pentaho.metadata.model.*;
  import org.pentaho.metadata.model.concept.types.LocalizedString;
  import org.pentaho.metadata.model.concept.types.RelationshipType;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  import java.util.*;

  public class MultiTableModelerSource implements ISpoonModelerSource {

    private ModelGenerator generator;
    private DatabaseMeta databaseMeta;
    private SchemaModel schemaModel;
    private List<String> selectedTables;
    private String datasourceName;

    private GeoContext geoContext;

    public static final String SOURCE_TYPE = MultiTableModelerSource.class.getSimpleName();
    private static Logger logger = LoggerFactory.getLogger(MultiTableModelerSource.class);

    public MultiTableModelerSource(DatabaseMeta databaseMeta, SchemaModel schemaModel, String datasourceName, List<String> selectedTables) {
      this(databaseMeta, schemaModel, datasourceName, selectedTables, null);
    }
    public MultiTableModelerSource(DatabaseMeta databaseMeta, SchemaModel schemaModel, String datasourceName, List<String> selectedTables, GeoContext geoContext) {
      this.datasourceName = datasourceName;
      this.databaseMeta = databaseMeta;
      this.schemaModel = schemaModel;
      this.selectedTables = selectedTables;
      this.generator = new ModelGenerator();
      this.geoContext = geoContext;
    }

    @Override
    public Domain generateDomain(boolean doOlap) throws ModelerException {
      
    	 Domain domain = null;
         try {
           // Generate domain based on the table names.

           String locale = LocalizedString.DEFAULT_LOCALE;
           this.generator.setLocale(locale);
           this.generator.setDatabaseMeta(databaseMeta);
           this.generator.setModelName(datasourceName);

           Set<String> usedTables = new HashSet<String>();
           List<SchemaTable> schemas = new ArrayList<SchemaTable>();
           if(selectedTables.size() == 1){   // special single table story BISERVER-5806

        	 String singleTable = selectedTables.get(0);
        	 if(!usedTables.contains(singleTable)){
               schemas.add(createSchemaTable(singleTable));  
        	 } 
        	 
           } else {
        	 for(JoinRelationshipModel joinModel : schemaModel.getJoins()) {
                 
        	   String fromTable = joinModel.getLeftKeyFieldModel().getParentTable().getName();	  
               if(!usedTables.contains(fromTable)){
                 schemas.add(createSchemaTable(fromTable));
                 usedTables.add(fromTable);
               }
               
        	   String toTable = joinModel.getRightKeyFieldModel().getParentTable().getName();
               if(!usedTables.contains(toTable)){
                 schemas.add(createSchemaTable(toTable));
                 usedTables.add(toTable);
               }
               
             }
           }
           SchemaTable tableNames[] = new SchemaTable[schemas.size()];
           tableNames = schemas.toArray(tableNames);
           this.generator.setTableNames(tableNames);
           domain = this.generator.generateDomain();
           domain.setId(datasourceName);
           
           ModelerWorkspaceHelper helper = new ModelerWorkspaceHelper(locale);
           if(selectedTables.size() == 1){ // single table mode
             helper.setAutoModelStrategy(new SimpleAutoModelStrategy(locale, geoContext));
           } else if (doOlap){
             helper.setAutoModelStrategy(new StarSchemaAutoModelStrategy(locale, geoContext));
           } else {
             helper.setAutoModelStrategy(new MultiTableAutoModelStrategy(locale));
           }

           domain.getLogicalModels().get(0).setProperty("DUAL_MODELING_SCHEMA", ""+doOlap);

           ModelerWorkspace workspace = new ModelerWorkspace(helper, geoContext);
           workspace.setDomain(domain);
           
           LogicalModel logicalModel = workspace.getLogicalModel(ModelerPerspective.REPORTING);
           logicalModel.setProperty("AGILE_BI_GENERATED_SCHEMA", "TRUE");
           logicalModel.setName(new LocalizedString(locale, datasourceName));
           logicalModel.setDescription(new LocalizedString(locale, "This is the data model for "
               + datasourceName));  // TODO do this with messages

           workspace.setModelName(datasourceName);
           helper.autoModelRelationalFlat(workspace);
           LogicalModel olapModel = null;

           if(doOlap) {

             olapModel = workspace.getLogicalModel(ModelerPerspective.ANALYSIS);
             if(olapModel.getLogicalRelationships().size() != logicalModel.getLogicalRelationships().size()) {
               ModelerConversionUtil.duplicateRelationshipsForOlap(logicalModel, olapModel);
             }
             String factTableName = getSchemaTablePair(schemaModel.getFactTable().getName())[1];
             LogicalTable factTable = findFactTable(factTableName, olapModel);
             if(factTable == null) {
               throw new IllegalStateException("Fact table not found");
             } else {
               factTable.getPhysicalTable().setProperty("FACT_TABLE", true);
               workspace.getAvailableTables().setFactTable(factTable.getPhysicalTable());
             }
             // somehow the strategy is getting set to simple. setting it back again.
             if(selectedTables.size() == 1){ // single table mode
               helper.setAutoModelStrategy(new SimpleAutoModelStrategy(locale, geoContext));
             } else {
               helper.setAutoModelStrategy(new StarSchemaAutoModelStrategy(locale, geoContext));
             }
             helper.autoModelFlat(workspace);
             workspace.setModellingMode(ModelerMode.ANALYSIS_AND_REPORTING);
           } else {
             workspace.setModellingMode(ModelerMode.REPORTING_ONLY);
           }

           // Create and add LogicalRelationships to the LogicalModel from the
           // domain.
           generateLogicalRelationships(logicalModel, false);
           if(doOlap) {
        	   generateLogicalRelationships(olapModel, true);
           }

           helper.populateDomain(workspace);

           for(LogicalTable businessTable : logicalModel.getLogicalTables()) {
             businessTable.setName(new LocalizedString(locale, businessTable.getPhysicalTable().getName(locale)));
           }

           if(olapModel != null) {
             for(LogicalTable businessTable : olapModel.getLogicalTables()) {
               businessTable.setName(new LocalizedString(locale, businessTable.getPhysicalTable().getName(locale)));
             }
           }

         } catch (Exception e) {
           logger.debug(e.getLocalizedMessage(), e);
           logger.info(e.getLocalizedMessage());
           throw new ModelerException(e.getLocalizedMessage(), e);
         }
         return domain;
    }
    
    private SchemaTable createSchemaTable(String table) {
      String schemaName = "";
      String tableName = table;
      if(table.indexOf(".") > 0){
         String[] pair = getSchemaTablePair(table);
         schemaName = pair[0];
         tableName = pair[1];
      }
      return new SchemaTable(schemaName, tableName);
    }

    private String[] getSchemaTablePair(String table) {
      if(table.indexOf(".") < 0){
        return new String[]{"",table};
      }
      String[] pair = new String[2];
      String[] parts = table.split("\\.");
      pair[0] = parts[0];
      pair[1] = StringUtils.join(Arrays.copyOfRange(parts, 1, parts.length), ".");
      return pair;
    }

    private LogicalTable findFactTable(String table, LogicalModel logicalModel) {
       LogicalTable factTable = null;
   	   for (LogicalTable lTable : logicalModel.getLogicalTables()) {
   		   if(lTable.getId().endsWith(BaseModelerWorkspaceHelper.OLAP_SUFFIX)) {
           Object prop = lTable.getPhysicalTable().getProperty("target_table");
	   		   if(prop != null && prop.equals(table)) {
	   		      factTable = lTable;
	   		      break;
	   		   }
   		   }
   	   }
   	   return factTable;
    }
    
    private void generateLogicalRelationships(LogicalModel logicalModel, boolean doOlap) throws IllegalStateException {
    	for(JoinRelationshipModel joinModel : schemaModel.getJoins()) {
    	  	String lTable = joinModel.getLeftKeyFieldModel().getParentTable().getName();
        	String rTable = joinModel.getRightKeyFieldModel().getParentTable().getName();
          lTable = getSchemaTablePair(lTable)[1];
          rTable = getSchemaTablePair(rTable)[1];
    		
            LogicalTable fromTable = null;
            LogicalColumn fromColumn = null;
            LogicalTable toTable = null;
            LogicalColumn toColumn = null;

            for (LogicalTable logicalTable : logicalModel.getLogicalTables()) {
            	
              if(doOlap && !logicalTable.getId().endsWith(BaseModelerWorkspaceHelper.OLAP_SUFFIX)) {
            	  continue; 
              } else if(!doOlap && logicalTable.getId().endsWith(BaseModelerWorkspaceHelper.OLAP_SUFFIX)) {
            	  continue;
              }

              if (logicalTable.getPhysicalTable().getProperty("target_table").equals(lTable)) {
                fromTable = logicalTable;

                for (LogicalColumn logicalColumn : fromTable.getLogicalColumns()) {
                  if (logicalColumn.getPhysicalColumn().getProperty("target_column").equals(joinModel.getLeftKeyFieldModel().getName())) {
                    fromColumn = logicalColumn;
                    break;
                  }
                }
              }
              if (logicalTable.getPhysicalTable().getProperty("target_table").equals(rTable)) {
                toTable = logicalTable;

                for (LogicalColumn logicalColumn : toTable.getLogicalColumns()) {
                  if (logicalColumn.getPhysicalColumn().getProperty("target_column").equals(joinModel.getRightKeyFieldModel().getName())) {
                    toColumn = logicalColumn;
                    break;
                  }
                }
              }
            }
            
            if(fromTable == null || fromColumn == null || toTable == null || toColumn == null) {
            	throw new IllegalStateException("Invalid Relationship");
            }

            LogicalRelationship logicalRelationship = new LogicalRelationship();
            logicalRelationship.setRelationshipType(RelationshipType._1_1);
            logicalRelationship.setFromTable(fromTable);
            logicalRelationship.setFromColumn(fromColumn);
            logicalRelationship.setToTable(toTable);
            logicalRelationship.setToColumn(toColumn);
            logicalModel.addLogicalRelationship(logicalRelationship);
          }
    }

    
    @Override
    public String getDatabaseName() {
      String name = null;
      if (this.databaseMeta != null) {
        name = this.databaseMeta.getDatabaseName();
      }
      return name;
    }

    @Override
    public void initialize(Domain domain) throws ModelerException {
    }

    @Override
    public void serializeIntoDomain(Domain d) {
      LogicalModel lm = d.getLogicalModels().get(0);
      lm.setProperty("source_type", SOURCE_TYPE);
    }

    @Override
    public String getSchemaName() {
      return null;
    }

    @Override
    public String getTableName() {
      return null;
    }

    @Override
    public DatabaseMeta getDatabaseMeta() {
      return this.databaseMeta;
    }

    public Domain generateDomain() throws ModelerException {
    	return generateDomain(false);
    }
  }
