/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
/*
 * Created on Sep 13, 2005
 * 
 * @author Fabio Zadrozny
 */
package com.python.pydev.analysis.additionalinfo;

import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.python.pydev.core.IInterpreterInfo;
import org.python.pydev.core.IInterpreterManager;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.ISystemModulesManager;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.docutils.PyStringUtils;
import org.python.pydev.core.log.Log;
import org.python.pydev.plugin.nature.SystemPythonNature;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.ui.interpreters.PythonInterpreterManager;
import org.python.pydev.ui.pythonpathconf.InterpreterInfo;

import com.python.pydev.analysis.AnalysisPlugin;

public class AdditionalSystemInterpreterInfo extends AbstractAdditionalInfoWithBuild {

    private final IInterpreterManager manager;
    private final String additionalInfoInterpreter;

    /**
     * holds system info (interpreter name points to system info)
     */
    private final static Map<Tuple<String, String>, AbstractAdditionalTokensInfo> additionalSystemInfo = new HashMap<Tuple<String, String>, AbstractAdditionalTokensInfo>();

    private final static Object additionalSystemInfoLock = new Object();

    private final File persistingFolder;

    private final File persistingLocation;
    private IPythonNature nature;

    public IInterpreterManager getManager() {
        return manager;
    }

    public String getAdditionalInfoInterpreter() {
        return additionalInfoInterpreter;
    }

    @Override
    protected String getUIRepresentation() {
        return manager != null ? manager.getManagerRelatedName() : "Unknown manager";
    }

    @Override
    protected IPythonNature getNature() {
        return nature;
    }

    /**
     * @return the path to the folder we want to keep things on
     * @throws MisconfigurationException 
     */
    @Override
    protected File getPersistingFolder() {
        return persistingFolder;
    }

    @Override
    protected File getPersistingLocation() throws MisconfigurationException {
        return persistingLocation;
    }

    @Override
    protected Set<String> getPythonPathFolders() {
        Set<String> ret = new HashSet<>();
        try {
            IInterpreterInfo interpreterInfo = this.manager.getInterpreterInfo(additionalInfoInterpreter,
                    new NullProgressMonitor());
            ret.addAll(interpreterInfo.getPythonPath());
        } catch (MisconfigurationException e) {
            Log.log(e);
        }
        return ret;
    }

    public AdditionalSystemInterpreterInfo(IInterpreterManager manager, String interpreter)
            throws MisconfigurationException {
        super(false); //don't call init just right now...
        this.manager = manager;
        this.additionalInfoInterpreter = interpreter;

        try {
            nature = manager != null ? new SystemPythonNature(manager) : null;
        } catch (MisconfigurationException e) {
            Log.log(e);
        }

        File base;
        try {
            IPath stateLocation = AnalysisPlugin.getDefault().getStateLocation();
            base = stateLocation.toFile();
        } catch (Exception e) {
            //it may fail in tests... (save it in default folder in this cases)
            Log.logInfo("Error getting persisting folder", e);
            base = new File(".");
        }
        File file = new File(base, manager.getManagerRelatedName() + "_"
                + PyStringUtils.getExeAsFileSystemValidPath(this.additionalInfoInterpreter));

        try {
            if (!file.exists()) {
                file.mkdirs();
            }
        } catch (Exception e) {
            Log.log(e);
        }
        persistingFolder = file;
        persistingLocation = new File(persistingFolder, manager.getManagerRelatedName() + ".pydevsysteminfo");

        init();
    }

    public static AbstractAdditionalDependencyInfo getAdditionalSystemInfo(IInterpreterManager manager,
            String interpreter) throws MisconfigurationException {
        return getAdditionalSystemInfo(manager, interpreter, false);
    }

    /**
     * Should only be used in tests.
     */
    public static void setAdditionalSystemInfo(PythonInterpreterManager manager, String executableOrJar,
            AdditionalSystemInterpreterInfo additionalInfo) {
        synchronized (additionalSystemInfoLock) {
            Tuple<String, String> key = new Tuple<String, String>(manager.getManagerRelatedName(), executableOrJar);
            additionalSystemInfo.put(key, additionalInfo);
        }
    }

    /**
     * @param m the module manager that we want to get info on (python, jython...)
     * @return the additional info for the system
     * @throws MisconfigurationException 
     */
    public static AbstractAdditionalDependencyInfo getAdditionalSystemInfo(IInterpreterManager manager,
            String interpreter, boolean errorIfNotAvailable) throws MisconfigurationException {
        Tuple<String, String> key = new Tuple<String, String>(manager.getManagerRelatedName(), interpreter);
        synchronized (additionalSystemInfoLock) {
            AbstractAdditionalDependencyInfo info = (AbstractAdditionalDependencyInfo) additionalSystemInfo.get(key);
            if (info == null) {
                //lazy-load.
                info = new AdditionalSystemInterpreterInfo(manager, interpreter);
                additionalSystemInfo.put(key, info);

                if (!info.load()) {
                    try {
                        recreateAllInfo(manager, interpreter, new NullProgressMonitor());
                        info = (AbstractAdditionalDependencyInfo) additionalSystemInfo.get(key);
                    } catch (Exception e) {
                        Log.log(e);
                    }
                }
            }
            return info;
        }
    }

    public static void recreateAllInfo(IInterpreterManager manager, String interpreter, IProgressMonitor monitor) {
        synchronized (additionalSystemInfoLock) {
            try {
                final IInterpreterInfo interpreterInfo = manager.getInterpreterInfo(interpreter, monitor);
                int grammarVersion = interpreterInfo.getGrammarVersion();
                AbstractAdditionalTokensInfo currInfo = AdditionalSystemInterpreterInfo.getAdditionalSystemInfo(
                        manager, interpreter);
                if (currInfo != null) {
                    currInfo.clearAllInfo();
                }
                InterpreterInfo defaultInterpreterInfo = (InterpreterInfo) manager.getInterpreterInfo(interpreter,
                        monitor);
                ISystemModulesManager m = defaultInterpreterInfo.getModulesManager();
                AbstractAdditionalTokensInfo info = restoreInfoForModuleManager(monitor, m,
                        "(system: " + manager.getManagerRelatedName() + " - " + interpreter + ")",
                        new AdditionalSystemInterpreterInfo(manager, interpreter), null, grammarVersion);

                if (info != null) {
                    //ok, set it and save it
                    additionalSystemInfo.put(new Tuple<String, String>(manager.getManagerRelatedName(), interpreter),
                            info);
                    info.save();
                }
            } catch (Throwable e) {
                Log.log(e);
            }
        }
    }

    //Make it available for being in a HashSet.

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

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof AdditionalSystemInterpreterInfo)) {
            return false;
        }
        AdditionalSystemInterpreterInfo additionalSystemInterpreterInfo = (AdditionalSystemInterpreterInfo) obj;
        return this.additionalInfoInterpreter.equals(additionalSystemInterpreterInfo.additionalInfoInterpreter);
    }

}
