/*
 * Copyright 2003-2015 JetBrains s.r.o.
 *
 * 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 jetbrains.mps.plugins;

import jetbrains.mps.module.ReloadableModule;
import jetbrains.mps.plugins.applicationplugins.BaseApplicationPlugin;
import jetbrains.mps.plugins.projectplugins.BaseProjectPlugin;
import jetbrains.mps.project.AbstractModule;
import jetbrains.mps.util.ModuleNameUtil;
import jetbrains.mps.vfs.IFile;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.module.SModule;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class ModulePluginContributor extends PluginContributor {
  private static final Logger LOG = LogManager.getLogger(ModulePluginContributor.class);
  private static final String PLUGIN_STRING = ".plugin.";
  private static final String PROJECT_PLUGIN_SUFFIX = "_ProjectPlugin";
  private static final String APP_PLUGIN_SUFFIX = "_ApplicationPlugin";

  private static String getProjectPluginClassName(SModule module) {
    return String.format("%s%s%s%s", module.getModuleName(), PLUGIN_STRING, ModuleNameUtil.getModuleShortName(module), PROJECT_PLUGIN_SUFFIX);
  }

  private static String getApplicationPluginClassName(SModule module) {
    return String.format("%s%s%s%s", module.getModuleName(), PLUGIN_STRING, ModuleNameUtil.getModuleShortName(module), APP_PLUGIN_SUFFIX);
  }

  @NotNull
  public ReloadableModule getModule() {
    return myModule;
  }

  @NotNull
  private final ReloadableModule myModule;

  public ModulePluginContributor(@NotNull ReloadableModule module) {
    myModule = module;
  }

  @Override
  public BaseApplicationPlugin createApplicationPlugin() {
    String pluginClassName;
    Properties cfg = getComponentStartupConfiguration();
    if (cfg == null || (pluginClassName = cfg.getProperty("init.application")) == null) {
      // fallback to legacy, name convention approach
      pluginClassName = getApplicationPluginClassName(myModule);
    }
    return pluginClassName == null ? null : createPlugin(BaseApplicationPlugin.class, pluginClassName);
  }

  @Override
  public BaseProjectPlugin createProjectPlugin() {
    String pluginClassName;
    Properties cfg = getComponentStartupConfiguration();
    if (cfg == null || (pluginClassName = cfg.getProperty("init.project")) == null) {
      // fallback to legacy, name convention approach
      pluginClassName = getProjectPluginClassName(myModule);
    }
    return pluginClassName == null ? null : createPlugin(BaseProjectPlugin.class, pluginClassName);
  }

  @Nullable
  private <T> T createPlugin(Class<T> expectedClass, String className) {
    try {
      Class<?> pluginClass = myModule.getOwnClass(className);
      return  pluginClass.asSubclass(expectedClass).newInstance();
    } catch (ClassNotFoundException e) {
      return null;
    } catch (Throwable t) {
      LOG.error("Failed to instantiate plugin component activator", t);
      return null;
    }
  }

  @Nullable
  private Properties getComponentStartupConfiguration() {
    // although getResource does look into dependencies and chances are we read configuration
    // of another module, the fact we use loadOwnClass later prevents loading it second time.
    // However, shall update fallback solution (try to load from config name, then try to load from fallback name)
    // unless switch to files here
    IFile dir = ((AbstractModule) myModule).getModuleSourceDir();
    if (dir == null) {
      return null;
    }
    IFile cfg = dir.getDescendant("startup.properties");
    // Note, META-INF location won't work for groups of modules distributed as a single plugin, shall come up with better approach
    if (!cfg.exists()) {
      return null;
    }
    InputStream is = null;
    try {
      is = cfg.openInputStream();
      Properties rv = new Properties();
      rv.load(is);
      return rv;
    } catch (IOException ex) {
      LOG.warn("Failed to read startup.properties for module " + myModule.getModuleName(), ex);
    } finally {
      if (is != null) {
        try {
          is.close();
        } catch (IOException ignore) {}
      }
    }
    return null;
  }

  @Override
  public int hashCode() {
    return myModule.hashCode();
  }

  @Override
  public boolean equals(Object o) {
    return o instanceof ModulePluginContributor && (((ModulePluginContributor) o).myModule == myModule);
  }

  @Override
  public String toString() {
    return myModule + " plugin contributor";
  }
}
