package mara.mybox.controller;

import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TabPane;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Modality;
import mara.mybox.color.CIEColorSpace;
import mara.mybox.color.CIEData;
import mara.mybox.color.ChromaticityDiagram;
import mara.mybox.color.ChromaticityDiagram.DataType;
import mara.mybox.color.ColorValue;
import mara.mybox.color.SRGB;
import mara.mybox.data.VisitHistory;
import mara.mybox.fxml.FxmlColor;
import mara.mybox.fxml.FxmlControl;
import static mara.mybox.fxml.FxmlControl.badStyle;
import mara.mybox.fxml.FxmlImageManufacture;
import mara.mybox.fxml.FxmlStage;
import mara.mybox.fxml.RecentVisitMenu;
import mara.mybox.image.ImageValue;
import mara.mybox.image.file.ImageFileWriters;
import static mara.mybox.tools.DoubleTools.scale;
import mara.mybox.tools.FileTools;
import mara.mybox.tools.FloatTools;
import mara.mybox.value.AppVariables;
import static mara.mybox.value.AppVariables.logger;
import static mara.mybox.value.AppVariables.message;
import mara.mybox.value.CommonFxValues;

/**
 * @Author Mara
 * @CreateDate 2019-5-20
 * @Description
 * @License Apache License Version 2.0
 */
public class ChromaticityDiagramController extends ChromaticityBaseController {

    private boolean isLine, inputInit = true;
    private int dotSize, fontSize;
    private java.awt.Color bgColor, calculateColor;
    private final ObservableList<ColorValue> calculatedValues = FXCollections.observableArrayList();
    private ObservableList<CIEData> degree2nm1Data, degree10nm1Data, degree2nm5Data, degree10nm5Data;
    private double X, Y = 1, Z, x = 0.4, y = 0.5;

    @FXML
    private ComboBox<String> dotTypeBox, backgroundBox, fontSelector;
    @FXML
    private CheckBox cdProPhotoCheck, cdColorMatchCheck, cdNTSCCheck, cdPALCheck, cdAppleCheck, cdAdobeCheck,
            cdSRGBCheck, cdECICheck, cdCIECheck, cdSMPTECCheck, degree2Check, degree10Check,
            waveCheck, whitePointsCheck, gridCheck, calculateCheck, inputCheck;
    @FXML
    private ImageView cieDiagram;
    @FXML
    private ScrollPane cieDiagramScroll;
    @FXML
    private TextArea sourceInputArea, sourceDataArea,
            d2n1Area, d2n5Area, d10n1Area, d10n5Area;
    @FXML
    private SplitPane dataTablePane;
    @FXML
    private ToggleGroup dataGroup;
    @FXML
    private VBox tableBox, calculateBox, inputBox;
    @FXML
    private TableView<ColorValue> calculatedValuesTable;
    @FXML
    private TableColumn<ColorValue, String> colorSpaceColumn, conditionsColumn, valuesColumn;
    @FXML
    private TableView<CIEData> d2n1TableView, d2n5TableView, d10n1TableView, d10n5TableView, inputTableView;
    @FXML
    private TableColumn<CIEData, Integer> wave2n1Column, wave10n1Column, wave2n5Column, wave10n5Column;
    @FXML
    private TableColumn<CIEData, Double> tx2n1Column, ty2n1Column, tz2n1Column, nx2n1Column, ny2n1Column, nz2n1Column,
            rx2n1Column, ry2n1Column, rz2n1Column, r2n1Column, g2n1Column, b2n1Column,
            tx10n1Column, ty10n1Column, tz10n1Column, nx10n1Column, ny10n1Column, nz10n1Column,
            rx10n1Column, ry10n1Column, rz10n1Column, r10n1Column, g10n1Column, b10n1Column;
    @FXML
    private TableColumn<CIEData, Integer> ri2n1Column, gi2n1Column, bi2n1Column,
            ri10n1Column, gi10n1Column, bi10n1Column;
    @FXML
    private TableColumn<CIEData, Double> tx2n5Column, ty2n5Column, tz2n5Column, nx2n5Column, ny2n5Column, nz2n5Column,
            rx2n5Column, ry2n5Column, rz2n5Column, r2n5Column, g2n5Column, b2n5Column,
            tx10n5Column, ty10n5Column, tz10n5Column, nx10n5Column, ny10n5Column, nz10n5Column,
            rx10n5Column, ry10n5Column, rz10n5Column, r10n5Column, g10n5Column, b10n5Column;
    @FXML
    private TableColumn<CIEData, Integer> ri2n5Column, gi2n5Column, bi2n5Column,
            ri10n5Column, gi10n5Column, bi10n5Column;
    @FXML
    private ToolBar dataToolbar;
    @FXML
    private TextField XInput, YInput, ZInput, xInput, yInput;
    @FXML
    private Button calculateXYZButton, calculateXYButton, displayDataButton;
    @FXML
    private TabPane csPane, filePane;
    @FXML
    protected Rectangle colorRect;
    @FXML
    protected Button paletteButton;

    public ChromaticityDiagramController() {
        baseTitle = AppVariables.message("DrawChromaticityDiagram");

        TipsLabelKey = "ChromaticityDiagramTips";

        SourceFileType = VisitHistory.FileType.Text;
        SourcePathType = VisitHistory.FileType.Text;
        TargetPathType = VisitHistory.FileType.Text;
        TargetFileType = VisitHistory.FileType.Text;
        AddFileType = VisitHistory.FileType.Text;
        AddPathType = VisitHistory.FileType.Text;

        sourceExtensionFilter = CommonFxValues.TextExtensionFilter;
        targetExtensionFilter = sourceExtensionFilter;
    }

    @Override
    public void initControls() {
        try {
            super.initControls();

            initToolBar();
            initDiagram();
            initDataBox();
            initCIETables();

            isSettingValues = true;
            backgroundBox.getSelectionModel().select(0);
            dotTypeBox.getSelectionModel().select(0);
            fontSelector.getSelectionModel().select(0);
            isSettingValues = false;
        } catch (Exception e) {
            logger.error(e.toString());
        }

    }

    @Override
    public void initializeNext() {
        try {
            super.initializeNext();

            initCIEData();

        } catch (Exception e) {
            logger.error(e.toString());
        }

    }

    private void initToolBar() {
        List<String> bgList = Arrays.asList(message("Transparent"),
                message("White"), message("Black")
        );
        backgroundBox.getItems().addAll(bgList);
        backgroundBox.setVisibleRowCount(bgList.size());
        backgroundBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable,
                    String oldValue, String newValue) {
                checkBackground();
            }
        });

        List<String> opList = Arrays.asList(message("Line4px"),
                message("Dot6px"), message("Dot10px"), message("Dot4px"),
                message("Dot12px"), message("Line1px"), message("Line2px"),
                message("Line6px"), message("Line10px")
        );
        dotTypeBox.getItems().addAll(opList);
        dotTypeBox.setVisibleRowCount(opList.size());
        dotTypeBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable,
                    String oldValue, String newValue) {
                checkDotType();
            }
        });

        List<String> fontList = Arrays.asList("20", "24", "28", "30", "18", "16", "15", "14", "12", "10");
        fontSelector.getItems().addAll(fontList);
        fontSelector.setVisibleRowCount(fontList.size());
        fontSelector.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable,
                    String oldValue, String newValue) {
                checkFontSize();
            }
        });

        calculateCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                if (!isSettingValues) {
                    displayChromaticityDiagram();
                }
            }
        });
        inputCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        waveCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        gridCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        whitePointsCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        degree2Check.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        degree10Check.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        cdProPhotoCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        cdColorMatchCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        cdNTSCCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        cdPALCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        cdAppleCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        cdAdobeCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        cdSRGBCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        cdECICheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        cdCIECheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });
        cdSMPTECCheck.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> ov,
                    Boolean old_val, Boolean new_val) {
                displayChromaticityDiagram();
            }
        });

    }

    private void initDiagram() {
        cieDiagram.fitWidthProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> ov,
                    Number old_val, Number new_val) {
                if (Math.abs(new_val.intValue() - old_val.intValue()) > 20) {
                    refinePane();
                }
            }
        });
        cieDiagram.fitHeightProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> ov,
                    Number old_val, Number new_val) {
                if (Math.abs(new_val.intValue() - old_val.intValue()) > 20) {
                    refinePane();
                }
            }
        });
        cieDiagramScroll.widthProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> ov,
                    Number old_val, Number new_val) {
                if (Math.abs(new_val.intValue() - old_val.intValue()) > 20) {
                    refinePane();
                }
            }
        });

    }

    private void initDataBox() {
        try {
            dataGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
                @Override
                public void changed(ObservableValue<? extends Toggle> observable,
                        Toggle oldValue, Toggle newValue) {
                    loadTableData();
                }
            });

            XInput.textProperty().addListener(new ChangeListener<String>() {
                @Override
                public void changed(ObservableValue<? extends String> observable,
                        String oldValue, String newValue) {
                    try {
                        X = Double.parseDouble(newValue);
                        XInput.setStyle(null);
                    } catch (Exception e) {
                        XInput.setStyle(badStyle);
                    }
                }
            });
            YInput.textProperty().addListener(new ChangeListener<String>() {
                @Override
                public void changed(ObservableValue<? extends String> observable,
                        String oldValue, String newValue) {
                    try {
                        Y = Double.parseDouble(newValue);
                        if (Y == 0) {
                            YInput.setStyle(badStyle);
                        } else {
                            YInput.setStyle(null);
                        }
                    } catch (Exception e) {
                        YInput.setStyle(badStyle);
                    }
                }
            });
            ZInput.textProperty().addListener(new ChangeListener<String>() {
                @Override
                public void changed(ObservableValue<? extends String> observable,
                        String oldValue, String newValue) {
                    try {
                        Z = Double.parseDouble(newValue);
                        ZInput.setStyle(null);
                    } catch (Exception e) {
                        ZInput.setStyle(badStyle);
                    }
                }
            });
            FxmlControl.setTooltip(YInput, new Tooltip(message("1-based")));

            xInput.textProperty().addListener(new ChangeListener<String>() {
                @Override
                public void changed(ObservableValue<? extends String> observable,
                        String oldValue, String newValue) {
                    try {
                        x = Double.parseDouble(newValue);
                        double z = 1 - x - y;
                        if (x > 1 || x < 0 || z < 0 || z > 1) {
                            xInput.setStyle(badStyle);
                        } else {
                            xInput.setStyle(null);
                        }
                    } catch (Exception e) {
                        xInput.setStyle(badStyle);
                    }
                }
            });
            yInput.textProperty().addListener(new ChangeListener<String>() {
                @Override
                public void changed(ObservableValue<? extends String> observable,
                        String oldValue, String newValue) {
                    try {
                        y = Double.parseDouble(newValue);
                        double z = 1 - x - y;
                        if (y > 1 || y <= 0 || z < 0 || z > 1) {
                            yInput.setStyle(badStyle);
                        } else {
                            yInput.setStyle(null);
                        }
                    } catch (Exception e) {
                        yInput.setStyle(badStyle);
                    }
                }
            });

            calculateXYZButton.disableProperty().bind(Bindings.isEmpty(XInput.textProperty())
                    .or(XInput.styleProperty().isEqualTo(badStyle))
                    .or(Bindings.isEmpty(YInput.textProperty()))
                    .or(YInput.styleProperty().isEqualTo(badStyle))
                    .or(Bindings.isEmpty(ZInput.textProperty()))
                    .or(ZInput.styleProperty().isEqualTo(badStyle))
            );

            calculateXYButton.disableProperty().bind(Bindings.isEmpty(xInput.textProperty())
                    .or(xInput.styleProperty().isEqualTo(badStyle))
                    .or(Bindings.isEmpty(yInput.textProperty()))
                    .or(yInput.styleProperty().isEqualTo(badStyle))
            );

            displayDataButton.disableProperty().bind(Bindings.isEmpty(sourceDataArea.textProperty())
            );

            colorSpaceColumn.setCellValueFactory(new PropertyValueFactory<>("colorSpace"));
            conditionsColumn.setCellValueFactory(new PropertyValueFactory<>("conditions"));
            valuesColumn.setCellValueFactory(new PropertyValueFactory<>("values"));

            sourceInputArea.setStyle(" -fx-text-fill: gray;");
            sourceInputArea.setText(message("ChromaticityDiagramTips"));
            sourceInputArea.focusedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(
                        ObservableValue<? extends Boolean> observable,
                        Boolean oldValue, Boolean newValue) {
                    if (inputInit) {
                        sourceInputArea.clear();
                        sourceInputArea.setStyle(null);
                        inputInit = false;
                    }
                }
            });

            sourceInputArea.textProperty().addListener(new ChangeListener<String>() {
                @Override
                public void changed(ObservableValue<? extends String> observable,
                        String oldValue, String newValue) {
                    if (isSettingValues) {
                        return;
                    }
                    checkInputs();
                }
            });

        } catch (Exception e) {
            logger.error(e.toString());
        }
    }

    @FXML
    @Override
    public void showPalette(ActionEvent event) {
        showPalette(paletteButton, message("DrawChromaticityDiagram"));
    }

    @Override
    public boolean setColor(Control control, Color color) {
        if (control == null || color == null) {
            return false;
        }
        if (paletteButton.equals(control)) {
            colorRect.setFill(color);
            FxmlControl.setTooltip(colorRect, FxmlColor.colorNameDisplay(color));
            calculateColor();
        }
        return true;
    }

    private void checkInputs() {
        String data = CIEData.cieString(sourceInputArea.getText());
        if (data != null) {
            sourceDataArea.setText(data);
        } else {
            popError(AppVariables.message("NoData"));
            sourceDataArea.clear();
        }
    }

    private void initCIETables() {
        wave2n1Column.setCellValueFactory(new PropertyValueFactory<>("waveLength"));
        tx2n1Column.setCellValueFactory(new PropertyValueFactory<>("X"));
        ty2n1Column.setCellValueFactory(new PropertyValueFactory<>("Y"));
        tz2n1Column.setCellValueFactory(new PropertyValueFactory<>("Z"));
        nx2n1Column.setCellValueFactory(new PropertyValueFactory<>("normalizedX"));
        ny2n1Column.setCellValueFactory(new PropertyValueFactory<>("normalizedY"));
        nz2n1Column.setCellValueFactory(new PropertyValueFactory<>("normalizedZ"));
        rx2n1Column.setCellValueFactory(new PropertyValueFactory<>("relativeX"));
        ry2n1Column.setCellValueFactory(new PropertyValueFactory<>("relativeY"));
        rz2n1Column.setCellValueFactory(new PropertyValueFactory<>("relativeZ"));
        r2n1Column.setCellValueFactory(new PropertyValueFactory<>("red"));
        g2n1Column.setCellValueFactory(new PropertyValueFactory<>("green"));
        b2n1Column.setCellValueFactory(new PropertyValueFactory<>("blue"));
        ri2n1Column.setCellValueFactory(new PropertyValueFactory<>("redi"));
        gi2n1Column.setCellValueFactory(new PropertyValueFactory<>("greeni"));
        bi2n1Column.setCellValueFactory(new PropertyValueFactory<>("bluei"));

        wave2n5Column.setCellValueFactory(new PropertyValueFactory<>("waveLength"));
        tx2n5Column.setCellValueFactory(new PropertyValueFactory<>("X"));
        ty2n5Column.setCellValueFactory(new PropertyValueFactory<>("Y"));
        tz2n5Column.setCellValueFactory(new PropertyValueFactory<>("Z"));
        nx2n5Column.setCellValueFactory(new PropertyValueFactory<>("normalizedX"));
        ny2n5Column.setCellValueFactory(new PropertyValueFactory<>("normalizedY"));
        nz2n5Column.setCellValueFactory(new PropertyValueFactory<>("normalizedZ"));
        rx2n5Column.setCellValueFactory(new PropertyValueFactory<>("relativeX"));
        ry2n5Column.setCellValueFactory(new PropertyValueFactory<>("relativeY"));
        rz2n5Column.setCellValueFactory(new PropertyValueFactory<>("relativeZ"));
        r2n5Column.setCellValueFactory(new PropertyValueFactory<>("red"));
        g2n5Column.setCellValueFactory(new PropertyValueFactory<>("green"));
        b2n5Column.setCellValueFactory(new PropertyValueFactory<>("blue"));
        ri2n5Column.setCellValueFactory(new PropertyValueFactory<>("redi"));
        gi2n5Column.setCellValueFactory(new PropertyValueFactory<>("greeni"));
        bi2n5Column.setCellValueFactory(new PropertyValueFactory<>("bluei"));

        wave10n1Column.setCellValueFactory(new PropertyValueFactory<>("waveLength"));
        tx10n1Column.setCellValueFactory(new PropertyValueFactory<>("X"));
        ty10n1Column.setCellValueFactory(new PropertyValueFactory<>("Y"));
        tz10n1Column.setCellValueFactory(new PropertyValueFactory<>("Z"));
        nx10n1Column.setCellValueFactory(new PropertyValueFactory<>("normalizedX"));
        ny10n1Column.setCellValueFactory(new PropertyValueFactory<>("normalizedY"));
        nz10n1Column.setCellValueFactory(new PropertyValueFactory<>("normalizedZ"));
        rx10n1Column.setCellValueFactory(new PropertyValueFactory<>("relativeX"));
        ry10n1Column.setCellValueFactory(new PropertyValueFactory<>("relativeY"));
        rz10n1Column.setCellValueFactory(new PropertyValueFactory<>("relativeZ"));
        r10n1Column.setCellValueFactory(new PropertyValueFactory<>("red"));
        g10n1Column.setCellValueFactory(new PropertyValueFactory<>("green"));
        b10n1Column.setCellValueFactory(new PropertyValueFactory<>("blue"));
        ri10n1Column.setCellValueFactory(new PropertyValueFactory<>("redi"));
        gi10n1Column.setCellValueFactory(new PropertyValueFactory<>("greeni"));
        bi10n1Column.setCellValueFactory(new PropertyValueFactory<>("bluei"));

        wave10n5Column.setCellValueFactory(new PropertyValueFactory<>("waveLength"));
        tx10n5Column.setCellValueFactory(new PropertyValueFactory<>("X"));
        ty10n5Column.setCellValueFactory(new PropertyValueFactory<>("Y"));
        tz10n5Column.setCellValueFactory(new PropertyValueFactory<>("Z"));
        nx10n5Column.setCellValueFactory(new PropertyValueFactory<>("normalizedX"));
        ny10n5Column.setCellValueFactory(new PropertyValueFactory<>("normalizedY"));
        nz10n5Column.setCellValueFactory(new PropertyValueFactory<>("normalizedZ"));
        rx10n5Column.setCellValueFactory(new PropertyValueFactory<>("relativeX"));
        ry10n5Column.setCellValueFactory(new PropertyValueFactory<>("relativeY"));
        rz10n5Column.setCellValueFactory(new PropertyValueFactory<>("relativeZ"));
        r10n5Column.setCellValueFactory(new PropertyValueFactory<>("red"));
        g10n5Column.setCellValueFactory(new PropertyValueFactory<>("green"));
        b10n5Column.setCellValueFactory(new PropertyValueFactory<>("blue"));
        ri10n5Column.setCellValueFactory(new PropertyValueFactory<>("redi"));
        gi10n5Column.setCellValueFactory(new PropertyValueFactory<>("greeni"));
        bi10n5Column.setCellValueFactory(new PropertyValueFactory<>("bluei"));

    }

    private void refinePane() {
        if (cieDiagram.getImage() == null) {
            return;
        }
        FxmlControl.paneSize(cieDiagramScroll, cieDiagram);
        cieDiagramScroll.setVvalue(cieDiagramScroll.getVmin());

    }

    private void checkDotType() {
        try {
            isLine = false;
            String type = dotTypeBox.getSelectionModel().getSelectedItem();
            if (message("Dot6px").equals(type)) {
                dotSize = 6;
            } else if (message("Dot10px").equals(type)) {
                dotSize = 10;
            } else if (message("Dot4px").equals(type)) {
                dotSize = 4;
            } else if (message("Dot12px").equals(type)) {
                dotSize = 12;
            } else if (message("Line2px").equals(type)) {
                isLine = true;
                dotSize = 2;
            } else if (message("Line1px").equals(type)) {
                isLine = true;
                dotSize = 1;
            } else if (message("Line4px").equals(type)) {
                isLine = true;
                dotSize = 4;
            } else if (message("Line6px").equals(type)) {
                isLine = true;
                dotSize = 6;
            } else if (message("Line10px").equals(type)) {
                isLine = true;
                dotSize = 10;
            } else {
                dotSize = 6;
            }
        } catch (Exception e) {
            dotSize = 6;
        }
        if (!isSettingValues) {
            displayChromaticityDiagram();
        }

    }

    private void checkBackground() {
        try {
            String type = backgroundBox.getSelectionModel().getSelectedItem();
            if (message("Transparent").equals(type)) {
                bgColor = null;
            } else if (message("White").equals(type)) {
                bgColor = java.awt.Color.WHITE;
            } else if (message("Black").equals(type)) {
                bgColor = java.awt.Color.BLACK;
            } else {
                bgColor = null;
            }
        } catch (Exception e) {
            bgColor = null;
        }
        if (!isSettingValues) {
            displayChromaticityDiagram();
        }

    }

    private void checkFontSize() {
        try {
            int v = Integer.parseInt(fontSelector.getValue());
            if (v > 0) {
                fontSize = v;
                fontSelector.getEditor().setStyle(null);
                if (!isSettingValues) {
                    displayChromaticityDiagram();
                }
            } else {
                fontSelector.getEditor().setStyle(badStyle);
            }
        } catch (Exception e) {
            fontSelector.getEditor().setStyle(badStyle);
        }
    }

    private void initCIEData() {
        synchronized (this) {
            if (task != null) {
                return;
            }
            task = new SingletonTask<Void>() {
                private String degree2nm1String, degree10nm1String,
                        degree2nm5String, degree10nm5String;

                @Override
                protected boolean handle() {
                    CIEData cieData = new CIEData();
                    ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);

                    degree2nm1Data = FXCollections.observableArrayList();
                    degree2nm1Data.addAll(cieData.cie1931Observer2Degree1nmData(cs));
                    degree2nm1String = cieData.cie1931Observer2Degree1nmString(cs);

                    degree10nm1Data = FXCollections.observableArrayList();
                    degree10nm1Data.addAll(cieData.cie1964Observer10Degree1nmData(cs));
                    degree10nm1String = cieData.cie1964Observer10Degree1nmString(cs);

                    degree2nm5Data = FXCollections.observableArrayList();
                    degree2nm5Data.addAll(cieData.cie1931Observer2Degree5nmData(cs));
                    degree2nm5String = cieData.cie1931Observer2Degree5nmString(cs);

                    degree10nm5Data = FXCollections.observableArrayList();
                    degree10nm5Data.addAll(cieData.cie1964Observer10Degree5nmData(cs));
                    degree10nm5String = cieData.cie1964Observer10Degree5nmString(cs);

                    return true;
                }

                @Override
                protected void whenSucceeded() {
                    d2n1TableView.setItems(degree2nm1Data);
                    d2n1Area.setText(degree2nm1String);

                    d10n1TableView.setItems(degree10nm1Data);
                    d10n1Area.setText(degree10nm1String);

                    d2n5TableView.setItems(degree2nm5Data);
                    d2n5Area.setText(degree2nm5String);

                    d10n5TableView.setItems(degree10nm5Data);
                    d10n5Area.setText(degree10nm5String);

                    setColor(paletteButton, Color.THISTLE);

                    loadTableData();
                }

            };
            openHandlingStage(task, Modality.WINDOW_MODAL);
            Thread thread = new Thread(task);
            thread.setDaemon(true);
            thread.start();
        }

    }

    private void displayChromaticityDiagram() {
        if (isSettingValues) {
            return;
        }
        synchronized (this) {
            if (task != null) {
                return;
            }
            task = new SingletonTask<Void>() {
                private Image image;

                @Override
                protected boolean handle() {
                    try {
                        LinkedHashMap<ChromaticityDiagram.DataType, Boolean> selections = new LinkedHashMap();
                        selections.put(DataType.CIE2Degree, degree2Check.isSelected());
                        selections.put(DataType.CIE10Degree, degree10Check.isSelected());
                        selections.put(DataType.CIEDataSource, inputCheck.isSelected());
                        selections.put(DataType.Calculate, calculateCheck.isSelected());
                        selections.put(DataType.Wave, waveCheck.isSelected());
                        selections.put(DataType.WhitePoints, whitePointsCheck.isSelected());
                        selections.put(DataType.Grid, gridCheck.isSelected());
                        selections.put(DataType.CIELines, cdCIECheck.isSelected());
                        selections.put(DataType.ECILines, cdECICheck.isSelected());
                        selections.put(DataType.sRGBLines, cdSRGBCheck.isSelected());
                        selections.put(DataType.AdobeLines, cdAdobeCheck.isSelected());
                        selections.put(DataType.AppleLines, cdAppleCheck.isSelected());
                        selections.put(DataType.PALLines, cdPALCheck.isSelected());
                        selections.put(DataType.NTSCLines, cdNTSCCheck.isSelected());
                        selections.put(DataType.ColorMatchLines, cdColorMatchCheck.isSelected());
                        selections.put(DataType.ProPhotoLines, cdProPhotoCheck.isSelected());
                        selections.put(DataType.SMPTECLines, cdSMPTECCheck.isSelected());

                        ChromaticityDiagram cd = new ChromaticityDiagram();
                        cd.setIsLine(isLine);
                        cd.setDotSize(dotSize);
                        cd.setBgColor(bgColor);
                        cd.setFontSize(fontSize);
                        cd.setDataSourceTexts(sourceDataArea.getText());
                        if (x >= 0 && x <= 1 && y > 0 && y <= 1) {
                            cd.setCalculateX(x);
                            cd.setCalculateY(y);
                            cd.setCalculateColor(calculateColor);
                        }
                        image = SwingFXUtils.toFXImage(cd.drawData(selections), null);

                    } catch (Exception e) {
                        error = e.toString();
                    }
                    return image != null;
                }

                @Override
                protected void whenSucceeded() {
                    cieDiagram.setImage(image);
                    FxmlControl.paneSize(cieDiagramScroll, cieDiagram);
                    d2n1Area.home();
                    d2n5Area.home();
                    d10n1Area.home();
                    d10n5Area.home();
                }

            };
            openHandlingStage(task, Modality.WINDOW_MODAL);
            Thread thread = new Thread(task);
            thread.setDaemon(true);
            thread.start();
        }

    }

    private void loadTableData() {
        try {
            tableBox.getChildren().clear();
            RadioButton selected = (RadioButton) dataGroup.getSelectedToggle();
            if (message("Calculate").equals(selected.getText())) {

                tableBox.getChildren().addAll(dataToolbar, calculateBox);

            } else if (message("Input").equals(selected.getText())) {

                tableBox.getChildren().addAll(dataToolbar, inputBox);

            }

            FxmlControl.refreshStyle(tableBox);
        } catch (Exception e) {
            logger.error(e.toString());
        }
    }

    @Override
    public void keyEventsHandler(KeyEvent event) {
        super.keyEventsHandler(event);
        if (event.isControlDown()) {
            String key = event.getText();
            if (key == null || key.isEmpty()) {
                return;
            }
            switch (key) {
                case "2":
                    paneSizeDiagram();
                    break;
                case "3":
                    zoomInDiagram();
                    break;
                case "4":
                    zoomOutDiagram();
                    break;
            }

        }
    }

    @Override
    public void sourceFileChanged(final File file) {
        if (file == null) {
            return;
        }
        synchronized (this) {
            if (task != null) {
                return;
            }
            task = new SingletonTask<Void>() {
                private String texts;

                @Override
                protected boolean handle() {
                    texts = FileTools.readTexts(file);
                    return texts != null;
                }

                @Override
                protected void whenSucceeded() {
                    sourceInputArea.setStyle(null);
                    inputInit = false;
//                            bottomLabel.setText(file.getAbsolutePath() + "\t" + AppVariables.getMessage("ChromaticityDiagramComments"));
                    isSettingValues = true;
                    sourceInputArea.setText(texts);
                    sourceInputArea.home();
                    isSettingValues = false;
                    checkInputs();
                }

                @Override
                protected void whenFailed() {
                    popError(AppVariables.message("NoData"));
                    sourceDataArea.clear();
                }

            };
            openHandlingStage(task, Modality.WINDOW_MODAL);
            Thread thread = new Thread(task);
            thread.setDaemon(true);
            thread.start();
        }
    }

    @FXML
    public void displayDataAction() {
        if (sourceDataArea.getText().isEmpty()) {
            return;
        }
        isSettingValues = true;
        inputCheck.setSelected(true);
        isSettingValues = false;
        displayChromaticityDiagram();
    }

    @FXML
    public void popDiagramPath(MouseEvent event) {
        if (AppVariables.fileRecentNumber <= 0) {
            return;
        }
        new RecentVisitMenu(this, event) {
            @Override
            public List<VisitHistory> recentFiles() {
                return null;
            }

            @Override
            public List<VisitHistory> recentPaths() {
                return VisitHistory.getRecentPath(VisitHistory.FileType.Image);
            }

            @Override
            public void handleSelect() {
                saveAction();
            }

            @Override
            public void handleFile(String fname) {

            }

            @Override
            public void handlePath(String fname) {
                handleTargetPath(fname);
            }

        }.pop();
    }

    @FXML
    public void pop21Path(MouseEvent event) {
        if (AppVariables.fileRecentNumber <= 0) {
            return;
        }
        new RecentVisitMenu(this, event) {
            @Override
            public List<VisitHistory> recentFiles() {
                return null;
            }

            @Override
            public List<VisitHistory> recentPaths() {
                return VisitHistory.getRecentPath(VisitHistory.FileType.Text);
            }

            @Override
            public void handleSelect() {
                export21Action();
            }

            @Override
            public void handleFile(String fname) {

            }

            @Override
            public void handlePath(String fname) {
                handleTargetPath(fname);
            }

        }.pop();
    }

    @FXML
    public void pop25Path(MouseEvent event) {
        if (AppVariables.fileRecentNumber <= 0) {
            return;
        }
        new RecentVisitMenu(this, event) {
            @Override
            public List<VisitHistory> recentFiles() {
                return null;
            }

            @Override
            public List<VisitHistory> recentPaths() {
                return VisitHistory.getRecentPath(VisitHistory.FileType.Text);
            }

            @Override
            public void handleSelect() {
                export25Action();
            }

            @Override
            public void handleFile(String fname) {

            }

            @Override
            public void handlePath(String fname) {
                handleTargetPath(fname);
            }

        }.pop();
    }

    @FXML
    public void pop101Path(MouseEvent event) {
        if (AppVariables.fileRecentNumber <= 0) {
            return;
        }
        new RecentVisitMenu(this, event) {
            @Override
            public List<VisitHistory> recentFiles() {
                return null;
            }

            @Override
            public List<VisitHistory> recentPaths() {
                return VisitHistory.getRecentPath(VisitHistory.FileType.Text);
            }

            @Override
            public void handleSelect() {
                export101Action();
            }

            @Override
            public void handleFile(String fname) {

            }

            @Override
            public void handlePath(String fname) {
                handleTargetPath(fname);
            }

        }.pop();
    }

    @FXML
    public void pop105Path(MouseEvent event) {
        if (AppVariables.fileRecentNumber <= 0) {
            return;
        }
        new RecentVisitMenu(this, event) {
            @Override
            public List<VisitHistory> recentFiles() {
                return null;
            }

            @Override
            public List<VisitHistory> recentPaths() {
                return VisitHistory.getRecentPath(VisitHistory.FileType.Text);
            }

            @Override
            public void handleSelect() {
                export105Action();
            }

            @Override
            public void handleFile(String fname) {

            }

            @Override
            public void handlePath(String fname) {
                handleTargetPath(fname);
            }

        }.pop();
    }

    @FXML
    public void zoomInDiagram() {
        FxmlControl.zoomIn(cieDiagramScroll, cieDiagram, 20, 20);
    }

    @FXML
    public void zoomOutDiagram() {
        FxmlControl.zoomOut(cieDiagramScroll, cieDiagram, 20, 20);
    }

    @FXML
    public void paneSizeDiagram() {
        FxmlControl.paneSize(cieDiagramScroll, cieDiagram);
    }

    @FXML
    @Override
    public void clearAction() {
        isSettingValues = true;

        cdProPhotoCheck.setSelected(false);
        cdColorMatchCheck.setSelected(false);
        cdNTSCCheck.setSelected(false);
        cdPALCheck.setSelected(false);
        cdAppleCheck.setSelected(false);
        cdAdobeCheck.setSelected(false);
        cdSRGBCheck.setSelected(false);
        cdECICheck.setSelected(false);
        cdCIECheck.setSelected(false);
        cdSMPTECCheck.setSelected(false);
        degree2Check.setSelected(false);
        degree10Check.setSelected(false);
        waveCheck.setSelected(false);
        inputCheck.setSelected(false);
        calculateCheck.setSelected(false);
        whitePointsCheck.setSelected(false);

        isSettingValues = false;

        displayChromaticityDiagram();
    }

    @FXML
    @Override
    public void allAction() {
        isSettingValues = true;

        cdProPhotoCheck.setSelected(true);
        cdColorMatchCheck.setSelected(true);
        cdNTSCCheck.setSelected(true);
        cdPALCheck.setSelected(true);
        cdAppleCheck.setSelected(true);
        cdAdobeCheck.setSelected(true);
        cdSRGBCheck.setSelected(true);
        cdECICheck.setSelected(true);
        cdCIECheck.setSelected(true);
        cdSMPTECCheck.setSelected(true);
        degree2Check.setSelected(true);
        degree10Check.setSelected(true);
        waveCheck.setSelected(true);
        inputCheck.setSelected(true);
        calculateCheck.setSelected(true);
        whitePointsCheck.setSelected(true);

        isSettingValues = false;

        displayChromaticityDiagram();
    }

    protected void calculateColor() {
        CIEData d = new CIEData((Color) colorRect.getFill());
        isSettingValues = true;
        XInput.setText(scale(d.getX(), 8) + "");
        YInput.setText(scale(d.getY(), 8) + "");
        ZInput.setText(scale(d.getZ(), 8) + "");
        calculateXYZAction();
        isSettingValues = false;
        if (calculateCheck.isSelected()) {
            displayChromaticityDiagram();
        }
    }

    @FXML
    public void calculateXYZAction() {
        CIEData d = new CIEData(-1, X, Y, Z);
        xInput.setText(scale(d.getNormalizedX(), 8) + "");
        yInput.setText(scale(d.getNormalizedY(), 8) + "");
        displayCalculatedValued();
    }

    @FXML
    public void calculateXYAction() {
        CIEData d = new CIEData(x, y);
        XInput.setText(scale(d.getX(), 8) + "");
        YInput.setText(scale(d.getY(), 8) + "");
        ZInput.setText(scale(d.getZ(), 8) + "");
        displayCalculatedValued();
    }

    private void displayCalculatedValued() {
        if (x >= 0 && x <= 1 && y > 0 && y <= 1
                && (x + y) <= 1) {
            double[] srgb = CIEColorSpace.XYZd50toSRGBd65(X, Y, Z);
            if (!isSettingValues) {
                isSettingValues = true;
                Color pColor = new Color((float) srgb[0], (float) srgb[1], (float) srgb[2], 1d);
                colorRect.setFill(pColor);
                isSettingValues = false;
            }
            Color pColor = (Color) colorRect.getFill();
            calculateColor = new java.awt.Color((float) pColor.getRed(), (float) pColor.getGreen(), (float) pColor.getBlue());

            List<ColorValue> values = new ArrayList<>();
            double[] cieLab = CIEColorSpace.XYZd50toCIELab(X, Y, Z);
            values.add(new ColorValue("CIE-L*ab", "D50", cieLab));

            double[] LCHab = CIEColorSpace.LabtoLCHab(cieLab);
            values.add(new ColorValue("LCH(ab)", "D50", LCHab));

            double[] cieLuv = CIEColorSpace.XYZd50toCIELuv(X, Y, Z);
            values.add(new ColorValue("CIE-L*uv", "D50", cieLuv));

            double[] LCHuv = CIEColorSpace.LuvtoLCHuv(cieLuv);
            values.add(new ColorValue("LCH(uv)", "D50", LCHuv));

            double[] hsb = {pColor.getHue(), pColor.getSaturation(),
                pColor.getBrightness()};
            values.add(new ColorValue("HSB", "D65", hsb));

            values.add(new ColorValue("sRGB", "D65 sRGB_Gamma", srgb, 255));
            double[] sRGBLinear = CIEColorSpace.XYZd50toSRGBd65Linear(X, Y, Z);
            values.add(new ColorValue("sRGB", "D65 Linear", sRGBLinear, 255));

            double[] adobeRGB = CIEColorSpace.XYZd50toAdobeRGBd65(X, Y, Z);
            values.add(new ColorValue("Adobe RGB", "D65 Gamma 2.2", adobeRGB, 255));

            double[] adobeRGBLinear = CIEColorSpace.XYZd50toAdobeRGBd65Linear(X, Y, Z);
            values.add(new ColorValue("Adobe RGB", "D65 Linear", adobeRGBLinear, 255));

            double[] appleRGB = CIEColorSpace.XYZd50toAppleRGBd65(X, Y, Z);
            values.add(new ColorValue("Apple RGB", "D65 Gamma 1.8", appleRGB, 255));

            double[] appleRGBLinear = CIEColorSpace.XYZd50toAppleRGBd65Linear(X, Y, Z);
            values.add(new ColorValue("Apple RGB", "D65 Linear", appleRGBLinear, 255));

            float[] eciRGB = SRGB.srgb2profile(ImageValue.eciRGBProfile(), pColor);
            values.add(new ColorValue("ECI RGB", "D50", FloatTools.toDouble(eciRGB), 255));

            double[] cmyk = SRGB.rgb2cmyk(pColor);
            values.add(new ColorValue("Calculated CMYK", "D65 sRGB_Gamma", cmyk, 100));

            float[] cmyk2 = SRGB.srgb2profile(ImageValue.eciCmykProfile(), pColor);
            values.add(new ColorValue("ECI CMYK", "D65 sRGB_Gamma", FloatTools.toDouble(cmyk2), 100));

            cmyk2 = SRGB.srgb2profile(ImageValue.adobeCmykProfile(), pColor);
            values.add(new ColorValue("Adobe CMYK Uncoated FOGRA29", "D65 sRGB_Gamma", FloatTools.toDouble(cmyk2), 100));

            calculatedValues.clear();
            calculatedValues.addAll(values);
            calculatedValuesTable.setItems(calculatedValues);
            calculatedValuesTable.refresh();

            if (calculateCheck.isSelected()) {
                displayChromaticityDiagram();
            }

        } else {
            calculateColor = null;
            calculatedValues.clear();
            calculatedValuesTable.refresh();
        }

    }

    @FXML
    @Override
    public void saveAction() {
        final File file = chooseSaveFile(AppVariables.getUserConfigPath(targetPathKey),
                "ChromaticityDiagram", CommonFxValues.ImageExtensionFilter, true);
        if (file == null) {
            return;
        }
        recordFileWritten(file, targetPathKey, VisitHistory.FileType.Image, VisitHistory.FileType.Image);

        synchronized (this) {
            if (task != null) {
                return;
            }
            task = new SingletonTask<Void>() {

                @Override
                protected boolean handle() {
                    String format = FileTools.getFileSuffix(file.getName());
                    final BufferedImage bufferedImage = FxmlImageManufacture.getBufferedImage(cieDiagram.getImage());
                    if (this == null || this.isCancelled()) {
                        return false;
                    }
                    ImageFileWriters.writeImageFile(bufferedImage, format, file.getAbsolutePath());
                    return true;
                }

                @Override
                protected void whenSucceeded() {
                    FxmlStage.openImageViewer(null, file);
                }

            };
            openHandlingStage(task, Modality.WINDOW_MODAL);
            Thread thread = new Thread(task);
            thread.setDaemon(true);
            thread.start();
        }
    }

    public void exportAction(String filename, TextArea textArea) {
        final File file = chooseSaveFile(AppVariables.getUserConfigPath(targetPathKey),
                filename, CommonFxValues.TextExtensionFilter, true);
        if (file == null) {
            return;
        }
        recordFileWritten(file, targetPathKey, VisitHistory.FileType.Text, VisitHistory.FileType.Text);

        synchronized (this) {
            if (task != null) {
                return;
            }
            task = new SingletonTask<Void>() {

                @Override
                protected boolean handle() {
                    return FileTools.writeFile(file, textArea.getText()) != null;
                }

                @Override
                protected void whenSucceeded() {
                    view(file);
                    popSuccessful();
                }

            };
            openHandlingStage(task, Modality.WINDOW_MODAL);
            Thread thread = new Thread(task);
            thread.setDaemon(true);
            thread.start();
        }
    }

    @FXML
    public void export21Action() {
        exportAction("CIE1931Observer2Degree1nm", d2n1Area);
    }

    @FXML
    public void export25Action() {
        exportAction("CIE1931Observer2Degree5nm", d2n5Area);
    }

    @FXML
    public void export101Action() {
        exportAction("CIE1964Observer10Degree1nm", d10n1Area);
    }

    @FXML
    public void export105Action() {
        exportAction("CIE1964Observer10Degree5nm", d10n5Area);
    }

}