from numpy.linalg.linalg import LinAlgError

from AnyQt.QtCore import Qt

from Orange.data import Table
from Orange.widgets import widget, gui, settings
from Orange.widgets.utils.itemmodels import PyTableModel
from Orange.widgets.widget import Input

from orangecontrib.timeseries import Timeseries, granger_causality


class OWGrangerCausality(widget.OWWidget):
    name = 'Granger Causality'
    description = 'Test if one time series Granger-causes (i.e. can be an ' \
                  'indicator of) another.'
    icon = 'icons/GrangerCausality.svg'
    priority = 190

    class Inputs:
        time_series = Input("Time series", Table, replaces=["Timeseries"])

    max_lag = settings.Setting(20)
    confidence = settings.Setting(95)
    autocommit = settings.Setting(False)

    UserAdviceMessages = [
        widget.Message('We say <i>X</i> Granger-causes <i>Y</i> if '
                       'predictions of values of <i>Y</i> based on its own '
                       'past values and on the past values of <i>X</i> are '
                       'better than predictions of <i>Y</i> based on its '
                       'past values alone.<br><br>'
                       'It does NOT mean <i>X</i> causes <i>Y</i>!',
                       'explanation',
                       widget.Message.Warning)
    ]

    class Error(widget.OWWidget.Error):
        unexpected_error = widget.Msg('Unexpected error: {}')

    def __init__(self):
        self.data = None
        box = gui.vBox(self.controlArea, 'Granger Test')
        gui.hSlider(box, self, 'confidence',
                    minValue=90, maxValue=99,
                    label='Confidence:',
                    labelFormat=" %d%%",
                    callback=self.on_changed)
        gui.spin(box, self, 'max_lag', 1, 50,
                 label='Max lag:',
                 callback=self.on_changed)
        gui.auto_commit(box, self, 'autocommit', '&Test')
        gui.rubber(self.controlArea)

        self.model = model = PyTableModel(parent=self)
        model.setHorizontalHeaderLabels(['Min. lag', 'Series 1', '', 'Series 2'])
        view = gui.TableView(self)
        view.setModel(model)
        bold = view.BoldFontDelegate(self)
        view.setItemDelegateForColumn(1, bold)
        view.setItemDelegateForColumn(3, bold)
        view.horizontalHeader().setStretchLastSection(False)
        self.mainArea.layout().addWidget(view)
        # TODO: output the series with subset columns of selected model rows
        # TODO: allow setting filters or choosing what variables to include in test

    def on_changed(self):
        self.commit()

    @Inputs.time_series
    def set_data(self, data):
        self.data = data = None if data is None else Timeseries.from_data_table(data)
        self.on_changed()

    def commit(self):
        data = self.data
        self.model.clear()
        self.Error.unexpected_error.clear()
        if data is None:
            return

        try:
            with self.progressBar() as progress:
                res = granger_causality(data,
                                        self.max_lag,
                                        1 - self.confidence / 100,
                                        callback=progress.advance)
                res = [[lag, row, '→', col]
                       for lag, row, col in res]
        except (ValueError, LinAlgError) as ex:
            self.Error.unexpected_error(ex.args[0])
        else:
            self.model.wrap(res)
            self.model.sort(0, Qt.DescendingOrder)


if __name__ == "__main__":
    from AnyQt.QtWidgets import QApplication

    a = QApplication([])
    ow = OWGrangerCausality()

    data = Timeseries('airpassengers')
    ow.set_data(data)

    ow.show()
    a.exec()
