/*
 * SLD Editor - The Open Source Java SLD Editor
 *
 * Copyright (C) 2016, SCISYS UK Limited
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.sldeditor.filter.v2.function;

import com.sldeditor.common.console.ConsoleManager;
import com.sldeditor.common.localisation.Localisation;
import com.sldeditor.filter.v2.expression.ExpressionNode;
import com.sldeditor.filter.v2.expression.ExpressionPanelv2;
import com.sldeditor.filter.v2.expression.FunctionInterfaceUtils;
import com.sldeditor.filter.v2.function.namefilter.FunctionNameFilterAll;
import com.sldeditor.filter.v2.function.namefilter.FunctionNameFilterInterface;
import com.sldeditor.filter.v2.function.namefilter.FunctionNameFilterRaster;
import com.sldeditor.ui.attribute.DataSourceAttributePanel;
import com.sldeditor.ui.attribute.SubPanelUpdatedInterface;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import org.geotools.filter.FunctionExpression;
import org.geotools.filter.FunctionExpressionImpl;
import org.geotools.filter.function.string.ConcatenateFunction;
import org.opengis.filter.capability.FunctionName;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
import org.opengis.parameter.Parameter;

/**
 * Panel to be able to edit FunctionField objects.
 *
 * @author Robert Ward (SCISYS)
 */
public class FunctionField extends JPanel {

    /** The Constant FUNCTION_PANEL. */
    private static final String FUNCTIONFIELD_PANEL = "FunctionField";

    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = 1L;

    /** The function combo box. */
    private JComboBox<String> functionComboBox;

    /** The function name map. */
    private transient Map<String, FunctionName> functionNameMap = new LinkedHashMap<>();

    /** The function name mgr. */
    private transient FunctionNameInterface functionNameMgr = null;

    /** Is edited symbol a raster flag. */
    private boolean isRasterSymbol = false;

    /** The current expression. */
    private transient Expression currentExpression = null;

    /** The current expression node. */
    private transient ExpressionNode currentExpressionNode = null;

    /** The add parameter button. */
    private JButton btnAddParameter;

    /** The pre selected function. */
    private boolean preSelectedFunction = false;

    /**
     * Gets the panel name.
     *
     * @return the panel name
     */
    public static String getPanelName() {
        return FUNCTIONFIELD_PANEL;
    }

    /**
     * Instantiates a new data source attribute panel.
     *
     * @param parentObj the parent obj
     * @param functionNameMgr the function name mgr
     */
    public FunctionField(
            SubPanelUpdatedInterface parentObj, FunctionNameInterface functionNameMgr) {
        this.functionNameMgr = functionNameMgr;

        setLayout(new BorderLayout(5, 0));

        functionComboBox = new JComboBox<>();
        add(functionComboBox, BorderLayout.CENTER);

        JPanel panel = new JPanel();
        add(panel, BorderLayout.SOUTH);

        btnAddParameter =
                new JButton(
                        Localisation.getString(
                                ExpressionPanelv2.class, "FunctionField.addParameter"));
        btnAddParameter.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        addButtonPressed(parentObj);
                    }
                });
        panel.add(btnAddParameter);

        functionComboBox.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent e) {

                        String newValueObj = (String) functionComboBox.getSelectedItem();

                        functionSelected(parentObj, newValueObj);
                    }
                });
    }

    /** Adds a variable parameter. */
    private void addParameter() {
        if (currentExpression instanceof FunctionExpression) {
            FunctionExpression functionExpression = (FunctionExpression) currentExpression;
            FunctionName functionName = functionExpression.getFunctionName();

            int argCount = functionName.getArgumentCount();

            if (functionName.getArgumentCount() < 0) {
                argCount *= -1;

                ExpressionNode childNode = new ExpressionNode();
                Parameter<?> parameter = functionName.getArguments().get(argCount - 1);
                childNode.setParameter(parameter);

                Expression newExpression = functionExpression.getParameters().get(argCount - 1);
                childNode.setExpression(newExpression);
                functionExpression.getParameters().add(newExpression);
                if (currentExpressionNode != null) {
                    currentExpressionNode.insert(childNode, currentExpressionNode.getChildCount());
                    currentExpressionNode.setDisplayString();
                }
            }
        } else if (currentExpression instanceof ConcatenateFunction) {
            ConcatenateFunction functionExpression = (ConcatenateFunction) currentExpression;
            FunctionName functionName = functionExpression.getFunctionName();

            int argCount = functionName.getArgumentCount();

            if (functionName.getArgumentCount() < 0) {
                argCount *= -1;
            }
            ExpressionNode childNode = new ExpressionNode();
            Parameter<?> parameter =
                    functionName
                            .getArguments()
                            .get(Math.min(functionName.getArguments().size() - 1, argCount - 1));
            childNode.setType(parameter.getType());
            childNode.setName(parameter.getName());

            Expression newExpression = null;
            childNode.setExpression(newExpression);
            List<Expression> parameters = functionExpression.getParameters();
            parameters.add(newExpression);
            functionExpression.setParameters(parameters);
            if (currentExpressionNode != null) {
                currentExpressionNode.insert(childNode, currentExpressionNode.getChildCount());
                currentExpressionNode.setDisplayString();
            }
        }
    }

    /**
     * Sets the field data types.
     *
     * @param fieldType the new data type
     */
    public void setDataType(Class<?> fieldType) {
        functionNameMap.clear();

        List<FunctionNameFilterInterface> functionNameFilterList = new ArrayList<>();

        if (isRasterSymbol) {
            functionNameFilterList.add(new FunctionNameFilterRaster());
        } else {
            functionNameFilterList.add(new FunctionNameFilterAll());
        }

        List<FunctionName> functionNameList =
                functionNameMgr.getFunctionNameList(fieldType, functionNameFilterList);

        for (FunctionName functionName : functionNameList) {
            String functionNameString = functionName.getName();

            functionNameMap.put(functionNameString, functionName);
        }

        populateFunctionComboBox();
    }

    /** Populate function combo box. */
    private void populateFunctionComboBox() {
        if (functionComboBox != null) {
            DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>();

            model.addElement("");

            // Sort function names alphabetically
            List<String> functionNameList = new ArrayList<>(functionNameMap.keySet());
            java.util.Collections.sort(functionNameList);

            for (String name : functionNameList) {
                model.addElement(name);
            }
            functionComboBox.setModel(model);
        }
    }

    /**
     * Gets the function list.
     *
     * @return the function list
     */
    protected List<String> getFunctionList() {
        List<String> functionList = new ArrayList<>();

        ComboBoxModel<String> model = functionComboBox.getModel();
        for (int i = 0; i < model.getSize(); i++) {
            functionList.add(model.getElementAt(i));
        }

        return functionList;
    }

    /**
     * Gets the selected item.
     *
     * @return the selected item
     */
    public String getSelectedItem() {
        return (String) functionComboBox.getSelectedItem();
    }

    /**
     * Sets the panel enabled.
     *
     * @param enabled the new panel enabled
     */
    public void setPanelEnabled(boolean enabled) {
        functionComboBox.setEnabled(enabled);
    }

    /**
     * Sets the function.
     *
     * @param expression the new attribute
     * @param node the node
     */
    public void setFunction(Expression expression, ExpressionNode node) {

        currentExpression = expression;
        currentExpressionNode = node;
        preSelectedFunction = true;

        if (expression == null) {
            functionComboBox.setSelectedIndex(-1);
        } else {
            if (expression instanceof FunctionExpressionImpl) {
                FunctionExpressionImpl functionExpression = (FunctionExpressionImpl) expression;
                FunctionName function = functionExpression.getFunctionName();

                String functionName = function.getName();

                functionComboBox.setSelectedItem(functionName);
            } else if (expression instanceof ConcatenateFunction) {
                ConcatenateFunction concatenateExpression = (ConcatenateFunction) expression;
                FunctionName function = concatenateExpression.getFunctionName();

                String functionName = function.getName();

                functionComboBox.setSelectedItem(functionName);
            } else if (expression instanceof Function) {
                Function functionExpression = (Function) expression;
                FunctionName function = functionExpression.getFunctionName();

                String functionName = function.getName();

                functionComboBox.setSelectedItem(functionName);
            } else {
                ConsoleManager.getInstance()
                        .error(
                                this,
                                Localisation.getString(
                                        DataSourceAttributePanel.class,
                                        "DataSourceAttributePanel.error1"));
            }
        }

        preSelectedFunction = false;
    }

    /**
     * Gets the expression.
     *
     * @return the expression
     */
    public Expression getExpression() {
        String functionNameString = (String) functionComboBox.getSelectedItem();

        FunctionName functionName = functionNameMap.get(functionNameString);

        if (functionName == null) {
            return null;
        }

        Expression newExpression = functionNameMgr.createExpression(functionName);

        int argCount = functionName.getArgumentCount();
        if (argCount < 0) {
            argCount *= -1;
        }
        if (newExpression instanceof FunctionExpression) {
            getFunctionExpression(newExpression, argCount);
        } else if (newExpression instanceof ConcatenateFunction) {
            getConcatenate(newExpression, argCount);
        } else if (newExpression instanceof Function) {
            Function function = (Function) newExpression;

            List<Expression> params = new ArrayList<>();

            for (Parameter<?> param : functionName.getArguments()) {
                for (int index = 0; index < param.getMinOccurs(); index++) {
                    params.add(null);
                }
            }
            boolean validSymbolFlag = (params.size() == argCount);
            if (validSymbolFlag) {
                Expression retValue =
                        FunctionInterfaceUtils.createNewFunction(newExpression, params);

                if (retValue != null) {
                    return retValue;
                } else {
                    ((FunctionExpression) function).setParameters(params);
                }
            }
        }

        return newExpression;
    }

    /**
     * Get function expression.
     *
     * @param newExpression the new expression
     * @param argCount the arg count
     */
    private void getFunctionExpression(Expression newExpression, int argCount) {
        FunctionExpression expression = (FunctionExpression) newExpression;

        List<Expression> params = new ArrayList<>();

        boolean validSymbolFlag = (params.size() == argCount);
        if (validSymbolFlag) {
            expression.setParameters(params);
        }
    }

    /**
     * Gets the concatenate.
     *
     * @param newExpression the new expression
     * @param argCount the arg count
     * @return the concatenate
     */
    private void getConcatenate(Expression newExpression, int argCount) {
        ConcatenateFunction expression = (ConcatenateFunction) newExpression;

        List<Expression> params = new ArrayList<>();

        for (int i = 0; i < argCount; i++) {
            params.add(null);
        }
        boolean validSymbolFlag = (params.size() == argCount);
        if (validSymbolFlag) {
            expression.setParameters(params);
        }
    }

    /**
     * Sets the is edited symbol a raster flag.
     *
     * @param isRasterSymbol the isRasterSymbol to set
     */
    public void setIsRasterSymbol(boolean isRasterSymbol) {
        this.isRasterSymbol = isRasterSymbol;
    }

    /**
     * Adds the button pressed.
     *
     * @param parentObj the parent obj
     */
    protected void addButtonPressed(SubPanelUpdatedInterface parentObj) {
        addParameter();
        if (parentObj != null) {
            parentObj.parameterAdded();
        }
    }

    /**
     * Sets the selected function.
     *
     * @param selectedFunction the new selected function
     */
    protected void setSelectedFunction(String selectedFunction) {
        functionComboBox.setSelectedItem(selectedFunction);
    }

    /**
     * Function selected.
     *
     * @param parentObj the parent obj
     * @param newValueObj the new value obj
     */
    private void functionSelected(SubPanelUpdatedInterface parentObj, String newValueObj) {
        FunctionName functionName = functionNameMap.get(newValueObj);
        boolean variableNoOfParameters = false;
        if (functionName != null) {
            variableNoOfParameters = (functionName.getArgumentCount() < 0);
        }

        btnAddParameter.setVisible(preSelectedFunction && variableNoOfParameters);

        if (parentObj != null) {
            parentObj.updateSymbol();
        }
    }
}
