/*!
 * Copyright 2010 - 2017 Hitachi Vantara.  All rights reserved.
 *
 * 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.repository.utils;

import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.plugins.PluginRegistry;
import org.pentaho.di.core.plugins.RepositoryPluginType;
import org.pentaho.di.repository.RepositoriesMeta;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.repository.RepositoryMeta;
import org.pentaho.platform.api.engine.ICacheManager;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;

/**
 * Implementations can be used to obtain a PDI Repository instance in the platform. Created by nbaker on 11/5/15.
 */
public interface IRepositoryFactory {

  String SINGLE_DI_SERVER_INSTANCE = "singleDiServerInstance";

  Repository connect( String repositoryName ) throws KettleException;

  IRepositoryFactory DEFAULT = new CachingRepositoryFactory() {
    {
      // Make sure we're registered with PentahoSystem so we can be found.
      PentahoSystem.registerObject( this );
    }
  };

  /**
   * Sets the "ID" of the Repository Plugin Type to use (filebased, db, enterprise)
   * 
   * @param id
   */
  void setRepositoryId( String id );

  /**
   * Decorating implementation which caches Repository instances by Principal name in the ICacheManager.
   * DefaultRepositoryFactory used by default if a delegate factory isn't supplied.
   */
  class CachingRepositoryFactory implements IRepositoryFactory {

    public static final String REGION = "pdi-repository-cache";
    private IRepositoryFactory delegate;
    private Logger logger = LoggerFactory.getLogger( getClass() );

    public CachingRepositoryFactory() {
      this( new DefaultRepositoryFactory() );
    }

    public CachingRepositoryFactory( IRepositoryFactory delegate ) {
      this.delegate = delegate;
    }

    @Override
    public void setRepositoryId( String id ) {
      delegate.setRepositoryId( id );
    }

    @Override
    public Repository connect( String repositoryName ) throws KettleException {

      IPentahoSession session = PentahoSessionHolder.getSession();
      if ( session == null ) {
        logger.debug( "No active Pentaho Session, attempting to load PDI repository unauthenticated." );
        throw new KettleException( "Attempting to create PDI Repository with no Active PentahoSession. "
            + "This is not allowed." );
      }
      ICacheManager cacheManager = PentahoSystem.getCacheManager( session );

      String sessionName = session.getName();
      Repository repository = (Repository) cacheManager.getFromRegionCache( REGION, sessionName );
      if ( repository == null ) {
        logger.debug( "Repository not cached for user: " + sessionName + ". Creating new Repository." );
        repository = delegate.connect( repositoryName );
        if ( !cacheManager.cacheEnabled( REGION ) ) {
          cacheManager.addCacheRegion( REGION );
        }
        cacheManager.putInRegionCache( REGION, sessionName, repository );
      } else {
        logger.debug( "Repository was cached for user: " + sessionName );
      }
      return repository;
    }
  }

  /**
   * Default implementation of the RepositoryFactory. Code moved from PDIImportUtil here pretty much as-is.
   */
  class DefaultRepositoryFactory implements IRepositoryFactory {
    private String repositoryId = "PentahoEnterpriseRepository";

    @Override
    public void setRepositoryId( String id ) {
      this.repositoryId = id;
    }

    @Override
    public Repository connect( String repositoryName ) throws KettleException {

      RepositoriesMeta repositoriesMeta = new RepositoriesMeta();
      boolean singleDiServerInstance =
          "true".equals( PentahoSystem.getSystemSetting( SINGLE_DI_SERVER_INSTANCE, "true" ) ); //$NON-NLS-1$ //$NON-NLS-2$

      try {
        if ( singleDiServerInstance ) {

          // only load a default enterprise repository. If this option is set, then you cannot load
          // transformations or jobs from anywhere but the local server.

          String repositoriesXml =
              "<?xml version=\"1.0\" encoding=\"UTF-8\"?><repositories>" //$NON-NLS-1$
                  + "<repository><id>" + repositoryId + "</id>" //$NON-NLS-1$
                  + "<name>" + SINGLE_DI_SERVER_INSTANCE + "</name>" //$NON-NLS-1$ //$NON-NLS-2$
                  + "<description>" + SINGLE_DI_SERVER_INSTANCE + "</description>" //$NON-NLS-1$ //$NON-NLS-2$
                  + "<repository_location_url>"
                  + PentahoSystem.getApplicationContext().getFullyQualifiedServerURL()
                  + "</repository_location_url>" //$NON-NLS-1$ //$NON-NLS-2$
                  + "<version_comment_mandatory>N</version_comment_mandatory>" //$NON-NLS-1$
                  + "</repository>" //$NON-NLS-1$
                  + "</repositories>"; //$NON-NLS-1$

          ByteArrayInputStream sbis = new ByteArrayInputStream( repositoriesXml.getBytes( "UTF8" ) );
          repositoriesMeta.readDataFromInputStream( sbis );
        } else {
          // TODO: add support for specified repositories.xml files...
          repositoriesMeta.readData(); // Read from the default $HOME/.kettle/repositories.xml file.
        }
      } catch ( Exception e ) {
        throw new KettleException( "Meta repository not populated", e ); //$NON-NLS-1$
      }

      // Find the specified repository.
      RepositoryMeta repositoryMeta = null;
      try {
        if ( singleDiServerInstance ) {
          repositoryMeta = repositoriesMeta.findRepository( SINGLE_DI_SERVER_INSTANCE );
        } else {
          repositoryMeta = repositoriesMeta.findRepository( repositoryName );
        }

      } catch ( Exception e ) {
        throw new KettleException( "Repository not found", e ); //$NON-NLS-1$
      }

      if ( repositoryMeta == null ) {
        throw new KettleException( "RepositoryMeta is null" ); //$NON-NLS-1$
      }

      Repository repository = null;
      try {
        repository =
            PluginRegistry.getInstance().loadClass( RepositoryPluginType.class, repositoryMeta.getId(),
                Repository.class );
        repository.init( repositoryMeta );

      } catch ( Exception e ) {
        throw new KettleException( "Could not get repository instance", e ); //$NON-NLS-1$
      }

      // Two scenarios here: internal to server or external to server. If internal, you are already authenticated. If
      // external, you must provide a username and additionally specify that the IP address of the machine running this
      // code is trusted.
      repository.connect( PentahoSessionHolder.getSession().getName(), "password" );

      return repository;
    }
  }
}
