# -*- coding: utf-8 -*-

# Copyright (c) 2003 - 2005 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a dialog to display profile data.
"""

import sys
import os
import marshal

from qt import *

from KdeQt import KQMessageBox

from PyProfileForm import PyProfileForm
import Utilities

from eric3config import getConfig

class ProfileListViewItem(QListViewItem):
    """
    Class implementing a custom QListViewItem to allow sorting on numeric values.
    """
    def __init__(self, *args):
        """
        Constructor
        """
        QListViewItem.__init__(self, *args)
        
    def getNC(self, itm):
        """
        Private method to get the value to compare on for the first column.
        
        @param itm item to operate on (ProfileListViewItem)
        """
        s = str(itm.text(0))
        return int(s.split('/')[0])
        
    def compare(self, itm, col, asc):
        """
        Public method used by QListView to compare the items.
        """
        if col == 0:
            return self.getNC(self) - self.getNC(itm)
        if col == 6:
            return int(str(self.text(col))) - int(str(itm.text(col)))
        return self.key(col, asc).localeAwareCompare(itm.key(col, asc))
        
class PyProfileDialog(PyProfileForm):
    """
    Class implementing a dialog to display the results of a syntax check run.
    """
    def __init__(self, parent = None):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        """
        PyProfileForm.__init__(self, parent)
        
        self.resultList.setSorting(0, 0)
        self.summaryList.setSorting(-1)
        
        for col in [0, 1, 2, 3, 4, 6]:
            self.resultList.setColumnAlignment(col, Qt.AlignRight)
        self.summaryList.setColumnAlignment(1, Qt.AlignRight)
        
        self.cancelled = 0
        self.exclude = 1
        self.ericpath = getConfig('ericDir')
        self.pyLibPath = Utilities.getPythonLibPath()
        
        self.menu = QPopupMenu(self)
        self.filterItm = self.menu.insertItem(self.trUtf8('Exclude Python Library'), 
            self.handleFilter)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Erase Profiling Info'), self.handleEraseProfile)
        self.menu.insertItem(self.trUtf8('Erase Timing Info'), self.handleEraseTiming)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Erase All Infos'), self.handleEraseAll)
        
    def start(self, pfn, fn=None):
        """
        Public slot to start the calculation of the profile data.
        
        @param pfn basename of the profiling file (string)
        @param fn file to display the profiling data for (string)
        """
        self.basename = os.path.splitext(pfn)[0]
        
        fname = "%s.profile" % self.basename
        if not os.path.exists(fname):
            KQMessageBox.warning(None,
                self.trUtf8("Profile Results"),
                self.trUtf8("""<p>There is no profiling data available for <b>%1</b>.</p>""")
                    .arg(pfn),
                self.trUtf8("&OK"),
                None,
                None,
                0, -1)
            self.close()
            return
        try:
            f = open(fname)
            self.stats = marshal.load(f)
            f.close()
        except:
            KQMessageBox.critical(None,
                self.trUtf8("Loading Profiling Data"),
                self.trUtf8("""<p>The profiling data could not be read from file <b>%1</b>.</p>""")
                    .arg(fname),
                self.trUtf8("&OK"),
                None,
                None,
                0, -1)
            self.close()
            return
        
        self.file = fn
        self.populateListViews()
        self.finish()
        
    def populateListViews(self, exclude=0):
        """
        Private method used to populate the listviews.
        
        @param exclude flag indicating whether files residing in the
                Python library should be excluded
        """
        self.resultList.clear()
        self.summaryList.clear()
        
        self.checkProgress.setTotalSteps(len(self.stats))
        qApp.processEvents()
        
        progress = 0
        total_calls = 0
        prim_calls  = 0
        total_tt    = 0
        
        # now go through all the files
        for func, (cc, nc, tt, ct, callers) in self.stats.items():
            if self.cancelled:
                return
            
            if not (self.ericpath and func[0].startswith(self.ericpath)) and \
               not (exclude and func[0].startswith(self.pyLibPath)):
                if self.file is None or func[0].startswith(self.file) or \
                   func[0].startswith(self.pyLibPath):
                    # calculate the totals
                    total_calls += nc
                    prim_calls  += cc
                    total_tt    += tt
                    
                    if nc != cc:
                        c = "%d/%d" % (nc, cc)
                    else:
                        c = str(nc)
                    if nc == 0:
                        tpc = "% 8.3f" % 0.0
                    else:
                        tpc = "% 8.3f" % (tt/nc,)
                    if cc == 0:
                        cpc = "% 8.3f" % 0.0
                    else:
                        cpc = "% 8.3f" % (ct/cc,)
                    itm = ProfileListViewItem(self.resultList, c, "% 8.3f" % tt, tpc, "% 8.3f" % ct, cpc,
                            func[0], str(func[1]), func[2])
                
            progress += 1
            self.checkProgress.setProgress(progress)
            qApp.processEvents()
            
        # now do the summary stuff
        itm = QListViewItem(self.summaryList, self.trUtf8("function calls"), str(total_calls))
        if total_calls != prim_calls:
            itm = QListViewItem(self.summaryList, self.trUtf8("primitive calls"), str(prim_calls))
        itm = QListViewItem(self.summaryList, self.trUtf8("CPU seconds"), "%.3f" % total_tt)
        
    def finish(self):
        """
        Private slot called when the action finished or the user pressed the button.
        """
        self.cancelled = 1
        self.cancelButton.setText(self.trUtf8('OK'))
        self.cancelButton.setDefault(1)
        
    def unfinish(self):
        """
        Private slot called to revert the effects of the finish slot.
        """
        self.cancelled = 0
        self.cancelButton.setText(self.trUtf8('&Cancel'))
        self.cancelButton.setDefault(1)
        
    def buttonPressed(self):
        """
        Private slot connected to the button clicked signal.
        """
        if self.cancelled:
            self.close()
        else:
            self.finish()
            
    def handleContextMenu(self, itm, pos, col):
        """
        Private slot to show the context menu of the listviews.
        
        @param itm the item under the mouse cursor (QListViewItem)
        @param pos global position of the context menu (QPoint)
        @param col column of the mouse cursor (int)
        """
        self.menu.popup(pos)
        
    def handleEraseProfile(self):
        """
        Private slot to handle the Erase Profile context menu action.
        """
        fname = "%s.profile" % self.basename
        if os.path.exists(fname):
            os.remove(fname)
        
    def handleEraseTiming(self):
        """
        Private slot to handle the Erase Timing context menu action.
        """
        fname = "%s.timings" % self.basename
        if os.path.exists(fname):
            os.remove(fname)
        
    def handleEraseAll(self):
        """
        Private slot to handle the Erase All context menu action.
        """
        self.handleEraseProfile()
        self.handleEraseTiming()
        
    def handleFilter(self):
        """
        Private slot to handle the Exclude/Include Python Library context menu action.
        """
        self.unfinish()
        if self.exclude:
            self.exclude = 0
            self.menu.changeItem(self.filterItm, self.trUtf8('Include Python Library'))
            self.populateListViews(1)
        else:
            self.exclude = 1
            self.menu.changeItem(self.filterItm, self.trUtf8('Exclude Python Library'))
            self.populateListViews(0)
        self.finish()
