/*******************************************************************************
 * Copyright (C) 2021, 1C-Soft LLC 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:
 *     1C-Soft LLC - initial API and implementation
 *******************************************************************************/
package com.e1c.v8codestyle.bsl.check;

import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.MODULE;

import java.text.MessageFormat;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.builder.MonitorBasedCancelIndicator;
import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.resource.IResourceDescription;

import com._1c.g5.v8.dt.bsl.common.IModuleExtensionService;
import com._1c.g5.v8.dt.bsl.common.IModuleExtensionServiceProvider;
import com._1c.g5.v8.dt.bsl.model.Method;
import com._1c.g5.v8.dt.bsl.model.Module;
import com._1c.g5.v8.dt.bsl.resource.BslResourceDescription;
import com._1c.g5.v8.dt.common.StringUtils;
import com._1c.g5.v8.dt.mcore.McorePackage;
import com.e1c.g5.v8.dt.check.ICheckParameters;
import com.e1c.g5.v8.dt.check.components.BasicCheck;
import com.e1c.g5.v8.dt.check.components.ModuleTopObjectNameFilterExtension;
import com.e1c.g5.v8.dt.check.settings.IssueType;
import com.e1c.v8codestyle.check.CommonSenseCheckExtension;
import com.e1c.v8codestyle.internal.bsl.BslPlugin;
import com.google.common.collect.Lists;
import com.google.inject.Inject;

/**
 * Unused module method check. This class needs {@link IQualifiedNameProvider} to inject other service.
 *
 * @author Andrey Volkov
 * @author Dmitriy Marmyshev
 */
public final class ModuleUnusedMethodCheck
    extends BasicCheck
{
    private static final String CHECK_ID = "module-unused-method"; //$NON-NLS-1$

    private static final String EXCLUDE_METHOD_NAME_PATTERN_PARAMETER_NAME = "excludeModuleMethodNamePattern"; //$NON-NLS-1$

    private static final String DEFAULT_EXCLUDE_METHOD_NAME_PATTERN = "^(?U)(Подключаемый|Attachable)_.*$"; //$NON-NLS-1$

    private final IResourceDescription.Manager resourceDescriptionManager;

    /**
     * Instantiates a new module unused method check.
     *
     * @param resourceDescriptionManager the resource description manager service, cannot be {@code null}.
     */
    @Inject
    public ModuleUnusedMethodCheck(IResourceDescription.Manager resourceDescriptionManager)
    {
        super();
        this.resourceDescriptionManager = resourceDescriptionManager;
    }

    @Override
    public String getCheckId()
    {
        return CHECK_ID;
    }

    @Override
    protected void configureCheck(CheckConfigurer builder)
    {
        builder.title(Messages.ModuleUnusedMethodCheck_Title)
            .description(Messages.ModuleUnusedMethodCheck_Description)
            .extension(new ModuleTopObjectNameFilterExtension())
            .extension(new CommonSenseCheckExtension(getCheckId(), BslPlugin.PLUGIN_ID))
            .parameter(EXCLUDE_METHOD_NAME_PATTERN_PARAMETER_NAME, String.class, DEFAULT_EXCLUDE_METHOD_NAME_PATTERN,
                Messages.ModuleUnusedMethodCheck_Exclude_method_name_pattern_title)
            .issueType(IssueType.WARNING)
            .module()
            .checkedObjectType(MODULE);
    }

    @Override
    protected void check(Object object, ResultAcceptor resultAceptor, ICheckParameters parameters,
        IProgressMonitor progressMonitor)
    {
        Module module = (Module)object;

        IModuleExtensionService service = IModuleExtensionServiceProvider.INSTANCE.getModuleExtensionService();
        String excludeNamePattern = parameters.getString(EXCLUDE_METHOD_NAME_PATTERN_PARAMETER_NAME);

        Predicate<? super Method> predicate = method -> method.getName() != null && !method.isUsed()
            && !method.isExport() && !method.isEvent()
            && service.getSourceMethodNames(method).isEmpty() && !isExcludeName(method.getName(), excludeNamePattern);

        // TODO - only full validation first, optimization later
        //@formatter:off
        //if (!((BslResource)module.eResource()).isOnlyMethodReparse())
        //{
        Set<URI> usedMethods = getUsedMethods(progressMonitor, module.eResource());
        predicate = predicate.and(method -> !usedMethods.contains(EcoreUtil.getURI((EObject)method)));
        //}
        //@formatter:on

        module.allMethods()
            .stream()
            .filter(predicate)
            .forEach(unusedMethod -> resultAceptor.addIssue(
                MessageFormat.format(Messages.ModuleUnusedMethodCheck_Unused_method__0, unusedMethod.getName()),
                unusedMethod, McorePackage.Literals.NAMED_ELEMENT__NAME));
    }

    private Set<URI> getUsedMethods(IProgressMonitor progressMonitor, Resource resource)
    {
        IResourceDescription descr = resourceDescriptionManager.getResourceDescription(resource);
        return (descr instanceof BslResourceDescription
            ? Lists.newArrayList(((BslResourceDescription)descr)
                .getReferenceDescriptions(new MonitorBasedCancelIndicator(progressMonitor)))
            : Lists.newArrayList(descr.getReferenceDescriptions())).stream()
                .map(reference -> reference.getTargetEObjectUri())
                .collect(Collectors.toSet());
    }

    private boolean isExcludeName(String name, String excludeNamePattern)
    {
        return StringUtils.isNotEmpty(excludeNamePattern) && name.matches(excludeNamePattern);
    }
}
