/*******************************************************************************
 * Copyright (C) 2022, 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.ui.qfix;

import java.util.Optional;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.link.LinkedPositionGroup;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;

import com._1c.g5.v8.dt.bsl.model.Method;
import com._1c.g5.v8.dt.bsl.model.StaticFeatureAccess;
import com._1c.g5.v8.dt.bsl.ui.contentassist.BslProposalProvider;
import com._1c.g5.v8.dt.bsl.ui.quickfix.BslQuickFixUtil;
import com._1c.g5.v8.dt.metadata.mdclass.ScriptVariant;
import com.e1c.g5.v8.dt.bsl.check.qfix.IXtextBslModuleFixModel;
import com.e1c.g5.v8.dt.bsl.check.qfix.IXtextInteractiveBslModuleFixModel;
import com.google.common.base.Strings;

/**
 * Helper methods for xtext-based quick fixes
 *
 * @author Vadim Geraskin
 */
public final class QuickFixMethodsHelper
{
    /**
     * Creates method and writes it to module
     *
     * @param model the xtext BSL quick fix model, cannot be {@code null}
     * @param isFunc indicates whether the method is function or procedure
     * @throws BadLocationException
     */
    static void createMethod(IXtextInteractiveBslModuleFixModel model, boolean isFunc) throws BadLocationException
    {
        EObject element = model.getElement();

        String methodName = getMethodName(((StaticFeatureAccess)element).getName());
        String directiveName = ""; //$NON-NLS-1$

        //up to Method class
        Method method = EcoreUtil2.getContainerOfType(element, Method.class);

        if (method != null)
        {
            String methodKeywordType = getTypeMethodName(model, isFunc);
            String methodEndKeywordType = getTypeEndMethodName(model, isFunc);

            //calculation offset
            int totalEndOffset = NodeModelUtils.getNode(method).getTotalEndOffset();
            Optional<String> indent = model.getFormatString(method);

            if (indent.isPresent())
            {
                String func = createMethod(model, indent.get(), directiveName, methodName, methodKeywordType,
                    methodEndKeywordType);

                // Write method to module
                IXtextDocument document = (IXtextDocument)model.getDocument();
                BslQuickFixUtil.writeToDoc(document, totalEndOffset, func);
                flushMethod(model, indent.get(), methodKeywordType, totalEndOffset, directiveName);
            }
        }
    }

    /**
     * Provides offset for the given {@Method}
     *
     * @param method the method, cannot be {@code null}
     * @return offset
     */
    static int getMethodOffset(Method method)
    {
        //method always has statement
        return NodeModelUtils.findActualNodeFor(method.allStatements().get(0)).getOffset();
    }

    /**
     * Gets 'Procedure' or 'Function'
     *
     * @param model {@link IXtextBslModuleFixModel}, cannot be {@code null}
     * @param isFunc defines what will be return
     * @return 'Procedure' if <code>isFunc == false, else return 'Function'</code>
     */
    static String getTypeMethodName(IXtextBslModuleFixModel model, boolean isFunc)
    {
        boolean isRussion = model.getScriptVariant() == ScriptVariant.RUSSIAN;
        return BslProposalProvider.getTypeMethodName(model.getBslGrammar(), isFunc, isRussion);
    }

    /**
     * Gets 'EndProcedure' or 'EndFunction'
     *
     * @param model {@link IXtextBslModuleFixModel}, cannot be {@code null}
     * @param isFunc defines what will be return
     * @return 'EndProcedure' if <code>isFunc == false, else return 'EndFunction'</code>
     */
    static String getTypeEndMethodName(IXtextBslModuleFixModel model, boolean isFunc)
    {
        boolean isRussion = model.getScriptVariant() == ScriptVariant.RUSSIAN;
        return BslProposalProvider.getTypeEndMethodName(model.getBslGrammar(), isFunc, isRussion);
    }

    private static String getMethodName(String name)
    {
        StringBuilder builder = new StringBuilder();

        builder.append(name);
        builder.append('(');
        builder.append(')');
        return builder.toString();
    }

    private static String createMethod(IXtextInteractiveBslModuleFixModel model, String indent, String directive,
        String methodName, String methodKeyword, String methodEndKeyword)
    {
        String lineSeparator = model.getLineSeparator();
        String commentContent = Strings.repeat(lineSeparator, 2);
        String todoComment = indent + model.getIndentProvider().getIndent()
            + model.getBslGeneratorMultiLangProposals().getImplementationPropStr();

        StringBuilder builder = new StringBuilder();
        //add comment above function
        builder.append(commentContent);
        //create function body
        if (!Strings.isNullOrEmpty(directive))
        {
            builder.append(indent).append('&').append(directive).append(lineSeparator);
        }
        builder.append(indent).append(methodKeyword).append(' ');
        builder.append(methodName);
        builder.append(lineSeparator).append(todoComment).append(lineSeparator);
        builder.append(indent).append(methodEndKeyword);
        return builder.toString();
    }

    private static void flushMethod(IXtextInteractiveBslModuleFixModel model, String indent, String methodKeywordType,
        int totalEndOffset, String directiveName)
    {
        IXtextDocument document = (IXtextDocument)model.getDocument();
        String lineSeparator = model.getLineSeparator();
        String commentContent = Strings.repeat(lineSeparator, 2);
        //creating LinkedModeModel
        LinkedPosition[] groupParams = calculateParamsGroupForMethod(commentContent, indent, lineSeparator,
            methodKeywordType, document, model.getIssueData(), totalEndOffset);
        int posDec = totalEndOffset + indent.length() + methodKeywordType.length() + 1 + commentContent.length();
        if (!Strings.isNullOrEmpty(directiveName))
        {
            posDec += indent.length() + 1 + directiveName.length() + lineSeparator.length();
        }
        int posUse = model.getIssue().getOffset();

        int nameLen = ((StaticFeatureAccess)model.getElement()).getName().length();
        createLinkedModeModel(model, posDec, posUse, nameLen, groupParams);
    }

    private static LinkedPosition[] calculateParamsGroupForMethod(String commentContent, String indent,
        String lineSeparator, String functionKeywordType, IXtextDocument doc, String[] data, int offset)
    {
        if (data != null && data.length >= 2)
        {
            int numParams = Integer.parseInt(data[2]);
            LinkedPosition[] groupParams = new LinkedPosition[numParams];
            int startLen = data[1].length() + 2 + offset + commentContent.length() + indent.length()
                + functionKeywordType.length();
            if (!Strings.isNullOrEmpty(data[0]))
            {
                startLen += indent.length() + data[0].length() + lineSeparator.length() + 1;
            }
            for (int i = 3; i < data.length; ++i)
            {
                groupParams[i - 3] = new LinkedPosition(doc, startLen, data[i].length());
                startLen += data[i].length() + 2;
            }
            return groupParams;
        }
        else
        {
            return new LinkedPosition[0];
        }
    }

    private static void createLinkedModeModel(IXtextInteractiveBslModuleFixModel model, int posDec, int posUse,
        int length, LinkedPosition[] params)
    {
        IXtextDocument document = (IXtextDocument)model.getDocument();
        try
        {
            // create groups - this step is independent of the linked mode
            LinkedPositionGroup group = new LinkedPositionGroup();
            group.addPosition(new LinkedPosition(document, posDec, length));
            group.addPosition(new LinkedPosition(document, posUse, length));
            /* set up linked mode */
            LinkedModeModel linkedModeModel = model.getLinkedModeModel();
            linkedModeModel.addGroup(group);
            for (int i = 0; i < params.length; ++i)
            {
                LinkedPositionGroup groupParam = new LinkedPositionGroup();
                groupParam.addPosition(params[i]);
                linkedModeModel.addGroup(groupParam);
            }
            LinkedModeModel.closeAllModels(document);
            linkedModeModel.forceInstall();
            model.enterUiMode();
            model.selectAndRevealForLinkedModeModel(posDec, length);
        }
        catch (Exception e)
        {
            model.selectAndRevealForLinkedModeModel(posUse, length);
        }
    }

    private QuickFixMethodsHelper()
    {
        // Not to be instantiated
    }
}
