/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Atsuhiko Yamanaka, JCraft,Inc. - initial API and implementation.
 *     IBM Corporation - ongoing maintenance
 *******************************************************************************/
package org.eclipse.jsch.internal.core;

import java.io.FileNotFoundException;
import java.util.Hashtable;
import java.util.ArrayList;

import org.eclipse.core.net.proxy.IProxyService;
import org.eclipse.core.runtime.*;
import org.eclipse.jsch.core.AbstractIdentityRepositoryFactory;
import org.eclipse.jsch.core.IJSchService;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;

import com.jcraft.jsch.IdentityRepository;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;

public class JSchCorePlugin extends Plugin{

  public static String ID="org.eclipse.jsch.core"; //$NON-NLS-1$

  // communication timeout with the server
  public static final int DEFAULT_TIMEOUT=60;
  private int communicationsTimeout=DEFAULT_TIMEOUT;
  private boolean needToLoadKnownHosts=true;
  private boolean needToLoadKeys=true;

  private JSch jsch;

  private String current_pkeys=""; //$NON-NLS-1$

  public static final String PT_AUTHENTICATOR="authenticator"; //$NON-NLS-1$
  public static final String PT_IDENTITYREPOSITORY="identityrepository"; //$NON-NLS-1$

  private static JSchCorePlugin plugin;
  private ServiceTracker<?, ?> tracker;

  private ServiceRegistration<?> jschService;

  public JSchCorePlugin(){
    plugin=this;
  }

  public static JSchCorePlugin getPlugin(){
    return plugin;
  }

  /**
   * Convenience method for logging CoreExceptions to the plugin log
   * 
   * @param e
   *          the exception
   */
  public static void log(CoreException e){
    log(e.getStatus().getSeverity(), e.getMessage(), e);
  }

  /**
   * Log the given status. Do not use this method for the IStatus from a
   * CoreException. Use<code>log(CoreException)</code> instead so the stack
   * trace is not lost.
   * 
   * @param status
   *          the status
   */
  public static void log(IStatus status){
    getPlugin().getLog().log(status);
  }

  public static void log(int severity, String message, Throwable e){
    log(new Status(severity, ID, 0, message, e));
  }

  /**
   * Get the communications timeout value in seconds
   * 
   * @return the timeout value in seconds
   */
  public int getTimeout(){
    return communicationsTimeout;
  }

  /**
   * Set the timeout value for communications to a value in seconds. The value
   * must be greater than or equal 0. If is it 0, there is no timeout.
   * 
   * @param timeout
   *          the timeout value in seconds
   */
  public void setTimeout(int timeout){
    this.communicationsTimeout=Math.max(0, timeout);
  }

  public synchronized JSch getJSch(){
    if(jsch==null){
      jsch=new JSch();
      setIdentityRepository();
    }
    return jsch;
  }

  public synchronized void setIdentityRepository(){

    IdentityRepository[] repositories = getPluggedInIdentityRepositries();
    String[] selected = Utils.getSelectedSSHAgent().split(","); //$NON-NLS-1$
    IdentityRepository irepo = null;

    for(String s : selected) {
      for(IdentityRepository _irepo : repositories) {
        if(s.equals(_irepo.getName()) && _irepo.getStatus()==IdentityRepository.RUNNING) {
          irepo = _irepo;
          break;
        }
      }
      if(irepo!=null)
        break;
    }

    if(irepo!=null){
      jsch.setIdentityRepository(irepo);
    }
    else{
      // set the internal default IdentityRepository
      jsch.setIdentityRepository(null);
    }

  }

  public IdentityRepository[] getPluggedInIdentityRepositries(){

    IExtension[] extensions=Platform.getExtensionRegistry().getExtensionPoint(
        JSchCorePlugin.ID, JSchCorePlugin.PT_IDENTITYREPOSITORY).getExtensions();

    if(extensions.length==0)
      return new IdentityRepository[0];

    ArrayList<IdentityRepository> tmp = new ArrayList<>();
    for(IExtension extension : extensions) {
      IConfigurationElement[] configs=extension.getConfigurationElements();
      if(configs.length==0){
        JSchCorePlugin
            .log(
                IStatus.ERROR,
                NLS
                    .bind(
                        "IdentityRepository {0} is missing required fields", (new Object[] {extension.getUniqueIdentifier()})), null);//$NON-NLS-1$
        continue;
      }
      try{
        IConfigurationElement config=configs[0];
        AbstractIdentityRepositoryFactory iirf =
            (AbstractIdentityRepositoryFactory)config.createExecutableExtension("run");//$NON-NLS-1$
        tmp.add(iirf.create());
      }
      catch(CoreException ex){
        JSchCorePlugin
            .log(
                IStatus.ERROR,
                NLS
                    .bind(
                        "Unable to instantiate identity repository {0}", (new Object[] {extension.getUniqueIdentifier()})), ex);//$NON-NLS-1$
      }
    }

    IdentityRepository[] repositories = new IdentityRepository[tmp.size()];
    for(int i=0; i<tmp.size(); i++){
      repositories[i]=tmp.get(i);
    }
    return repositories;
  }

  public void loadKnownHosts(){
    Preferences preferences=JSchCorePlugin.getPlugin().getPluginPreferences();
    String ssh_home=preferences.getString(IConstants.KEY_SSH2HOME);

    if(ssh_home.length()==0)
      ssh_home=PreferenceInitializer.SSH_HOME_DEFAULT;

    java.io.File file=new java.io.File(ssh_home, "known_hosts"); //$NON-NLS-1$
    try{
      getJSch().setKnownHosts(file.getPath());
    }
    catch(JSchException e){
      if (!(e.getCause() instanceof FileNotFoundException)) {
        JSchCorePlugin.log(IStatus.ERROR, NLS.bind(
            "An error occurred while loading the know hosts file {0}", file //$NON-NLS-1$
                .getAbsolutePath()), e);
      }
    }
    needToLoadKnownHosts=false;
  }

  public boolean isNeedToLoadKnownHosts(){
    return needToLoadKnownHosts;
  }

  public void setNeedToLoadKnownHosts(boolean needToLoadKnownHosts){
    this.needToLoadKnownHosts=needToLoadKnownHosts;
  }

  public boolean isNeedToLoadKeys(){
    return needToLoadKeys;
  }

  public void setNeedToLoadKeys(boolean needToLoadKeys){
    this.needToLoadKeys=needToLoadKeys;
  }

  public void loadPrivateKeys(){
    current_pkeys=Utils.loadPrivateKeys(getJSch(), current_pkeys);
    setNeedToLoadKeys(false);
  }

  /**
   * Return the {@link IProxyService} or <code>null</code> if the service is
   * not available.
   * 
   * @return the {@link IProxyService} or <code>null</code>
   */
  public IProxyService getProxyService(){
    return (IProxyService)tracker.getService();
  }

  @Override
  public void start(BundleContext context) throws Exception{
    super.start(context);
    tracker=new ServiceTracker<Object, Object>(getBundle().getBundleContext(),
        IProxyService.class.getName(), null);
    tracker.open();
    jschService=getBundle().getBundleContext().registerService(
        IJSchService.class.getName(), JSchProvider.getInstance(),
        new Hashtable<String, Object>());
  }

  @Override
  public void stop(BundleContext context) throws Exception{
    super.stop(context);
    tracker.close();
    jschService.unregister();
  }
}
