#!/usr/bin/env python
"""
GUI for searching through postings and transactions.
"""
__author__ = 'Christian Caron <ccaron@gmail.com>'


#------------------------------------------------------------------------------

from beancount.ledger import Ledger
from beancount.utils import itertree
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import logging, codecs, signal, os
from collections import defaultdict
from itertools import izip

#------------------------------------------------------------------------------
 
class LayoutWidget(QWidget):
    def __init__(self, widgets, vertical=True):
        QWidget.__init__(self)
        layout = QVBoxLayout() if vertical else QHBoxLayout()
        for w in widgets:
            layout.addWidget(w)
        self.setLayout(layout)

#------------------------------------------------------------------------------

class PieView(QAbstractItemView):

    colors = (Qt.darkRed,
              Qt.green, Qt.darkGreen, Qt.blue, Qt.darkBlue,
              Qt.cyan, Qt.darkCyan, Qt.magenta, Qt.darkMagenta,
              Qt.yellow, Qt.darkYellow, Qt.gray, Qt.darkGray, Qt.lightGray)
    
    def verticalOffset(self):
        return self.verticalScrollBar().value()
    def horizontalOffset(self):
        return self.horizontalScrollBar().value()
    def paintEvent(self, event):
        painter = QPainter(self.viewport())
        painter.setRenderHint(QPainter.Antialiasing)
        rect = event.rect().adjusted(10,10,-10,-10)
        values = defaultdict(int)
        for i in xrange(self.model().rowCount(self.rootIndex())):
            index = self.model().index(i, 1, self.rootIndex())
            values[self.model().itemFromIndex(index).text()] += 1

        total = float(sum(values.values()))
        current = 0
        for i, (key, val) in enumerate(values.iteritems()):
            a = current/total
            b = val/total
            color = QColor(self.colors[i%len(self.colors)])
            painter.setBrush(color)
            painter.setPen(color.darker())
            painter.drawPie(rect, a*360*16, b*360*16)
            current += val
        

#------------------------------------------------------------------------------

class Application(QApplication):
    def __init__(self, ledger_fn, *args):
        QApplication.__init__(self, [])

        # FIXME: This should be an abstract model that talks
        #        directly to the ledger.
        self.accmodel = QStandardItemModel(self)

        self.ledger = Ledger()
        self.readLedger(ledger_fn)

        # Account list filter
        self.accfilter = QSortFilterProxyModel()
        self.accfilter.setSourceModel(self.accmodel)
        self.accfilter.setFilterCaseSensitivity(Qt.CaseInsensitive)

        # Selected account model
        self.selaccmodel = QStandardItemModel()

        # Selected account filter
        self.selaccfilter = QSortFilterProxyModel()
        self.selaccfilter.setSourceModel(self.selaccmodel)
        self.selaccfilter.setFilterKeyColumn(-1)
        self.selaccfilter.setFilterCaseSensitivity(Qt.CaseInsensitive)

        # Label the columns of the table.
        for i, string in enumerate(('Date', 'Account', 'Comment', 'Price')):
            self.selaccmodel.setHorizontalHeaderItem(i, QStandardItem(string))

        # Account list view.
        self.accview = QListView()
        self.accview.selectionChanged = self.onAccSelectionChanged
        self.accview.setModel(self.accfilter)

        # Selected account view.
        self.selaccview = QTableView()
        self.selaccview.horizontalHeader().setStretchLastSection(True)
        self.selaccview.setSelectionMode(QAbstractItemView.SingleSelection)
        self.selaccview.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.selaccview.setSortingEnabled(True)
        self.selaccview.setModel(self.selaccfilter)

        # Inputs fields for regexs
        regex_acc = QLineEdit()
        regex_selacc = QLineEdit()
        QObject.connect(regex_acc, SIGNAL('textChanged(const QString&)'), self.accfilter.setFilterRegExp)
        QObject.connect(regex_selacc, SIGNAL('textChanged(const QString&)'), self.selaccfilter.setFilterRegExp)

        # Pie View
        self.pieview = PieView()
        self.pieview.setModel(self.selaccmodel)

        # Layout everything nicely
        self.ui = QMainWindow()

        splitter = QSplitter()
        splitter.addWidget(LayoutWidget((regex_acc, self.accview)))
        splitter.addWidget(LayoutWidget((regex_selacc, self.selaccview, self.pieview)))
        splitter.setStretchFactor(0,1)
        splitter.setStretchFactor(1,2)
        self.ui.setCentralWidget(splitter)
        self.ui.show()

        signal.signal(signal.SIGINT, signal.SIG_DFL)

        self.buildMenu()

    def buildMenu(self):

        # File menu
        fileMenu = self.ui.menuBar().addMenu("File")

        for text, seq, cb in (('Open...', 'Ctrl+O', self.onFileOpen),
                              ('Refresh', 'Ctrl+R', self.refresh),
                              ('Quit', 'Ctrl+Q', self.quit)):            

            action = QAction(text, self)
            action.setShortcut(seq)
            fileMenu.addAction(action)
            QObject.connect(action, SIGNAL("triggered()"), cb)

    def onAccSelectionChanged(self, selected, deselected):
        indexes = selected.indexes()
        indexes = map(lambda x:self.accfilter.mapToSource(x), indexes)
        items = map(lambda x:self.accmodel.itemFromIndex(x), indexes)
        items = map(lambda x:str(x.text()), items)

        # Clear all the rows of the table but not the header.
        self.selaccmodel.removeRows(0, self.selaccmodel.rowCount())

        for _, _, account in iter(itertree(self.ledger.get_account(items[0]))):
            for p in account.postings:
                self.selaccmodel.appendRow([QStandardItem(str(p.get_date())),
                                            QStandardItem(str(p.account_name)),
                                            QStandardItem(str(p.txn.narration)),
                                            QStandardItem(str(p.amount))])


        self.selaccmodel.emit(SIGNAL("dataChanged(const QModelIndex&, const QModelIndex&)"),
                              self.selaccmodel.index(0, 0),
                              self.selaccmodel.index(self.selaccmodel.rowCount(),
                                                     self.selaccmodel.columnCount()))

        
    def onFileOpen(self):
        filename = QFileDialog.getOpenFileName(self.ui, "Open Ledger...")
        if filename:
            self.readLedger(str(filename))

    def readLedger(self, fn):
        f = open(fn)
        Reader = codecs.getreader('utf-8')
        f = Reader(f)
        self.ledger = Ledger()
        self.ledger.parse_file(f, fn, 'utf-8')
        self.refresh()

    def bindShortcut(self, seq, target):
        QObject.connect(QShortcut(QKeySequence(seq), self.ui),
                        SIGNAL('activated()'), target)
        
    def refresh(self):
        self.accmodel.clear()
        for _, _, x in iter(itertree(self.ledger.get_root_account())):
            self.accmodel.appendRow([QStandardItem(x.fullname)])

#------------------------------------------------------------------------------
        
def main():
    import optparse
    parser = optparse.OptionParser(__doc__.strip())
    opts, args = parser.parse_args()

    if len(args) != 1:
        parser.error("You need to specify a ledger file to process.")

    app = Application(args[0])
    app.exec_()

if __name__ == '__main__':
    main()

