/*
    Copywrite 2016-2017 Will Winder

    This file is part of Universal Gcode Sender (UGS).

    UGS 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.

    UGS 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 UGS.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.willwinder.universalgcodesender.uielements.panels;

import com.willwinder.universalgcodesender.i18n.Localization;
import com.willwinder.universalgcodesender.listeners.ControllerListener;
import com.willwinder.universalgcodesender.listeners.ControllerStatus;
import com.willwinder.universalgcodesender.listeners.UGSEventListener;
import com.willwinder.universalgcodesender.model.BackendAPI;
import com.willwinder.universalgcodesender.model.Position;
import com.willwinder.universalgcodesender.model.UGSEvent;
import com.willwinder.universalgcodesender.services.JogService;
import com.willwinder.universalgcodesender.types.GcodeCommand;
import com.willwinder.universalgcodesender.uielements.jog.JogPanel;
import com.willwinder.universalgcodesender.utils.CommUtils;
import com.willwinder.universalgcodesender.utils.FirmwareUtils;
import net.miginfocom.swing.MigLayout;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.util.List;

import static com.willwinder.universalgcodesender.utils.GUIHelpers.displayErrorDialog;
import java.awt.event.KeyEvent;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ConnectionPanelGroup extends JPanel implements UGSEventListener, ControllerListener {
    private static final Logger logger = Logger.getLogger(ConnectionPanelGroup.class.getName());

    private final JLabel portLabel = new JLabel(Localization.getString("mainWindow.swing.portLabel"));
    private final JLabel baudLabel = new JLabel(Localization.getString("mainWindow.swing.baudLabel"));
    private final JLabel firmwareLabel = new JLabel(Localization.getString("mainWindow.swing.firmwareLabel"));

    private final JComboBox portCombo = new JComboBox();
    private final JComboBox baudCombo = new JComboBox();
    private final JComboBox firmwareCombo = new JComboBox();

    private final JButton refreshButton = new JButton(new javax.swing.ImageIcon(getClass().getResource("/resources/icons/refresh.gif")));
    private final JButton openCloseButton = new JButton(Localization.getString("mainWindow.swing.opencloseButton"));

    private final JButton sendPauseResumeButton = new JButton(Localization.getString("mainWindow.swing.sendButton"));
    private final JButton browseCancelButton = new JButton(Localization.getString("mainWindow.swing.browseButton"));

    private final javax.swing.JFileChooser fileChooser;

    private final JPanel connection = new JPanel();
    private final MachineStatusPanel machineStatus;

    private final JLabel currentFile = new JLabel();
    private final SendStatusPanel sendStatusPanel;

    private final JogPanel jogPanel;

    private final BackendAPI backend;

    public ConnectionPanelGroup(BackendAPI backend, JogService jogService) {
        this.backend = backend;
        if (this.backend != null) {
            this.backend.addUGSEventListener(this);
        }

        machineStatus = new MachineStatusPanel(backend);
        machineStatus.setVisible(false);
        sendStatusPanel = new SendStatusPanel(backend);
        setName(Localization.getString("mainWindow.swing.connectionPanel"));

        fileChooser = new JFileChooser();

        jogPanel = new JogPanel(backend, jogService, true);

        initComponents();
        addKeyboardListener();
    }

    private void initComponents() {
        loadFirmwareSelector();

        portCombo.setEditable(true);

        baudCombo.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400"}));
        baudCombo.setSelectedIndex(2);
        baudCombo.setToolTipText("Select baudrate to use for the serial port.");

        openCloseButton.addActionListener(evt -> opencloseButtonActionPerformed(evt));
        refreshButton.addActionListener(evt -> loadPortSelector());
        sendPauseResumeButton.addActionListener(evt -> sendPauseResumeButtonActionPerformed(evt));
        browseCancelButton.addActionListener(evt -> browseCancelButtonActionPerformed(evt));

        Font f = currentFile.getFont();
        currentFile.setFont(f.deriveFont(f.getStyle() | Font.BOLD));

        sendStatusPanel.setBorder(BorderFactory.createTitledBorder(""));

        this.updateConnectionControlsStateOpen(false);

        // MigLayout... 3rd party layout library.
        setLayout(new MigLayout("flowy, hidemode 3"));
        add(openCloseButton, "wmin button, grow, flowx, split 3");
        add(sendPauseResumeButton, "r, wmin button, grow");
        add(browseCancelButton, "r, wmin button, grow");

        connection.setLayout(new MigLayout("fill, wrap 3"));
        connection.add(portLabel, "al right");
        connection.add(portCombo, "span 3");
        connection.add(baudLabel, "al right");
        connection.add(baudCombo);
        connection.add(refreshButton);
        connection.add(firmwareLabel, "al right");
        connection.add(firmwareCombo, "span 3");
        add(connection);
        add(machineStatus, "grow");
        add(currentFile, "grow");
        add(sendStatusPanel, "grow");
        jogPanel.setBorder(BorderFactory.createTitledBorder(
                Localization.getString("mainWindow.swing.keyboardMovementPanel")));
        add(jogPanel, "grow");
    }

    @Override
    public void UGSEvent(UGSEvent evt) {
        if (evt.isFileChangeEvent()) {
           switch(evt.getFileState()) {
               case FILE_LOADED:
                   updateConnectionControlsStateOpen(backend.isConnected());
                   updateCurrentFileLabel(new File(evt.getFile()));
                   break;

               case FILE_LOADING:
                   sendPauseResumeButton.setEnabled(false);
                   currentFile.setText("Loading...");
                   currentFile.setToolTipText("");
                   break;
           }
        }


        if (evt.isStateChangeEvent()) {
            switch (evt.getControlState()) {
                case COMM_DISCONNECTED:
                    this.updateConnectionControlsStateOpen(false);
                    break;

                case COMM_IDLE:
                    this.updateConnectionControlsStateOpen(true);
                    sendPauseResumeButton.setText(Localization.getString("mainWindow.swing.sendButton"));
                    browseCancelButton.setText(Localization.getString("mainWindow.swing.browseButton"));
                    break;

                case COMM_SENDING:
                    sendPauseResumeButton.setText(Localization.getString("mainWindow.swing.pauseButton"));
                    browseCancelButton.setText(Localization.getString("mainWindow.swing.cancelButton"));
                    break;

                case COMM_SENDING_PAUSED:
                    sendPauseResumeButton.setText(Localization.getString("mainWindow.ui.resumeButton"));
                    break;

                default:
            }
        }
    }

    @Override
    public void controlStateChange(UGSEvent.ControlState state) {
    }

    @Override
    public void fileStreamComplete(String filename, boolean success) {
        sendPauseResumeButton.setText(Localization.getString("mainWindow.swing.sendButton"));
        browseCancelButton.setText(Localization.getString("mainWindow.swing.browseButton"));
    }

    @Override
    public void commandSkipped(GcodeCommand command) {

    }

    @Override
    public void commandSent(GcodeCommand command) {

    }

    @Override
    public void commandComplete(GcodeCommand command) {

    }

    @Override
    public void commandComment(String comment) {

    }

    @Override
    public void probeCoordinates(Position p) {

    }

    @Override
    public void messageForConsole(MessageType type, String msg) {

    }

    @Override
    public void statusStringListener(ControllerStatus status) {

    }

    @Override
    public void postProcessData(int numRows) {

    }

    private void opencloseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_opencloseButtonActionPerformed
        if (this.openCloseButton.getText().equalsIgnoreCase(Localization.getString("open"))) {
            String firmware = this.firmwareCombo.getSelectedItem().toString();
            String port = portCombo.getSelectedItem().toString();
            int baudRate = Integer.parseInt(baudCombo.getSelectedItem().toString());

            try {
                this.backend.connect(firmware, port, baudRate);
            } catch (Exception e) {
                logger.log(Level.WARNING, "Problem during backend.connect.", e);
                displayErrorDialog(e.getMessage());
            }
        } else {
            try {
                this.backend.disconnect();
            } catch (Exception e) {
                displayErrorDialog(e.getMessage());
            }
        }
    }


    private void loadPortSelector() {
        portCombo.removeAllItems();
        String[] portList = CommUtils.getSerialPortList();

        if (portList.length < 1) {
            if (backend.getSettings().isShowSerialPortWarning()) {
                displayErrorDialog(Localization.getString("mainWindow.error.noSerialPort"));
            }
        } else {
            for (String port : portList) {
                portCombo.addItem(port);
            }

            portCombo.setSelectedIndex(0);
        }
    }

    private void loadFirmwareSelector() {
        firmwareCombo.removeAllItems();
        List<String> firmwareList = FirmwareUtils.getFirmwareList();

        if (firmwareList.size() < 1) {
            displayErrorDialog(Localization.getString("mainWindow.error.noFirmware"));
        } else {
            java.util.Iterator<String> iter = firmwareList.iterator();
            while (iter.hasNext()) {
                firmwareCombo.addItem(iter.next());
            }
        }
    }

    public void saveSettings() {
        backend.getSettings().setPort(portCombo.getSelectedItem().toString());
        backend.getSettings().setPortRate(baudCombo.getSelectedItem().toString());
        backend.getSettings().setFirmwareVersion(firmwareCombo.getSelectedItem().toString());
        backend.getSettings().setLastOpenedFilename(fileChooser.getSelectedFile().getAbsolutePath());
        jogPanel.saveSettings();
    }

    public void loadSettings() {
        portCombo.setSelectedItem(backend.getSettings().getPort());
        baudCombo.setSelectedItem(backend.getSettings().getPortRate());
        firmwareCombo.setSelectedItem(backend.getSettings().getFirmwareVersion());

        String lastOpenedFilename = backend.getSettings().getLastOpenedFilename();
        if (lastOpenedFilename != null && !lastOpenedFilename.isEmpty()) {
            fileChooser.setSelectedFile(new File(lastOpenedFilename));
        }

        updateCurrentFileLabel(backend.getGcodeFile());
        jogPanel.loadSettings();
    }

    private void updateCurrentFileLabel(File lastOpenedFilename) {
        if (lastOpenedFilename != null) {
            currentFile.setText(lastOpenedFilename.getName());
            currentFile.setToolTipText(lastOpenedFilename.getAbsolutePath());
        } else {
            currentFile.setText("");
            currentFile.setToolTipText("");
        }
    }

    private void updateConnectionControlsStateOpen(boolean isOpen) {
        connection.setVisible(!isOpen);
        machineStatus.setVisible(isOpen);

        sendPauseResumeButton.setEnabled(isOpen && backend != null && backend.getGcodeFile() != null);
        browseCancelButton.setEnabled(isOpen);

        if (isOpen) {
            this.openCloseButton.setText(Localization.getString("close"));
        } else {
            this.openCloseButton.setText(Localization.getString("open"));
        }
    }

    private void sendPauseResumeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sendButtonActionPerformed
        try {
            if (sendPauseResumeButton.getText().equals(Localization.getString("mainWindow.swing.sendButton"))) {
                this.backend.send();
            } else {
                this.backend.pauseResume();
            }
        } catch (Exception e) {
            displayErrorDialog(e.getMessage());
        }
    }

    private void browseCancelButtonActionPerformed(java.awt.event.ActionEvent evt) {
        try {
            if (browseCancelButton.getText().equals(Localization.getString("mainWindow.swing.browseButton"))) {
                openFileChooser();
            } else {
                backend.cancel();

            }
        } catch (Exception e) {
            displayErrorDialog(e.getMessage());
        }
    }

    private void openFileChooser() {
        int returnVal = fileChooser.showOpenDialog(this);
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            try {
                File gcodeFile = fileChooser.getSelectedFile();
                backend.setGcodeFile(gcodeFile);
            } catch (Exception ex) {
                displayErrorDialog(ex.getMessage());
            }
        } else {
            // Canceled file open.
        }
    }

    public String getDuration() {
        return sendStatusPanel.getDuration();
    }

    private void addKeyboardListener() {
        KeyboardFocusManager.getCurrentKeyboardFocusManager()
                .addKeyEventDispatcher(new KeyEventDispatcher() {
                    @Override
                    public boolean dispatchKeyEvent(KeyEvent e) {
                        // Check context.
                        if (((jogPanel.isKeyboardMovementEnabled()) &&
                                e.getID() == KeyEvent.KEY_PRESSED)) {
                            switch (e.getKeyCode()) {
                                case KeyEvent.VK_RIGHT:
                                case KeyEvent.VK_KP_RIGHT:
                                case KeyEvent.VK_NUMPAD6:
                                    jogPanel.xPlusButtonActionPerformed();
                                    e.consume();
                                    return true;
                                case KeyEvent.VK_LEFT:
                                case KeyEvent.VK_KP_LEFT:
                                case KeyEvent.VK_NUMPAD4:
                                    jogPanel.xMinusButtonActionPerformed();
                                    e.consume();
                                    return true;
                                case KeyEvent.VK_UP:
                                case KeyEvent.VK_KP_UP:
                                case KeyEvent.VK_NUMPAD8:
                                    jogPanel.yPlusButtonActionPerformed();
                                    e.consume();
                                    return true;
                                case KeyEvent.VK_DOWN:
                                case KeyEvent.VK_KP_DOWN:
                                case KeyEvent.VK_NUMPAD2:
                                    jogPanel.yMinusButtonActionPerformed();
                                    e.consume();
                                    return true;
                                case KeyEvent.VK_PAGE_UP:
                                case KeyEvent.VK_NUMPAD9:
                                    jogPanel.zPlusButtonActionPerformed();
                                    e.consume();
                                    return true;
                                case KeyEvent.VK_PAGE_DOWN:
                                case KeyEvent.VK_NUMPAD3:
                                    jogPanel.zMinusButtonActionPerformed();
                                    e.consume();
                                    return true;
                                case KeyEvent.VK_ADD:
                                    jogPanel.increaseStepActionPerformed();
                                    e.consume();
                                    return true;
                                case KeyEvent.VK_SUBTRACT:
                                    jogPanel.decreaseStepActionPerformed();
                                    e.consume();
                                    return true;
                                case KeyEvent.VK_DIVIDE:
                                    jogPanel.divideStepActionPerformed();
                                    e.consume();
                                    return true;
                                case KeyEvent.VK_MULTIPLY:
                                    jogPanel.multiplyStepActionPerformed();
                                    e.consume();
                                    return true;
                                case KeyEvent.VK_INSERT:
                                case KeyEvent.VK_NUMPAD0:
                                    //resetCoordinatesButtonActionPerformed(null);
                                    e.consume();
                                    return true;
                                default:
                                    break;
                            }
                        }

                        return false;
                    }
                });
    }
}
