/*
 * 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.ui.detail;

import com.sldeditor.common.data.SelectedSymbol;
import com.sldeditor.common.vendoroption.minversion.VendorOptionPresent;
import com.sldeditor.common.xml.ui.FieldIdEnum;
import com.sldeditor.common.xml.ui.GroupIdEnum;
import com.sldeditor.ui.detail.config.base.GroupConfigInterface;
import com.sldeditor.ui.detail.config.base.MultiOptionGroup;
import com.sldeditor.ui.detail.config.base.OptionGroup;
import com.sldeditor.ui.detail.vendor.geoserver.VendorOptionInterface;
import com.sldeditor.ui.detail.vendor.geoserver.raster.VendorOptionRasterFactory;
import com.sldeditor.ui.iface.PopulateDetailsInterface;
import com.sldeditor.ui.iface.UpdateSymbolInterface;
import com.sldeditor.ui.widgets.ValueComboBoxData;
import java.util.List;
import org.geotools.styling.ChannelSelection;
import org.geotools.styling.ColorMap;
import org.geotools.styling.ContrastEnhancement;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.SelectedChannelType;
import org.geotools.styling.ShadedRelief;
import org.geotools.styling.ShadedReliefImpl;
import org.geotools.styling.Symbolizer;
import org.opengis.filter.expression.Expression;
import org.opengis.style.ContrastMethod;
import org.opengis.style.OverlapBehavior;

/**
 * The Class RasterSymbolizerDetails allows a user to configure raster data in a panel.
 *
 * @author Robert Ward (SCISYS)
 */
public class RasterSymbolizerDetails extends StandardPanel
        implements PopulateDetailsInterface, UpdateSymbolInterface {

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

    /** The vendor option raster factory. */
    private transient VendorOptionRasterFactory vendorOptionRasterFactory = null;

    /** Constructor. */
    public RasterSymbolizerDetails() {
        super(RasterSymbolizerDetails.class);

        createUI();
    }

    /** Creates the ui. */
    private void createUI() {

        createVendorOptionPanel();

        readRasterConfigFile(vendorOptionRasterFactory, getClass(), this, "Raster.xml");
    }

    /**
     * Creates the vendor option panel.
     *
     * @return the detail panel
     */
    private void createVendorOptionPanel() {

        vendorOptionRasterFactory = new VendorOptionRasterFactory(getClass(), this);

        List<VendorOptionInterface> voList = vendorOptionRasterFactory.getVendorOptionList();
        if (voList != null) {
            for (VendorOptionInterface vendorOption : voList) {
                vendorOption.setParentPanel(this);
            }
        }
    }

    /**
     * Populate.
     *
     * @param selectedSymbol the selected symbol
     */
    /*
     * (non-Javadoc)
     *
     * @see com.sldeditor.ui.iface.PopulateDetailsInterface#populate(com.sldeditor.ui.detail.
     * selectedsymbol.SelectedSymbol)
     */
    @Override
    public void populate(SelectedSymbol selectedSymbol) {

        if (selectedSymbol != null) {
            RasterSymbolizer rasterSymbolizer = (RasterSymbolizer) selectedSymbol.getSymbolizer();
            if (rasterSymbolizer != null) {
                populateStandardData(rasterSymbolizer);

                // Opacity
                fieldConfigVisitor.populateField(
                        FieldIdEnum.RASTER_OPACITY, rasterSymbolizer.getOpacity());

                // Contrast enhancement
                populateContrastEnhancement(rasterSymbolizer);

                // Channel selection
                populateChannelSelection(rasterSymbolizer);

                // Colour map
                populateColourMap(rasterSymbolizer);

                // Shaded relief
                populateShadedRelief(rasterSymbolizer);

                // Overlap behaviour
                populateOverlapBehavior(rasterSymbolizer);

                if (vendorOptionRasterFactory != null) {
                    vendorOptionRasterFactory.populate(rasterSymbolizer);
                }
            }
        }
    }

    /**
     * Populate overlap behavior.
     *
     * @param rasterSymbolizer the raster symbolizer
     */
    private void populateOverlapBehavior(RasterSymbolizer rasterSymbolizer) {
        OverlapBehavior overlapBehaviour = rasterSymbolizer.getOverlapBehavior();

        GroupConfigInterface group = getGroup(GroupIdEnum.RASTER_OVERLAP);
        if (group != null) {
            group.enable(overlapBehaviour != null);
        }
        if (overlapBehaviour != null) {
            fieldConfigVisitor.populateComboBoxField(
                    FieldIdEnum.RASTER_OVERLAP_BEHAVIOUR, overlapBehaviour.name());
        }
    }

    /**
     * Populate shaded relief.
     *
     * @param rasterSymbolizer the raster symbolizer
     */
    private void populateShadedRelief(RasterSymbolizer rasterSymbolizer) {
        GroupConfigInterface group;
        ShadedRelief shadedRelief = rasterSymbolizer.getShadedRelief();

        group = getGroup(GroupIdEnum.RASTER_SHADEDRELIEF);
        if (group != null) {
            group.enable(shadedRelief != null);
        }

        if (shadedRelief != null) {
            fieldConfigVisitor.populateBooleanField(
                    FieldIdEnum.RASTER_SHADEDRELIEF_BRIGHTNESS, shadedRelief.isBrightnessOnly());
            fieldConfigVisitor.populateField(
                    FieldIdEnum.RASTER_SHADEDRELIEF_FACTOR, shadedRelief.getReliefFactor());
        }
    }

    /**
     * Populate colour map.
     *
     * @param rasterSymbolizer the raster symbolizer
     */
    private void populateColourMap(RasterSymbolizer rasterSymbolizer) {
        ColorMap colourMap = rasterSymbolizer.getColorMap();

        fieldConfigVisitor.populateComboBoxField(
                FieldIdEnum.RASTER_COLOURMAP_TYPE, Integer.toString(colourMap.getType()));
        fieldConfigVisitor.populateColourMapField(FieldIdEnum.RASTER_COLOURMAP, colourMap);
    }

    /**
     * Populate channel selection.
     *
     * @param rasterSymbolizer the raster symbolizer
     */
    private void populateChannelSelection(RasterSymbolizer rasterSymbolizer) {
        GroupConfigInterface group;
        group = getGroup(GroupIdEnum.RASTER_CHANNELSELECTION);
        if (group != null) {
            MultiOptionGroup channelSelectionGroup = (MultiOptionGroup) group;

            ChannelSelection channelSelection = rasterSymbolizer.getChannelSelection();

            boolean enableChannelSelection = false;

            if (channelSelection != null) {
                SelectedChannelType[] rgbChannels = channelSelection.getRGBChannels();

                enableChannelSelection =
                        ((channelSelection.getGrayChannel() != null)
                                || (rgbChannels[0] != null)
                                || (rgbChannels[1] != null)
                                || (rgbChannels[2] != null));
            }
            channelSelectionGroup.enable(enableChannelSelection);
            if (enableChannelSelection) {
                SelectedChannelType greyChannel = channelSelection.getGrayChannel();
                if (greyChannel != null) {
                    channelSelectionGroup.setOption(GroupIdEnum.RASTER_GREY_CHANNEL_OPTION);

                    populateContrastEnhancementGroup(
                            GroupIdEnum.RASTER_GREY_CHANNEL,
                            GroupIdEnum.RASTER_RGB_CHANNEL_GREY_CONTRAST,
                            FieldIdEnum.RASTER_RGB_CHANNEL_GREY_CONTRAST_GAMMA,
                            GroupIdEnum.RASTER_RGB_CHANNEL_GREY_CONTRAST_METHOD,
                            greyChannel);
                } else {
                    SelectedChannelType[] rgbChannels = channelSelection.getRGBChannels();

                    channelSelectionGroup.setOption(GroupIdEnum.RASTER_RGB_CHANNEL_OPTION);

                    populateContrastEnhancementGroup(
                            GroupIdEnum.RASTER_RGB_CHANNEL_RED,
                            GroupIdEnum.RASTER_RGB_CHANNEL_RED_CONTRAST,
                            FieldIdEnum.RASTER_RGB_CHANNEL_RED_CONTRAST_GAMMA,
                            GroupIdEnum.RASTER_RGB_CHANNEL_RED_CONTRAST_METHOD,
                            rgbChannels[0]);

                    populateContrastEnhancementGroup(
                            GroupIdEnum.RASTER_RGB_CHANNEL_GREEN,
                            GroupIdEnum.RASTER_RGB_CHANNEL_GREEN_CONTRAST,
                            FieldIdEnum.RASTER_RGB_CHANNEL_GREEN_CONTRAST_GAMMA,
                            GroupIdEnum.RASTER_RGB_CHANNEL_GREEN_CONTRAST_METHOD,
                            rgbChannels[1]);

                    populateContrastEnhancementGroup(
                            GroupIdEnum.RASTER_RGB_CHANNEL_BLUE,
                            GroupIdEnum.RASTER_RGB_CHANNEL_BLUE_CONTRAST,
                            FieldIdEnum.RASTER_RGB_CHANNEL_BLUE_CONTRAST_GAMMA,
                            GroupIdEnum.RASTER_RGB_CHANNEL_BLUE_CONTRAST_METHOD,
                            rgbChannels[2]);
                }
            }
        }
    }

    /**
     * Populate contrast enhancement.
     *
     * @param rasterSymbolizer the raster symbolizer
     */
    private void populateContrastEnhancement(RasterSymbolizer rasterSymbolizer) {
        ContrastEnhancement contrast = rasterSymbolizer.getContrastEnhancement();

        GroupConfigInterface group = getGroup(GroupIdEnum.RASTER_CONTRAST);
        if (group != null) {
            group.enable(contrast != null);
        }
        if (contrast != null) {
            Expression gammaValue = contrast.getGammaValue();
            fieldConfigVisitor.populateField(FieldIdEnum.RASTER_CONTRAST_GAMMAVALUE, gammaValue);

            populateContrastMethod(contrast, GroupIdEnum.RASTER_OVERALL_CONTRAST_METHOD);
        }
    }

    /**
     * Populate contrast enhancement group.
     *
     * @param channelGroup the channel group
     * @param contrastGroup the contrast group
     * @param gammaField the gamma field
     * @param methodField the method field
     * @param channelType the channel type
     */
    private void populateContrastEnhancementGroup(
            GroupIdEnum channelGroup,
            GroupIdEnum contrastGroup,
            FieldIdEnum gammaField,
            GroupIdEnum methodField,
            SelectedChannelType channelType) {

        this.vendorOptionRasterFactory.setChannelName(channelGroup, channelType);

        GroupConfigInterface contrastGrp = getGroup(contrastGroup);

        ContrastEnhancement contrastEnhancement = null;

        if (channelType != null) {
            contrastEnhancement = channelType.getContrastEnhancement();
        }
        contrastGrp.enable(contrastEnhancement != null);
        if (contrastEnhancement != null) {
            fieldConfigVisitor.populateField(gammaField, contrastEnhancement.getGammaValue());

            populateContrastMethod(contrastEnhancement, methodField);
        }
    }

    /**
     * Populate contrast method.
     *
     * @param contrastEnhancement the contrast enhancement
     * @param contrastMethodGroup the contrast method group
     */
    private void populateContrastMethod(
            ContrastEnhancement contrastEnhancement, GroupIdEnum contrastMethodGroup) {
        GroupConfigInterface group = getGroup(contrastMethodGroup);
        if (group != null) {
            GroupIdEnum selectedNormalizeMethod = GroupIdEnum.UNKNOWN;
            MultiOptionGroup contrastNormalizeMethodGroup = (MultiOptionGroup) group;
            if (contrastEnhancement != null) {
                ContrastMethod contrastMethod = contrastEnhancement.getMethod();

                if (contrastMethod != null) {
                    String contrastMethodString = contrastMethod.name();

                    for (OptionGroup option : contrastNormalizeMethodGroup.getGroupList()) {
                        if (option.getLabel().compareToIgnoreCase(contrastMethodString) == 0) {
                            selectedNormalizeMethod = option.getId();
                        }
                    }
                }
            }
            contrastNormalizeMethodGroup.setOption(selectedNormalizeMethod);
        }
    }

    /** Update symbol. */
    private void updateSymbol() {

        // Contrast enhancement
        Expression gammaValueExpression =
                fieldConfigVisitor.getExpression(FieldIdEnum.RASTER_CONTRAST_GAMMAVALUE);

        ContrastEnhancement contrastEnhancement = updateContrastEnhancement(gammaValueExpression);

        // Colour map
        ColorMap colorMap = updateColourMap();

        // Channel selection
        ChannelSelection channelSelection = updateEnhancementGroup();

        //
        // Overlap
        //
        OverlapBehavior overlapBehavior = updateOverlapBehavior();

        //
        // Shaded relief
        //
        ShadedRelief shadedRelief = updateShadedRelief();

        Symbolizer symbolizer = null;

        StandardData standardData = getStandardData();

        Expression opacityExpression = fieldConfigVisitor.getExpression(FieldIdEnum.RASTER_OPACITY);

        // Geometry field
        Expression geometryField = ExtractGeometryField.getGeometryField(fieldConfigVisitor);

        RasterSymbolizer rasterSymbolizer =
                getStyleFactory()
                        .rasterSymbolizer(
                                standardData.getName(),
                                geometryField,
                                standardData.getDescription(),
                                (standardData.getUnit() != null)
                                        ? standardData.getUnit().getUnit()
                                        : null,
                                opacityExpression,
                                channelSelection,
                                overlapBehavior,
                                colorMap,
                                contrastEnhancement,
                                shadedRelief,
                                symbolizer);

        if (vendorOptionRasterFactory != null) {
            vendorOptionRasterFactory.updateSymbol(rasterSymbolizer);
        }
        rasterSymbolizer.setOverlapBehavior(overlapBehavior);
        SelectedSymbol.getInstance().replaceSymbolizer(rasterSymbolizer);

        this.fireUpdateSymbol();
    }

    /**
     * Update contrast enhancement.
     *
     * @param gammaValueExpression the gamma value expression
     * @return the contrast enhancement
     */
    private ContrastEnhancement updateContrastEnhancement(Expression gammaValueExpression) {
        ContrastEnhancement contrastEnhancement = null;

        GroupConfigInterface group = getGroup(GroupIdEnum.RASTER_CONTRAST);
        if (group.isPanelEnabled()) {
            String method = null;
            group = getGroup(GroupIdEnum.RASTER_OVERALL_CONTRAST_METHOD);
            if (group != null) {
                MultiOptionGroup contrastNormalizeMethodGroup = (MultiOptionGroup) group;

                OptionGroup selectedOption = contrastNormalizeMethodGroup.getSelectedOptionGroup();

                if (selectedOption != null) {
                    method = selectedOption.getLabel();
                }
            }

            contrastEnhancement =
                    (ContrastEnhancement)
                            getStyleFactory().contrastEnhancement(gammaValueExpression, method);
        }
        return contrastEnhancement;
    }

    /**
     * Update colour map.
     *
     * @return the color map
     */
    private ColorMap updateColourMap() {
        ColorMap colorMap = fieldConfigVisitor.getColourMap(FieldIdEnum.RASTER_COLOURMAP);
        ValueComboBoxData colourMapType =
                fieldConfigVisitor.getComboBox(FieldIdEnum.RASTER_COLOURMAP_TYPE);

        colorMap.setType(Integer.valueOf(colourMapType.getKey()));
        return colorMap;
    }

    /**
     * Update overlap behavior.
     *
     * @return the overlap behavior
     */
    private OverlapBehavior updateOverlapBehavior() {
        GroupConfigInterface group;
        OverlapBehavior overlapBehavior = null;
        group = getGroup(GroupIdEnum.RASTER_OVERLAP);
        if (group.isPanelEnabled()) {
            ValueComboBoxData overlapBehaviorValue =
                    fieldConfigVisitor.getComboBox(FieldIdEnum.RASTER_OVERLAP_BEHAVIOUR);

            overlapBehavior = OverlapBehavior.valueOf(overlapBehaviorValue.getKey());
        }
        return overlapBehavior;
    }

    /**
     * Update shaded relief.
     *
     * @return the shaded relief
     */
    private ShadedRelief updateShadedRelief() {
        GroupConfigInterface group;
        ShadedRelief shadedRelief = null;
        group = getGroup(GroupIdEnum.RASTER_SHADEDRELIEF);
        if (group.isPanelEnabled()) {
            shadedRelief = new ShadedReliefImpl();
            shadedRelief.setBrightnessOnly(
                    fieldConfigVisitor.getBoolean(FieldIdEnum.RASTER_SHADEDRELIEF_BRIGHTNESS));
            shadedRelief.setReliefFactor(
                    fieldConfigVisitor.getExpression(FieldIdEnum.RASTER_SHADEDRELIEF_FACTOR));
        }
        return shadedRelief;
    }

    /**
     * Update enhancement group.
     *
     * @return the channel selection
     */
    private ChannelSelection updateEnhancementGroup() {
        GroupConfigInterface group;
        ChannelSelection channelSelection = null;
        group = getGroup(GroupIdEnum.RASTER_CHANNELSELECTION);
        if (group != null) {
            if (group.isPanelEnabled()) {
                MultiOptionGroup contrastEnhancementGroup = (MultiOptionGroup) group;

                OptionGroup selectedOption = contrastEnhancementGroup.getSelectedOptionGroup();
                if (selectedOption.getId() == GroupIdEnum.RASTER_GREY_CHANNEL_OPTION) {
                    // Grey option group
                    SelectedChannelType greyChannel =
                            extractContrastEnhancementGroup(
                                    GroupIdEnum.RASTER_GREY_CHANNEL,
                                    GroupIdEnum.RASTER_RGB_CHANNEL_GREY_CONTRAST,
                                    FieldIdEnum.RASTER_RGB_CHANNEL_GREY_CONTRAST_GAMMA,
                                    GroupIdEnum.RASTER_RGB_CHANNEL_GREY_CONTRAST_METHOD);

                    channelSelection = getStyleFactory().channelSelection(greyChannel);
                } else {
                    SelectedChannelType redChannel =
                            extractContrastEnhancementGroup(
                                    GroupIdEnum.RASTER_RGB_CHANNEL_RED,
                                    GroupIdEnum.RASTER_RGB_CHANNEL_RED_CONTRAST,
                                    FieldIdEnum.RASTER_RGB_CHANNEL_RED_CONTRAST_GAMMA,
                                    GroupIdEnum.RASTER_RGB_CHANNEL_RED_CONTRAST_METHOD);

                    SelectedChannelType greenChannel =
                            extractContrastEnhancementGroup(
                                    GroupIdEnum.RASTER_RGB_CHANNEL_GREEN,
                                    GroupIdEnum.RASTER_RGB_CHANNEL_GREEN_CONTRAST,
                                    FieldIdEnum.RASTER_RGB_CHANNEL_GREEN_CONTRAST_GAMMA,
                                    GroupIdEnum.RASTER_RGB_CHANNEL_GREEN_CONTRAST_METHOD);

                    SelectedChannelType blueChannel =
                            extractContrastEnhancementGroup(
                                    GroupIdEnum.RASTER_RGB_CHANNEL_BLUE,
                                    GroupIdEnum.RASTER_RGB_CHANNEL_BLUE_CONTRAST,
                                    FieldIdEnum.RASTER_RGB_CHANNEL_BLUE_CONTRAST_GAMMA,
                                    GroupIdEnum.RASTER_RGB_CHANNEL_BLUE_CONTRAST_METHOD);

                    SelectedChannelType[] channels = new SelectedChannelType[3];
                    channels[0] = redChannel;
                    channels[1] = greenChannel;
                    channels[2] = blueChannel;
                    channelSelection = getStyleFactory().createChannelSelection(channels);
                }
            }
        }
        return channelSelection;
    }

    /**
     * Extract contrast enhancement group.
     *
     * @param channelGroup the channel group
     * @param contrastGroup the contrast group
     * @param gammaField the gamma field
     * @param contrastMethod the contrast method
     * @return the selected channel type
     */
    private SelectedChannelType extractContrastEnhancementGroup(
            GroupIdEnum channelGroup,
            GroupIdEnum contrastGroup,
            FieldIdEnum gammaField,
            GroupIdEnum contrastMethod) {

        SelectedChannelType channelType = null;

        GroupConfigInterface group = getGroup(channelGroup);

        if (group.isPanelEnabled()) {
            Expression channelName = this.vendorOptionRasterFactory.getChannelName(channelGroup);

            GroupConfigInterface contrastGrp = getGroup(contrastGroup);

            ContrastEnhancement contrastEnhancement = null;
            if (contrastGrp.isPanelEnabled()) {
                Expression gammaExpression = fieldConfigVisitor.getExpression(gammaField);

                GroupConfigInterface constrastMethodGroup = getGroup(contrastMethod);
                if (constrastMethodGroup != null) {
                    String method = null;
                    MultiOptionGroup constrastMethodGroup2 =
                            (MultiOptionGroup) constrastMethodGroup;
                    OptionGroup selectedOption = constrastMethodGroup2.getSelectedOptionGroup();
                    if (selectedOption != null) {
                        method = selectedOption.getLabel();
                    }

                    contrastEnhancement =
                            (ContrastEnhancement)
                                    getStyleFactory().contrastEnhancement(gammaExpression, method);
                }
            }

            channelType =
                    getStyleFactory().createSelectedChannelType(channelName, contrastEnhancement);
        }
        return channelType;
    }

    /**
     * Data changed.
     *
     * @param changedField the changed field
     */
    @Override
    public void dataChanged(FieldIdEnum changedField) {
        updateSymbol();
    }

    /**
     * Gets the field data manager.
     *
     * @return the field data manager
     */
    /*
     * (non-Javadoc)
     *
     * @see com.sldeditor.ui.iface.PopulateDetailsInterface#getFieldDataManager()
     */
    @Override
    public GraphicPanelFieldManager getFieldDataManager() {
        if (vendorOptionRasterFactory != null) {
            vendorOptionRasterFactory.updateFieldDataManager(fieldConfigManager);
        }

        return fieldConfigManager;
    }

    /**
     * Checks if is data present.
     *
     * @return true, if is data present
     */
    /*
     * (non-Javadoc)
     *
     * @see com.sldeditor.ui.iface.PopulateDetailsInterface#isDataPresent()
     */
    @Override
    public boolean isDataPresent() {
        return true;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sldeditor.ui.iface.PopulateDetailsInterface#initialseFields()
     */
    @Override
    public void preLoadSymbol() {
        setAllDefaultValues();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.sldeditor.ui.iface.PopulateDetailsInterface#getMinimumVersion(java.lang.Object,
     * java.util.List)
     */
    @Override
    public void getMinimumVersion(
            Object parentObj, Object sldObj, List<VendorOptionPresent> vendorOptionsPresentList) {
        vendorOptionRasterFactory.getMinimumVersion(parentObj, sldObj, vendorOptionsPresentList);
    }
}
