/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2017 by Hitachi Vantara : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/

package org.pentaho.di.starmodeler.generator;

import java.util.List;
import java.util.Set;

import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.starmodeler.ConceptUtil;
import org.pentaho.di.starmodeler.DefaultIDs;
import org.pentaho.metadata.model.Domain;
import org.pentaho.metadata.model.LogicalModel;
import org.pentaho.metadata.model.LogicalTable;
import org.pentaho.metadata.model.SqlDataSource;
import org.pentaho.metadata.model.SqlDataSource.DataSourceType;
import org.pentaho.metadata.model.SqlPhysicalModel;
import org.pentaho.metadata.model.SqlPhysicalTable;
import org.pentaho.metadata.model.concept.Concept;
import org.pentaho.metadata.model.concept.types.LocalizedString;

public class MetadataGenerator {
  private Domain logicalDomain;
  private List<DatabaseMeta> databases;

  /**
   * @param logicalDomain The logical domain containing star schemas without physical layer.
   * @param databases The list of databases to reference
   */
  public MetadataGenerator(Domain logicalDomain, List<DatabaseMeta> databases) {
    this.logicalDomain = logicalDomain;
    this.databases = databases;
  }

  public Domain generatePhysicalMetadataModel() throws KettleException {

    // First do some checking and lookups...
    //
    String targetDatabaseName = ConceptUtil.getString(logicalDomain, DefaultIDs.DOMAIN_TARGET_DATABASE);
    if (Utils.isEmpty(targetDatabaseName)) {
      throw new KettleException("Please specify a target database!");
    }
    DatabaseMeta targetDatabaseMeta = DatabaseMeta.findDatabase(databases, targetDatabaseName);
    if (targetDatabaseMeta==null) {
      throw new KettleException("Target database with name '"+targetDatabaseName+"' can't be found!");
    }

    // Now start creation of a new domain with physical underpinning.
    //
    Domain domain = new Domain();

    // Copy the domain information...
    //
    domain.setId( createId("DOMAIN", null, domain) );
    domain.setName(logicalDomain.getName());
    domain.setDescription(logicalDomain.getDescription());

    // Now copy all the models...
    //
    for (LogicalModel logicalModel : logicalDomain.getLogicalModels()) {
      // Copy model information...
      //
      LogicalModel model = new LogicalModel();
      model.setId( createId("MODEL", domain, model));
      model.setName(logicalModel.getName());
      model.setDescription(logicalModel.getDescription());

      // Create a physical model...
      //
      SqlPhysicalModel sqlModel = new SqlPhysicalModel();
      sqlModel.setDatasource(createSqlDataSource(targetDatabaseMeta));
      model.setPhysicalModel(sqlModel);

      for (LogicalTable logicalTable : logicalModel.getLogicalTables()) {
        LogicalTable table = new LogicalTable();
        table.setId( createId("LOGICAL_TABLE", logicalModel, logicalTable) );
        table.setName(logicalTable.getName());
        table.setDescription(logicalTable.getDescription());

        String targetTable = ConceptUtil.getString(logicalTable, DefaultIDs.LOGICAL_TABLE_PHYSICAL_TABLE_NAME);

        SqlPhysicalTable sqlTable = new SqlPhysicalTable(sqlModel);
        table.setPhysicalTable(sqlTable);

        // Copy name & description from physical level...
        //
        sqlTable.setId( createId("PHYSICAL_TABLE", logicalModel, logicalTable));
        sqlTable.setName(logicalTable.getName());
        sqlTable.setDescription(logicalTable.getDescription());
        sqlTable.setTableType(ConceptUtil.getTableType(logicalTable));
        sqlTable.setTargetSchema(targetDatabaseMeta.getPreferredSchemaName());
        sqlTable.setTargetTable(targetTable);


      }
    }

    return domain;
  }

  private SqlDataSource createSqlDataSource(DatabaseMeta databaseMeta) {
    SqlDataSource dataSource = new SqlDataSource();
    dataSource.setDatabaseName(databaseMeta.getDatabaseName());
    dataSource.setHostname(databaseMeta.getHostname());
    dataSource.setUsername(databaseMeta.getUsername());
    dataSource.setPassword(databaseMeta.getPassword());
    dataSource.setPort(databaseMeta.getDatabasePortNumberString());
    dataSource.setAttributes(databaseMeta.getExtraOptions());
    DataSourceType dataSourceType;
    switch(databaseMeta.getAccessType()) {
    case DatabaseMeta.TYPE_ACCESS_NATIVE : dataSourceType = DataSourceType.NATIVE; break;
    case DatabaseMeta.TYPE_ACCESS_ODBC   : dataSourceType = DataSourceType.ODBC; break;
    case DatabaseMeta.TYPE_ACCESS_JNDI   : dataSourceType = DataSourceType.JNDI; break;
    case DatabaseMeta.TYPE_ACCESS_OCI    : dataSourceType = DataSourceType.OCI; break;
    default: dataSourceType = DataSourceType.CUSTOM; break;
    }
    dataSource.setType(dataSourceType);

    return dataSource;
  }

  private String createId(String prefix, Concept parent, Concept item) {
    StringBuilder id = new StringBuilder(prefix);

    if (parent!=null) {
      id.append("_");
      id.append( extractId(parent));
    }
    id.append("_");
    id.append( extractId(item));

    return id.toString();
  }

  private String extractId(Concept item) {
    LocalizedString localizedName = item.getName();
    Set<String> locales = localizedName.getLocales();
    if (locales.isEmpty()) return "";
    // Just grab the first locale we come across
    // This should normally only one for the star modeler
    //
    String locale = locales.iterator().next();

    String id = localizedName.getLocalizedString(locale);
    id = id.toUpperCase().replace(" ", "_");

    return id;
  }

  /**
   * @return the logicalDomain
   */
  public Domain getLogicalDomain() {
    return logicalDomain;
  }

  /**
   * @param logicalDomain the logicalDomain to set
   */
  public void setLogicalDomain(Domain logicalDomain) {
    this.logicalDomain = logicalDomain;
  }

  /**
   * @return the databases
   */
  public List<DatabaseMeta> getDatabases() {
    return databases;
  }

  /**
   * @param databases the databases to set
   */
  public void setDatabases(List<DatabaseMeta> databases) {
    this.databases = databases;
  }
}
