from PySide import QtCore, QtGui

from tools.modified.androguard.session import Session
from tools.modified.androguard.core import androconf
from tools.modified.androguard.gui.fileloading import FileLoadingThread
from tools.modified.androguard.gui.treewindow import TreeWindow
from tools.modified.androguard.gui.sourcewindow import SourceWindow
from tools.modified.androguard.gui.stringswindow import StringsWindow

from tools.modified.androguard.gui.helpers import class2func

import os

class MainWindow(QtGui.QMainWindow):

    '''Main window:
       self.central: QTabWidget in center area
       self.dock: QDockWidget in left area
       self.tree: TreeWindow(QTreeWidget) in self.dock
    '''

    def __init__(self, parent=None, input_file=None):
        super(MainWindow, self).__init__(parent)
        self.session = None

        self.setupSession()

        self.setupFileMenu()
        self.setupViewMenu()
        self.setupHelpMenu()

        self.setupCentral()
        self.setupEmptyTree()
        self.setupDock()
        self.setWindowTitle("Androguard GUI")

        self.showStatus("Androguard GUI")

        if input_file != None:
            self.openFile(input_file)

    def showStatus(self, msg):
        '''Helper function called by any window to display a message
           in status bar.
        '''
        androconf.debug(msg)
        self.statusBar().showMessage(msg)

    def about(self):
        '''User clicked About menu. Display a Message box.'''
        QtGui.QMessageBox.about(self, "About Androguard GUI",
                "<p><b>Androguard GUI</b> is basically a GUI for Androguard :)." \
                "<br>Have fun !</p>")

    def setupSession(self):
        self.session = Session()

        self.fileLoadingThread = FileLoadingThread(self.session)
        self.connect(self.fileLoadingThread,
                QtCore.SIGNAL("loadedFile(bool)"),
                self.loadedFile)

    def loadedFile(self, success):
        if not success:
            self.showStatus("Analysis of %s failed :(" %
                    str(self.fileLoadingThread.file_path))
            return

        self.updateDockWithTree()
        self.cleanCentral()

        self.showStatus("Analysis of %s done!" %
                str(self.fileLoadingThread.file_path))

    def openFile(self, path=None):
        '''User clicked Open menu. Display a Dialog to ask which file to open.'''
        self.session.reset()

        if not path:
            path = QtGui.QFileDialog.getOpenFileName(self, "Open File",
                    '', "Android Files (*.apk *.jar *.dex *.odex *.dey);;Androguard Session (*.ag)")
            path = str(path[0])

        if path:
            self.setupTree()
            self.showStatus("Analyzing %s..." % str(path))
            self.fileLoadingThread.load(path)

    def addFile(self, path=None):
        '''User clicked Open menu. Display a Dialog to ask which APK to open.'''
        if not self.session.isOpen():
            return

        if not path:
            path = QtGui.QFileDialog.getOpenFileName(self, "Add File",
                    '', "Android Files (*.apk *.jar *.dex *.odex *.dey)")
            path = str(path[0])

        if path:
            self.showStatus("Analyzing %s..." % str(path))
            self.fileLoadingThread.load(path)

    def saveFile(self, path=None):
        '''User clicked Save menu. Display a Dialog to ask whwre to save.'''
        if not path:
            path = QtGui.QFileDialog.getSaveFileName(self, "Save File",
                    '', "Androguard Session (*.ag)")
            path = str(path[0])

        if path:
            self.showStatus("Saving %s..." % str(path))
            self.saveSession(path)

    def saveSession(self, path):
        '''Save androguard session.'''
        try:
            self.session.save(path)
        except RuntimeError, e:
            androconf.error(str(e))
            # http://stackoverflow.com/questions/2134706/hitting-maximum-recursion-depth-using-pythons-pickle-cpickle
            androconf.error("Try increasing sys.recursionlimit")
            os.remove(path)
            androconf.warning("Session not saved")

    def quit(self):
        '''Clicked in File menu to exit or CTRL+Q to close main window'''
        QtGui.qApp.quit()

    def closeEvent(self, event):
        '''Clicked [x] to close main window'''
        event.accept()

    def setupEmptyTree(self):
        '''Setup empty Tree at startup. '''
        if hasattr(self, "tree"):
            del self.tree
        self.tree = QtGui.QTreeWidget(self)
        self.tree.header().close()

    def setupDock(self):
        '''Setup empty Dock at startup. '''
        self.dock = QtGui.QDockWidget("Classes", self)
        self.dock.setWidget(self.tree)
        self.dock.setFeatures(QtGui.QDockWidget.NoDockWidgetFeatures)
        self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.dock)

    def setupTree(self):
        androconf.debug("Setup Tree")
        self.tree = TreeWindow(win=self, session=self.session)
        self.tree.setWindowTitle("Tree model")
        self.dock.setWidget(self.tree)

    def setupCentral(self):
        '''Setup empty window supporting tabs at startup. '''
        self.central = QtGui.QTabWidget()
        self.central.setTabsClosable(True)
        self.central.tabCloseRequested.connect(self.tabCloseRequestedHandler)
        self.central.currentChanged.connect(self.currentTabChanged)
        self.setCentralWidget(self.central)

    def tabCloseRequestedHandler(self, index):
        self.central.removeTab(index)

    def currentTabChanged(self, index):
        androconf.debug("curentTabChanged -> %d" % index)
        if index == -1:
            return # all tab closed

    def cleanCentral(self):
        #TOFIX: Removes all the pages, but does not delete them.
        self.central.clear()

    def setupFileMenu(self):
        fileMenu = QtGui.QMenu("&File", self)
        self.menuBar().addMenu(fileMenu)

        fileMenu.addAction("&Open...", self.openFile, "Ctrl+O")
        fileMenu.addAction("&Add...", self.addFile, "Ctrl+A")
        fileMenu.addAction("&Save...", self.saveFile, "Ctrl+S")
        fileMenu.addAction("E&xit", self.quit, "Ctrl+Q")

    def setupViewMenu(self):
        viewMenu = QtGui.QMenu("&View", self)
        self.menuBar().addMenu(viewMenu)

        viewMenu.addAction("&Strings...", self.openStringsWindow)

    def setupHelpMenu(self):
        helpMenu = QtGui.QMenu("&Help", self)
        self.menuBar().addMenu(helpMenu)

        helpMenu.addAction("&About", self.about)
        helpMenu.addAction("About &Qt", QtGui.qApp.aboutQt)

    def updateDockWithTree(self, empty=False):
        '''Update the classes tree. Called when
            - a new APK has been imported
            - a classe has been renamed (displayed in the tree)
        '''
        self.setupTree()
        self.tree.fill()


    def openStringsWindow(self):
        stringswin = StringsWindow(win=self, session=self.session)
        self.central.addTab(stringswin, stringswin.title)
        self.central.setTabToolTip(self.central.indexOf(stringswin), stringswin.title)
        self.central.setCurrentWidget(stringswin)

    def openBytecodeWindow(self, current_class, method=None):
        pass#self.central.setCurrentWidget(sourcewin)

    def openSourceWindow(self, current_class, method=None):
        '''Main function to open a .java source window
           It checks if it already opened and open that tab,
           otherwise, initialize a new window.
        '''
        androconf.debug("openSourceWindow for %s" % current_class)

        sourcewin = self.getMeSourceWindowIfExists(current_class)
        if not sourcewin:
            current_filename = self.session.get_filename_by_class(current_class)
            current_digest = self.session.get_digest_by_class(current_class)

            sourcewin = SourceWindow(win=self,
                                    current_class=current_class,
                                    current_title=current_class.current_title,
                                    current_filename=current_filename,
                                    current_digest=current_digest,
                                    session=self.session)
            sourcewin.reload_java_sources()
            self.central.addTab(sourcewin, sourcewin.title)
            self.central.setTabToolTip(self.central.indexOf(sourcewin), current_class.get_name())

        if method:
            sourcewin.browse_to_method(method)

        self.central.setCurrentWidget(sourcewin)

    def getMeSourceWindowIfExists(self, current_class):
        '''Helper for openSourceWindow'''
        for idx in range(self.central.count()):
            if current_class.get_name() == self.central.tabToolTip(idx):
                androconf.debug("Tab %s already opened at: %d" % (current_class.get_name(), idx))
                return self.central.widget(idx)
        return None

    def doesClassExist(self, path):
        arg = class2func(path)
        try:
            getattr(self.d, arg)
        except AttributeError:
            return False
        return True
