/*******************************************************************************
 * 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.OPERATOR_STYLE_CREATOR;
import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.STRING_LITERAL__LINES;

import java.util.Collection;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.EcoreUtil2;

import com._1c.g5.v8.bm.core.IBmTransaction;
import com._1c.g5.v8.bm.integration.IBmModel;
import com._1c.g5.v8.dt.bsl.model.BslIndexPackage;
import com._1c.g5.v8.dt.bsl.model.DynamicFeatureAccess;
import com._1c.g5.v8.dt.bsl.model.Expression;
import com._1c.g5.v8.dt.bsl.model.FeatureEntry;
import com._1c.g5.v8.dt.bsl.model.Module;
import com._1c.g5.v8.dt.bsl.model.ModuleContextDefIndex;
import com._1c.g5.v8.dt.bsl.model.OperatorStyleCreator;
import com._1c.g5.v8.dt.bsl.model.StaticFeatureAccess;
import com._1c.g5.v8.dt.bsl.model.StringLiteral;
import com._1c.g5.v8.dt.bsl.resource.DynamicFeatureAccessComputer;
import com._1c.g5.v8.dt.bsl.resource.ExportMethodProvider;
import com._1c.g5.v8.dt.common.StringUtils;
import com._1c.g5.v8.dt.core.platform.IBmModelManager;
import com._1c.g5.v8.dt.core.platform.IDtProject;
import com._1c.g5.v8.dt.core.platform.IResourceLookup;
import com._1c.g5.v8.dt.mcore.DerivedProperty;
import com._1c.g5.v8.dt.mcore.Environmental;
import com._1c.g5.v8.dt.mcore.Method;
import com._1c.g5.v8.dt.mcore.Property;
import com._1c.g5.v8.dt.mcore.util.Environments;
import com._1c.g5.v8.dt.mcore.util.McoreUtil;
import com._1c.g5.v8.dt.metadata.mdclass.CommonModule;
import com._1c.g5.v8.dt.metadata.mdclass.MdClassPackage;
import com.e1c.g5.dt.core.api.naming.INamingService;
import com.e1c.g5.v8.dt.check.CheckComplexity;
import com.e1c.g5.v8.dt.check.ICheckParameters;
import com.e1c.g5.v8.dt.check.components.BasicCheck;
import com.e1c.g5.v8.dt.check.settings.IssueSeverity;
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.inject.Inject;

/**
 * Check that notify description procedure is exist and available at the client.
 *
 * @author Dmitriy Marmyshev
 * @author Victor Golubev
 */
public class NotifyDescriptionToServerProcedureCheck
    extends BasicCheck
{
    private static final String CHECK_ID = "notify-description-to-server-procedure"; //$NON-NLS-1$

    private static final String BSL = "bsl"; //$NON-NLS-1$

    private static final String THIS_OBJECT = "ThisObject"; //$NON-NLS-1$

    private static final String THIS_OBJECT_RU = "ЭтотОбъект"; //$NON-NLS-1$

    private static final String NOTIFICATION = "NotifyDescription"; //$NON-NLS-1$

    private final DynamicFeatureAccessComputer dynamicFeatureAccessComputer;

    private final ExportMethodProvider exportMethodProvider;

    private final IResourceLookup resourceLookup;

    private final INamingService namingService;

    private final IBmModelManager bmModelManager;


    /**
     * Instantiates a new notify description to server procedure check.
     *
     * @param dynamicFeatureAccessComputer the dynamic feature access computer service, cannot be {@code null}
     * @param exportMethodProvider the export method provider service, cannot be {@code null}
     */
    @Inject
    public NotifyDescriptionToServerProcedureCheck(DynamicFeatureAccessComputer dynamicFeatureAccessComputer,
        ExportMethodProvider exportMethodProvider, IResourceLookup resourceLookup, INamingService namingService,
        IBmModelManager bmModelManager)
    {
        super();
        this.dynamicFeatureAccessComputer = dynamicFeatureAccessComputer;
        this.exportMethodProvider = exportMethodProvider;
        this.resourceLookup = resourceLookup;
        this.namingService = namingService;
        this.bmModelManager = bmModelManager;
    }

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

    @Override
    protected void configureCheck(CheckConfigurer builder)
    {
        builder.title(Messages.NotifyDescriptionToServerProcedureCheck_title)
            .description(Messages.NotifyDescriptionToServerProcedureCheck_description)
            .complexity(CheckComplexity.NORMAL)
            .severity(IssueSeverity.MAJOR)
            .issueType(IssueType.ERROR)
            .extension(new CommonSenseCheckExtension(getCheckId(), BslPlugin.PLUGIN_ID))
            .module()
            .checkedObjectType(OPERATOR_STYLE_CREATOR);
    }

    @Override
    protected void check(Object object, ResultAcceptor resultAceptor, ICheckParameters parameters,
        IProgressMonitor monitor)
    {
        OperatorStyleCreator osc = (OperatorStyleCreator)object;
        String operatorName = McoreUtil.getTypeName(osc.getType());

        if (!NOTIFICATION.equals(operatorName) || osc.getParams().isEmpty()
            || !(osc.getParams().get(0) instanceof StringLiteral))
        {
            return;
        }

        StringLiteral param = (StringLiteral)osc.getParams().get(0);
        final String methodName = getCalledProcedureName(param);
        if (monitor.isCanceled() || StringUtils.isBlank(methodName))
        {
            return;
        }

        String contextDefUriAsString = getBslModuleUri(osc);

        if (monitor.isCanceled() || contextDefUriAsString == null)
        {
            return;
        }

        URI contextDefUri = URI.createURI(contextDefUriAsString, true);

        IDtProject project = resourceLookup.getDtProject(contextDefUri);
        String contextDefIndexFqn = namingService.getDependentObjectFqnAsString(contextDefUri,
            BslIndexPackage.Literals.MODULE_CONTEXT_DEF_INDEX);
        IBmModel model = bmModelManager.getModel(project);
        IBmTransaction bmTransaction = model.getEngine().getCurrentTransaction();
        ModuleContextDefIndex index = (ModuleContextDefIndex)bmTransaction.getTopObjectByFqn(contextDefIndexFqn);
        if (index == null)
        {
            return;
        }

        Collection<Method> methods = exportMethodProvider.getMockMethods(index.getContextDef(), methodName, osc);
        if (methods.isEmpty())
        {
            resultAceptor.addIssue(
                Messages.NotifyDescriptionToServerProcedureCheck_Notify_description_procedure_should_be_export, param,
                STRING_LITERAL__LINES);
        }
        else
        {
            for (Method method : methods)
            {
                Environments calleeEnv = method.environments();
                if (calleeEnv.containsAny(Environments.MNG_CLIENTS))
                {
                    return;
                }
            }

            resultAceptor.addIssue(
                Messages.NotifyDescriptionToServerProcedureCheck_Notify_description_to_Server_procedure, param,
                STRING_LITERAL__LINES);
        }

    }

    private String getCalledProcedureName(Expression param)
    {
        if (param instanceof StringLiteral)
        {
            StringLiteral literal = (StringLiteral)param;
            if (literal.getLines().size() == 1)
            {
                return literal.lines(true).get(0);
            }
        }
        return null;
    }

    private String getBslModuleUri(OperatorStyleCreator osc)
    {
        List<Expression> params = osc.getParams();

        if (params.size() > 1)
        {
            Expression moduleParam = params.get(1);
            if (moduleParam instanceof StaticFeatureAccess)
            {
                return getBslModuleUri((StaticFeatureAccess)moduleParam);
            }
            else if (moduleParam instanceof DynamicFeatureAccess)
            {
                return getBslModuleUri((DynamicFeatureAccess)moduleParam);
            }
        }

        return null;
    }

    private String getBslModuleUri(StaticFeatureAccess object)
    {
        URI uri = null;
        if (object.getName().equals(THIS_OBJECT) || object.getName().equals(THIS_OBJECT_RU))
        {
            uri = EcoreUtil.getURI(object);
        }
        else
        {
            uri = getCommonModuleUri(object);
        }
        return constructBslUri(uri);
    }

    private String getBslModuleUri(DynamicFeatureAccess object)
    {
        URI uri = getCommonModuleUri(object);

        return constructBslUri(uri);
    }

    private String constructBslUri(URI uri)
    {
        if (uri == null)
        {
            return null;
        }
        return uri.trimFragment().trimFileExtension().appendFileExtension(BSL).toString();
    }

    private URI getCommonModuleUri(DynamicFeatureAccess object)
    {
        Environmental environmental = EcoreUtil2.getContainerOfType(object, Environmental.class);

        List<FeatureEntry> features = dynamicFeatureAccessComputer.getLastObject(object, environmental.environments());
        for (FeatureEntry featureEntry : features)
        {
            EObject feature = featureEntry.getFeature();
            if (feature instanceof Property && ((Property)feature).getName().equals(THIS_OBJECT))
            {
                Expression staticFeatureAccess = object.getSource();
                if (staticFeatureAccess instanceof StaticFeatureAccess)
                {
                    return getCommonModuleUri((StaticFeatureAccess)staticFeatureAccess);
                }
            }
            else if (feature instanceof Module)
            {
                return EcoreUtil.getURI(feature);
            }
            else if (feature instanceof CommonModule)
            {
                EObject module = (EObject)feature.eGet(MdClassPackage.Literals.COMMON_MODULE__MODULE, false);
                if (module != null)
                {
                    return EcoreUtil.getURI(module);
                }
            }
        }
        return null;
    }

    private URI getCommonModuleUri(StaticFeatureAccess object)
    {
        for (FeatureEntry entry : object.getFeatureEntries())
        {
            EObject f = entry.getFeature();
            if (f instanceof DerivedProperty)
            {
                EObject source = ((DerivedProperty)f).getSource();
                if (source instanceof CommonModule)
                {
                    return EcoreUtil.getURI((EObject)source.eGet(MdClassPackage.Literals.COMMON_MODULE__MODULE, false));
                }
            }
        }
        return null;
    }

}
