/**
 * Copyright © CENRA (2016)
 *
 * remi.clement@espaces-naturels.fr
 * 
 * This software is a multi-platform and portable application based on 
 * the scientifical toolbox to monitor wetlands. With this toolbox it’s 
 * possible to calculate indicators «RhoMéO» to monitor quality, 
 * fonctionalities and urban and agricultural pressures of wetlands. 
 * Indicators are calculated using naturalist data entered and/or imported,
 * and geographic data downloaded from a geographic server.
 * 
 * This software is governed by the CeCILL license under French law and
 * abiding by the rules of distribution of free software.  You can  use, 
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info". 
 * 
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability. 
 * 
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or 
 * data to be ensured and,  more generally, to use and operate it in the 
 * same conditions as regards security. 
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
*/
package fr.cenra.rhomeo.fx.edition;

import com.sun.javafx.PlatformUtil;
import fr.cenra.rhomeo.Rhomeo;
import fr.cenra.rhomeo.api.preferences.PreferenceGroup;
import fr.cenra.rhomeo.api.ui.PreferencePane;
import fr.cenra.rhomeo.core.CSVMapper;
import fr.cenra.rhomeo.core.CSVMappingBuilder;
import fr.cenra.rhomeo.preferences.csv.CSVKey;
import fr.cenra.rhomeo.preferences.csv.CSVPreferences;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.logging.Level;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import javafx.util.StringConverter;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
 * A panel to edit CSV format preferences for dataset import / export. You can
 * also acquire a {@link CSVMappingBuilder} from a configuration typed in a popup
 * by calling {@link #showConfigurationForm(javafx.stage.Window) }.
 * @author Alexis Manin (Geomatys)
 */
@Component
@Scope("prototype")
public class CSVConfigurationPane extends VBox implements PreferencePane<CSVKey> {

    private static final String INFO_TEXT = new StringBuilder("L'échappement des chaînes de caractère est optionnel. Le caractère d'échappement est \" et doit entourer la chaine complète.")
            .append(System.lineSeparator())
            .append("Pour utiliser le caractère \" dans les messages échappés, il doit être doublé.")
            .append(System.lineSeparator())
            .append("Ex : \"Ceci est un texte \"\"échappé\"\".\"")
            .toString();

    @FXML
    private ComboBox<Charset> uiEncoding;

    @FXML
    private TextField uiText;

    @FXML
    private CheckBox uiTab;

    @FXML
    private Label uiInfo;

    @FXML
    private Button uiCancel;

    @FXML
    private Button uiApply;

    @Autowired
    private CSVPreferences prefs;

    private CSVMappingBuilder builder;

    private CSVConfigurationPane() {
        super();
        Rhomeo.loadFXML(this);

        Charset defaultCharset;
        if (PlatformUtil.isWindows())
            try {
                defaultCharset = Charset.forName("windows-1252");
            } catch (Exception e) {
                Rhomeo.LOGGER.log(Level.FINE, "No charset found for windows-1252.", e);
                defaultCharset = StandardCharsets.ISO_8859_1;
            }
        else
            defaultCharset = StandardCharsets.UTF_8;

        Rhomeo.initComboBox(uiEncoding, FXCollections.observableArrayList(Charset.availableCharsets().values()).sorted(), defaultCharset);
        uiEncoding.setConverter(new CharsetStringConverter());

        uiText.disableProperty().bind(uiTab.selectedProperty());
        uiText.textProperty().addListener(this::textChanged);

        uiInfo.setGraphic(new ImageView(Rhomeo.ICON_INFO_BLUE));
        uiInfo.setText(INFO_TEXT);
    }

    @PostConstruct
    private void init() {
        init(prefs.getPreference(CSVPreferences.CHARSET_KEY), prefs.getPreference(CSVPreferences.SEPARATOR_KEY));
    }

    private void init(final String charset, final String separator) {
        // Restore default values from preferences.
        if (separator.equals("\t")) {
            uiTab.setSelected(true);
        } else {
            uiText.setText(separator);
        }

        if (charset != null)
            uiEncoding.setValue(Charset.forName(charset));
    }

    private void initButtons() {
        uiApply.setVisible(true);
        uiCancel.setVisible(true);

        uiApply.setOnAction(this::createBuilder);
        uiApply.disableProperty().bind(Bindings.createBooleanBinding(() ->
            uiText.getCharacters().length() != 1 && uiEncoding.getValue() == null
        , uiText.textProperty(), uiEncoding.valueProperty()));
        uiCancel.setCancelButton(true);
    }

    /**
     * Ensure that typed separator is a single character.
     *
     * @param obs
     * @param oldValue
     * @param newValue
     */
    private void textChanged(final ObservableValue<? extends String> obs, final String oldValue, final String newValue) {
        if (newValue != null && newValue.length() > 1) {
            uiText.setText(newValue.substring(newValue.length() -1));
        }
    }

    private void createBuilder(final ActionEvent e) {
        if (uiEncoding.getValue() == null || (!uiTab.isSelected() && uiText.getText() == null || uiText.getText().isEmpty()))
            return;

        final char separator = uiTab.isSelected()? '\t' : uiText.getText().charAt(uiText.getText().length() -1);
        builder = new CSVMappingBuilder()
                .withSeparator(separator)
                .withEncoding(uiEncoding.getValue());
    }

    /**
     * Show a dialog allowing user to configure the basic parameters for CSV
     * mapping (separator, encoding).
     *
     * /!\ MUST BE CALLED FROM FX-THREAD.
     *
     * @return A csv mapper builder if user confirmed its configuration, Nothing
     * if he cancelled.
     */
    public static Optional<CSVMappingBuilder> showConfigurationForm() {
        return showConfigurationForm(null);
    }

    /**
     * Show a dialog allowing user to configure the basic parameters for CSV
     * mapping (separator, encoding).
     *
     * /!\ MUST BE CALLED FROM FX-THREAD.
     *
     * @param owner The owner of the dialog to display. If the owner is null,
     * then displayed stage is application modal. Otherwise, the modality is
     * limited only on owner window.
     * @return A csv mapper builder if user confirmed its configuration, Nothing
     * if he cancelled.
     */
    public static Optional<CSVMappingBuilder> showConfigurationForm(final Window owner) {
        final CSVConfigurationPane content = new CSVConfigurationPane();
        content.init(StandardCharsets.UTF_8.name(), String.valueOf(CSVMapper.DEFAULT_SEPARATOR));
        content.initButtons();

        final Stage stage = new Stage(StageStyle.UTILITY);
        final EventHandler<ActionEvent> btnListener = (evt) -> stage.close();
        content.uiApply.addEventHandler(ActionEvent.ACTION, btnListener);
        content.uiCancel.addEventHandler(ActionEvent.ACTION, btnListener);

        if (owner != null) {
            stage.initModality(Modality.WINDOW_MODAL);
            stage.initOwner(owner);
        } else {
            stage.initModality(Modality.APPLICATION_MODAL);
        }

        stage.setScene(new Scene(content));
        stage.setTitle("Paramètres de lecture");
        stage.showAndWait();
        return Optional.ofNullable(content.builder);
    }

    @Override
    public void save() {
        if (uiText.getText() != null && !uiText.getText().isEmpty()) {
            final char separator = uiTab.isSelected() ? '\t' : uiText.getText().charAt(uiText.getText().length() - 1);
            prefs.setPreference(CSVPreferences.SEPARATOR_KEY, String.valueOf(separator));
        }
        if (uiEncoding.getValue() != null)
            prefs.setPreference(CSVPreferences.CHARSET_KEY, uiEncoding.getValue().name());

    }

    @Override
    public PreferenceGroup<CSVKey> getPreferenceGroup() {
        return prefs;
    }

    @Override
    public Node getEditor() {
        return this;
    }

    /**
     * A simple converter between string and charset types.
     */
    private static class CharsetStringConverter extends StringConverter<Charset> {

        @Override
        public String toString(Charset object) {
            if (object == null)
                return null;
            return object.displayName();
        }

        @Override
        public Charset fromString(String string) {
            if (string == null || (string = string.trim()).isEmpty())
                return null;
            return Charset.forName(string);
        }
    }
}
