# Copyright (c) 2011-2014, B.I.Stepanov Institute of Physics, National Academy
# of Sciences of Belarus.
#
# 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 2 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, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

from PyQt4.QtCore import *
from PyQt4.QtGui import *

from common.utils import gui
from common.utils import txt
from common.utils import utils

from common.FileSelectionWidget import *
from common.StatusLabelWidget import *
from common.StatusSignalingWidget import *

from LidarDatabaseWidget import *
from LidarTableWidget import *
from OutputViewerWidget import *
from PlotWidgets import *
from RamanOutput import *
from RamanParams import *
from RamanRetrievalProcess import *
from RetrievalProgressDialog import *

try:
    from RamanPerturbanceProcess import *
except ImportError:
    ramanPerturbanceProcessPluginLoaded = False
else:
    ramanPerturbanceProcessPluginLoaded = True

if ramanPerturbanceProcessPluginLoaded:
    from RamanPerturbanceParams import *

__all__ = ['RamanRetrieverWidget']

# *****************************************************************************
class RamanRetrieverWidget(QWidget):
    """Main GUI window of the aerosol backscatter and lidar ratio retrieval
    algorithm utilizing Raman lidar signals."""

    # ---- Public methods -----------------------------------------------------
    def __init__(self, parent = None):

        QWidget.__init__(self, parent)
        # Free the resources if other windows would still exist upon closure.
        self.setAttribute(Qt.WA_DeleteOnClose)

        # ---- Members ----
        # This will hold the widget opened by 'View output' button click.
        self.outputViewerWidget = None

        self.algorithmParams = RamanParams.loadFromFile()
        if ramanPerturbanceProcessPluginLoaded:
            self.perturbanceParams = RamanPerturbanceParams.loadFromFile()

        # ---- Layout: main widgets ----
        layout = QVBoxLayout()

        self.lidarDatabaseWidget = LidarDatabaseWidget()
        self.lidarDatabaseWidget.dataListChanged.connect(
            self.onLidarDataListChanged)

        self.lidarTableWidget = LidarTableWidget()
        self.lidarTableWidget.addAllowedChannelSequence([
            LidarChannelInfo(355.0, (0, 3)),
            LidarChannelInfo(387.0, 1)])
        self.lidarTableWidget.addAllowedChannelSequence([
            LidarChannelInfo(532.0, (0, 3)),
            LidarChannelInfo(607.0, 1)])

        lidarLayout = QVBoxLayout()
        lidarLayout.addWidget(self.lidarDatabaseWidget)
        # Set stretch factor to 1 to prevent vertical shrinkage.
        lidarLayout.addWidget(self.lidarTableWidget, 1)
        lidarLayout.setContentsMargins(0, 0, 0, 0)

        layout.addLayout(lidarLayout)

        self.retrievalSelectionWidget = RamanRetrievalSelectionWidget()
        layout.addWidget(self.retrievalSelectionWidget)

        # ---- Layout: status bar and button box ----
        self.statusLabel = StatusLabelWidget()
        layout.addWidget(self.statusLabel)

        self.paramsButton = QPushButton('&Parameters')
        self.paramsButton.setIcon(gui.loadIcon('parameters'))
        self.paramsButton.clicked.connect(self.onParamsButtonClicked)

        if ramanPerturbanceProcessPluginLoaded:
            self.perturbanceButton = QPushButton('Per&turbance options')
            self.perturbanceButton.clicked.connect(
                self.onPerturbanceButtonClicked)

        self.retrieveButton = QPushButton('&Retrieve')
        self.retrieveButton.clicked.connect(self.onRetrieveButtonClicked)

        self.viewOutputButton = QPushButton('&View output')
        self.viewOutputButton.clicked.connect(self.onViewOutputButtonClicked)

        buttonBoxLayout = QHBoxLayout()
        buttonBoxLayout.addWidget(self.paramsButton)
        if ramanPerturbanceProcessPluginLoaded:
            buttonBoxLayout.addWidget(self.perturbanceButton)
        buttonBoxLayout.addStretch()
        buttonBoxLayout.addWidget(self.retrieveButton)
        buttonBoxLayout.addWidget(self.viewOutputButton)
        layout.addLayout(buttonBoxLayout)

        self.setLayout(layout)

        self.setWindowTitle('Raman aerosol retriever')
        self.setWindowIcon(gui.loadIcon('institute-of-physics'))

        # ---- Initialization ----
        for widget in (self.lidarDatabaseWidget, self.retrievalSelectionWidget,
            self.lidarTableWidget):

            widget.statusMessageChanged.connect(self.onStatusMessageChanged)
            widget.repaintRequested.connect(
                self.statusLabel.repaintParentWindow)

        self.loadSettings()

        self.lidarTableWidget.getTableWidget().setFocus()

    # ---- Private overridden methods -----------------------------------------
    def closeEvent(self, event):
        self.saveSettings()
        event.accept()

    # ---- Private methods ----------------------------------------------------
    def saveSettings(self):
        settings = QSettings('settings/interface/RamanRetrieverWidget.ini',
            QSettings.IniFormat)

        # Save window size, position and maximization state.
        gui.saveWindowSettings(self, settings)

        # Save input and output file paths.
        self.lidarDatabaseWidget.saveSettings(settings)
        self.lidarTableWidget.saveSettings(settings)
        self.retrievalSelectionWidget.saveSettings(settings)

    def loadSettings(self):
        settings = QSettings('settings/interface/RamanRetrieverWidget.ini',
            QSettings.IniFormat)

        # Load window size, position and maximization state.
        gui.loadWindowSettings(self, settings)

        # Load input and output file paths.
        self.lidarDatabaseWidget.loadSettings(settings)
        self.lidarTableWidget.loadSettings(settings)
        self.retrievalSelectionWidget.loadSettings(settings)

    # ---- Private slots ------------------------------------------------------
    def onLidarDataListChanged(self):
        """Populate lidar table with contents of the newly loaded lidar
        database."""

        lidarDataList = self.lidarDatabaseWidget.getDataList()

        self.lidarTableWidget.connectDataList(lidarDataList)

        # Do nothing more if the database fails to load or is empty.
        if len(lidarDataList) == 0:
            return

    def onStatusMessageChanged(self):
        """Update Retrieve button state and show the most relevant error
        message in 'self.statusLabel'."""

        self.viewOutputButton.setEnabled(
            self.retrievalSelectionWidget.getStatusMessage() is None)

        # List of widgets that may generate errors. Widget order defines error
        # message priority.
        widgetList = (self.lidarDatabaseWidget, self.retrievalSelectionWidget,
            self.lidarTableWidget)

        statusMessages = [widget.getStatusMessage() for widget in widgetList]

        if any(message is not None for message in statusMessages):
            self.statusLabel.setMostSevereStatusMessage(statusMessages)
            self.retrieveButton.setEnabled(False)
            return

        lidarInputs = self.lidarTableWidget.getData()

        # Display information about selected lidar channels to the user.
        assert len(lidarInputs) == 2

        sourceStr = '%.0f nm' % lidarInputs[0].wavelength
        ramanStr = '%.0f nm' % lidarInputs[1].wavelength

        self.statusLabel.setCompleted('The data are ready for the retrieval '
            '(source channel: %s, Raman channel: %s)' % (
            txt.quote(sourceStr, addQuotes = False),
            txt.quote(ramanStr, addQuotes = False)))

        self.retrieveButton.setEnabled(True)

    def onParamsButtonClicked(self):
        paramsDialog = RamanParamsDialog(self, self.algorithmParams)
        paramsDialog.exec_()

    if ramanPerturbanceProcessPluginLoaded:
        def onPerturbanceButtonClicked(self):

            paramsDialog = RamanPerturbanceParamsDialog(
                self, self.perturbanceParams)
            paramsDialog.exec_()

    def onRetrieveButtonClicked(self):
        """Launch the retrieval modal dialog."""

        # If there is an error, retrieval button should be disabled.
        assert self.lidarTableWidget.getStatusMessage() is None

        lidarInputs = self.lidarTableWidget.getData()

        retrievalProcess = RamanRetrievalProcess(lidarInputs,
            self.algorithmParams)
        if ramanPerturbanceProcessPluginLoaded:
            retrievalProcess = RamanPerturbanceProcess(lidarInputs,
                self.algorithmParams, self.perturbanceParams)

        progressDialog = RamanRetrievalDialog(self, retrievalProcess,
            self.retrievalSelectionWidget.getFilePath())

        progressDialog.exec_()

    def onViewOutputButtonClicked(self):
        """Open the output database in the output viewer window, select the
        last database record and activate the window.

        If there is no output viewer window yet, or if the window has been
        closed, create it."""

        if self.outputViewerWidget is not None:
            # Check if the previously created window still exists.
            try:
                self.outputViewerWidget.objectName()
            except RuntimeError:
                self.outputViewerWidget = None

        if self.outputViewerWidget is None:
            # Create a new widget with no database selected.
            self.outputViewerWidget = OutputViewerWidget(
                skipDatabaseLoading = True)

        # Open the output database in the widget.
        self.outputViewerWidget.openLastDatabaseRecord(
            self.retrievalSelectionWidget.getFilePath())
        # This will have no effect if the widget had already existed.
        self.outputViewerWidget.show()

        self.outputViewerWidget.activateWindow()

# *****************************************************************************
class RamanRetrievalSelectionWidget(StatusSignalingWidget):
    """Widget responsible for selection and initialization of the output
    aerosol database in the the aerosol backscatter and lidar ratio retrieval
    algorithm utilizing Raman lidar signals."""

    # ---- Public methods -----------------------------------------------------
    def __init__(self, parent = None):
        StatusSignalingWidget.__init__(self, parent)

        layout = QVBoxLayout()

        self.fileSelector = FileSelectionWidget(
            'Raman retrieval &output database:',
            'Microsoft Access databases (*.mdb)',
            'Select Raman retrieval output database',
            'Open an existing Raman retrieval output database file')
        self.fileSelector.showCreateButton(
            'Select name for a new Raman retrieval output database',
            'Create a new empty Raman retrieval output database file')
        self.fileSelector.fileOpened.connect(self.onFileOpened)
        self.fileSelector.fileCreated.connect(self.onFileCreated)
        layout.addWidget(self.fileSelector)

        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

    def getFilePath(self):
        return self.fileSelector.getFilePath()

    def saveSettings(self, settings):
        with utils.SettingsGrouper(settings, 'RamanRetrievalSelectionWidget'):
            self.fileSelector.saveSettings(settings, 'filePath')

    def loadSettings(self, settings):
        with utils.SettingsGrouper(settings, 'RamanRetrievalSelectionWidget'):
            self.fileSelector.loadSettings(settings, 'filePath')

    # ---- Private slots ------------------------------------------------------
    def onFileOpened(self):
        """Check format of an existing database file selected by the user."""

        filePath = self.fileSelector.getFilePath()

        if filePath is None:
            self.setErrorMessage(
                'Raman retrieval output database is not specified')
            return

        try:
            self.setProgressMessage(
                'Checking Raman retrieval output database...', True)
            RamanOutput.checkDataFormat(filePath)

        except txt.Error as e:
            self.setErrorMessage(e.text)
            return

        self.clearStatusMessage()

    def onFileCreated(self):
        """Create a new database file at the file path selected by the user."""

        filePath = self.fileSelector.getFilePath()

        try:
            self.setProgressMessage(
                'Creating Raman retrieval output database...', True)
            RamanOutput.createDatabase(filePath)

        except txt.Error as e:
            self.setErrorMessage(e.text)
            self.fileSelector.invalidateFilePath()
            return

        # Check format of the newly created database file.
        self.onFileOpened()

# *****************************************************************************
class RamanRetrievalDialog(RetrievalProgressDialog):
    """Graphical user interface for the optimization process."""

    # ---- Private overridden methods -----------------------------------------
    def createPlotWidget(self):
        return RamanPlotWidget()

    def getSettingsFilePath(self):
        return 'settings/interface/RamanRetrievalDialog.ini'
