/*******************************************************************************
 * Copyright (c) 2006, 2018 Red Hat Inc. and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Kyu Lee <klee@redhat.com> - initial API and implementation
 *******************************************************************************/
package org.eclipse.linuxtools.internal.changelog.core;

import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.linuxtools.changelog.core.IFormatterChangeLogContrib;
import org.eclipse.linuxtools.changelog.core.IParserChangeLogContrib;


/**
 * This class will manage extension related operations.
 */
public final class ChangeLogExtensionManager {


    private static final ChangeLogExtensionManager EXM = new ChangeLogExtensionManager();


    // These are used as a simple cache so we don't have to iterate over
    // all extensions to formatContribution every time the action is invoked.
    private IConfigurationElement cachedPrefFormatter = null;

    private IConfigurationElement[] cachedInFileFormateters = null;

    private IExtensionPoint parserExtensions = null;

    private IExtensionPoint formatterExtensions = null;

    private IParserChangeLogContrib parserContributor = null;

    private IConfigurationElement formatterConfigElementToUse = null;

    private ChangeLogExtensionManager() {
        getParserContributions();
        getFormatterContributions();
    }

    public static ChangeLogExtensionManager getExtensionManager() {
        return EXM;
    }

    private void getFormatterContributions() {
        formatterExtensions = Platform
                .getExtensionRegistry()
                .getExtensionPoint(
                        "org.eclipse.linuxtools.changelog.core", "formatterContribution"); //$NON-NLS-1$
    }

    private void getParserContributions() {

        parserExtensions = Platform.getExtensionRegistry().getExtensionPoint(
                "org.eclipse.linuxtools.changelog.core", "parserContribution"); //$NON-NLS-1$

    }

    public IParserChangeLogContrib getParserContributor(String editorName) {

         if (parserExtensions != null) {
            IConfigurationElement[] elements = parserExtensions
                    .getConfigurationElements();
            for (int i = 0; i < elements.length; i++) {
                if (elements[i].getName().equals("parser") // $NON-NLS-1$
                        && (elements[i].getAttribute("editor") // $NON-NLS-1$
                                .equals(editorName))) {
                    //$NON-NLS-1$
                    try {
                        IConfigurationElement bob = elements[i];
                        parserContributor = (IParserChangeLogContrib) bob
                                .createExecutableExtension("class"); // $NON-NLS-1$
                        return parserContributor;
                    } catch (CoreException e) {
                        ChangelogPlugin.getDefault().getLog().log(
                                new Status(IStatus.ERROR, ChangelogPlugin.PLUGIN_ID,
                                        IStatus.ERROR, e.getMessage(), e));
                    }

                }
            }
        }



        return null;
    }




    public IConfigurationElement getFormatterConfigElement() {
        return formatterConfigElementToUse;
    }

    /**
     * Fetches formatterName formatter from extension, but if there exists a inline
     * formatter for entryFileName, then it uses that inline formatter.
     * @param entryFilePath The full file name.
     * @param formatterName The formatter name.
     * @return The changelog formatter.
     */
    public IFormatterChangeLogContrib getFormatterContributor(String entryFilePath, String formatterName) {


        // extract just file name;
        String fileName;

        int lastDir = entryFilePath.lastIndexOf('/');
        if ((lastDir >= 0) && (lastDir +1 <= entryFilePath.length()))
            fileName = entryFilePath.substring(lastDir + 1, entryFilePath.length());
        else
            fileName = entryFilePath;

        // We don't yet know which formatter to use
        formatterConfigElementToUse = null;

        // IFile file = null;

        if (formatterExtensions != null) {
            IConfigurationElement[] elements = formatterExtensions
                    .getConfigurationElements();

            // cache the in-file formatters on the first run
            if (cachedInFileFormateters == null) {
                List<IConfigurationElement> inFileFormatters = new LinkedList<>();
                for (int i = 0; i < elements.length; i++) {
                    IConfigurationElement formatterConfigElement = elements[i];
                    if (formatterConfigElement.getName().equals("formatter") // $NON-NLS-1$
                            && formatterConfigElement.getAttribute("inFile") // $NON-NLS-1$
                                    .equalsIgnoreCase("true")) { // $NON-NLS-1$
                        inFileFormatters.add(elements[i]);
                    }
                }
                cachedInFileFormateters = inFileFormatters
                        .toArray(new IConfigurationElement[] {});
            }

            // check if there is an in-file changelog formatter for the
            // currently
            // edited file
            for (int i = 0; i < cachedInFileFormateters.length; i++) {
                IConfigurationElement formatterConfigElement = cachedInFileFormateters[i];

                IConfigurationElement[] patternElementTmp = formatterConfigElement
                        .getChildren();

                // error check
                if (patternElementTmp == null)
                    continue;
                IConfigurationElement patternElement = patternElementTmp[0];

                if (patternElement.getAttribute("pattern") == null) { // $NON-NLS-1$
                    ChangelogPlugin
                            .getDefault()
                            .getLog()
                            .log(
                                    new Status(
                                            IStatus.ERROR,
                                            ChangelogPlugin.PLUGIN_ID,
                                            IStatus.ERROR,
                                            Messages.getString("ChangeLog.ErrNonPattern"), // $NON-NLS-1$
                                            new Exception(Messages.getString("ChangeLog.ErrNonPattern")))); // $NON-NLS-1$
                } else {
                    String filePattern = patternElement.getAttribute("pattern"); // $NON-NLS-1$

                    try {
                        Pattern pattern = Pattern.compile(filePattern);
                        Matcher fileMatcher = pattern.matcher(fileName);

                        // if the filename of the current editor matches the
                        // file
                        // pattern then we're done
                        if (fileMatcher.matches()) {
                            formatterConfigElementToUse = formatterConfigElement;
                            break;
                        }
                    } catch (PatternSyntaxException e) {
                        ChangelogPlugin.getDefault().getLog().log(
                                new Status(IStatus.ERROR, ChangelogPlugin.PLUGIN_ID,
                                        IStatus.ERROR, e.getMessage(), e));
                    }
                }

            }

            // if we haven't found an in-file formatter we try to get the user's
            // prefered formatter
            if (formatterConfigElementToUse == null) {

                // we cache the user's preferred formatter on the first run, and
                // whenever it changes
                if (cachedPrefFormatter == null
                        || !cachedPrefFormatter.getAttribute("name").equals( // $NON-NLS-1$
                                formatterName)) {

                    for (int i = 0; i < elements.length; i++) {
                        IConfigurationElement formatterConfigElement = elements[i];
                        if (formatterConfigElement.getName()
                                .equals("formatter") && formatterConfigElement.getAttribute("inFile").equalsIgnoreCase("false")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                            if (formatterConfigElement.getAttribute("name") // $NON-NLS-1$
                                    .equals(formatterName))
                                cachedPrefFormatter = formatterConfigElement;
                            break;

                        }
                    }
                }

                formatterConfigElementToUse = cachedPrefFormatter;

                if (formatterConfigElementToUse == null) {
                    ChangelogPlugin
                            .getDefault()
                            .getLog()
                            .log(
                                    new Status(
                                            IStatus.ERROR,
                                            ChangelogPlugin.PLUGIN_ID,
                                            IStatus.ERROR,
                                            Messages.getString("ChangeLog.ErrRetrieveFormatter"), // $NON-NLS-1$
                                            new Exception(Messages.getString("ChangeLog.ErrRetrieveFormatter")))); // $NON-NLS-1$

                    return null;
                }

            }
        }



        try {
            return (IFormatterChangeLogContrib) formatterConfigElementToUse
                    .createExecutableExtension("class"); // $NON-NLS-1$

        } catch (CoreException e) {
            ChangelogPlugin.getDefault().getLog().log(
                    new Status(IStatus.ERROR, ChangelogPlugin.PLUGIN_ID, IStatus.ERROR, e
                            .getMessage(), e));
            e.printStackTrace();
        }
        return null;
    }
}
