# This file is part of the Coral project.
# Please read the copyright notice in the LICENSE file.

from coral.modeler.dlg.SearchWindowDlg import SearchWindowDlg
#from coral.modeler.dlg.NameSearchletDlg import NameSearchletDlg
from coral.modeler.dlg.ExprSearchletDlg import ExprSearchletDlg
from coral.modeler.dlg.RegExpSearchletDlg import RegExpSearchletDlg
from coral.modeler.dlg.QuerySearchletDlg import QuerySearchletDlg
from coral.modeler.getFQName import getFQName,getFQTypeName
from coral.modeler.coralmodeler import CoralModeler,ContinuousModelObserver
from coral.modeler import iconcache

from coral.modeler.window import CoralActionHandler
from coral.modeler import CoralAction

import coral

from coral.modeler.editor import Editor

from qt import *
import re

from coral import log
logger=log.getLogger("searchwindow")


class SearchError( Exception ):
    pass

class ResultElementLI( QListViewItem ):

    def __init__( self, listview, element ):
        QListViewItem.__init__(self,listview,getFQName(element)+":"+getFQTypeName(element.__type__()))
        self.element = element



class SearchWindow( Editor, SearchWindowDlg, ContinuousModelObserver, CoralActionHandler ):

    SEARCH_STRIDE = 32
    IDLE,SEARCHING = range(2)
    

    def __init__( self, parent=None ):
        Editor.__init__(self)
        
        SearchWindowDlg.__init__(self,parent)
        CoralActionHandler.__init__(self)
        
        self.setIcon(iconcache.getIcon("find"))
        self.state = self.IDLE
        self.activeSearchlet = None
        self.generator = None
        
        layout = QVBoxLayout(self.resultFrame)
        self.results=[]
        self.resultList = QListView(self.resultFrame)

        # QuerySearchlet needs a tree structure
        self.resultList.setRootIsDecorated(1)
        self.resultList.setTreeStepSize(12)
        
        self.resultList.addColumn("Element")
        layout.add(self.resultList)
        self.connect(self.resultList,
                     SIGNAL("doubleClicked(QListViewItem *, const QPoint &, int )"),
                     self.selected)
        self.connect(self.resultList,
                     SIGNAL("returnPressed(QListViewItem *)"),
                     self.selected)

        self.statusLabel.setText("")
        
        self.createSearchlets([RegExpSearchlet(),
                               TypeSearchlet(),
                               ExprSearchlet(),
                               QuerySearchlet(),
                               ])


        
    def getMenuLabel( self ):
        return "Search Window..."


    def selected( self, li ):
        #self.emit(PYSIGNAL('selectElement'),(li.element,))
        #print "NFON",li.element
        self.notifyFocusOn(li.element)
    def createSearchlets( self, searchlets=[] ):        
        self.searchlets = searchlets
        while self.tabWidget.currentPage():
            self.tabWidget.removePage(self.tabWidget.currentPage())
        for searchlet in self.searchlets:
            w = searchlet.getWidget()
            frame = QVBox()
            if not w:
                label = QLabel("This search provides no widget",frame)
                label.setEnabled(False)
            else:
                frame.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,QSizePolicy.Fixed))
                w.reparent(frame,QPoint(0,0),False)

                # This is a hack: if there are any QLineEdit widgets in the
                # searchlet, connect their returnPressed signal to search.
                # This should really be done with AccelOverride events, but
                # stupid PyQt doesn't downcast events properly
                stack = [w]
                while stack:
                    c = stack.pop()
                    if isinstance(c,QLineEdit):
                        self.connect(c,SIGNAL("returnPressed()"),self.startSearch)
                    stack += c.children()

            bottom = QHBox(frame)
            bottom.layout().addItem(QSpacerItem(50,20,QSizePolicy.Expanding,QSizePolicy.Minimum))
            go = QPushButton("Go",bottom)
            self.connect(go,SIGNAL("clicked()"),self.toggleSearch)
            searchlet.SearchWindow_goButton = go
                
            self.tabWidget.addTab(frame,searchlet.getLabel())


    def onModelChanged( self, d ):
        self.generator = self.searchGenerator()


    def doIt( self ):
        if self.state==self.SEARCHING:
            try:
                for i in xrange(self.SEARCH_STRIDE):
                    e = self.generator.next()
                    result = self.activeSearchlet.criterion(e)
                    if result==True:
                        ResultElementLI(self.resultList,e)
                        self.results.append(e)
                    elif result == False:
                        continue
                    elif isinstance(result, (str, unicode) ):
                        self.n_errors += 1
                    
            except StopIteration:
                self.endSearch()


    def toggleSearch( self ):
        if self.state==self.IDLE:
            self.startSearch()
        else:
            self.endSearch()
            

    def startSearch( self , searchletLabel="", expression="" ):
        self.results=[]
        self.n_errors = 0
        
        searchlet=None

        # If it is called from a script using actions there wont be any tabWidget
        if searchletLabel:
            for s in self.searchlets:
                if searchletLabel==s.getLabel():
                    searchlet=s
        else:
            searchlet = self.searchlets[self.tabWidget.currentPageIndex()]

        try:
            searchlet.prepareSearch(expression)
        except SearchError,e:
            logger.error("Search error:"+unicode(e).encode('ascii','replace'))
            return
        
        self.resultList.setSorting(-1)
        self.resultList.setEnabled(True)
        self.resultList.clear()

        searchlet.SearchWindow_goButton.setText("Cancel")
        if searchlet.getWidget():
            searchlet.getWidget().setEnabled(False)

        self.state = self.SEARCHING
        self.activeSearchlet = searchlet
        self.generator = self.searchGenerator()

        CoralModeler.getInstance().addContinuousObserver(self)
        

    def endSearch( self,searchletLabel="" ):
        self.state = self.IDLE

        if searchletLabel:
            for s in self.searchlets:
                if searchletLabel==s.getLabel():
                    self.activeSearchlet=s

        if not self.activeSearchlet:
            return
        
        try:            
            self.activeSearchlet.SearchWindow_goButton.setText("Go")
            if self.activeSearchlet.getWidget():
                self.activeSearchlet.getWidget().setEnabled(True)

            self.activeSearchlet = None

            cc = self.resultList.childCount()
            errorstr = ", %d errors found" % self.n_errors
            if not cc:
                self.statusLabel.setText("No matching elements found" + errorstr)
                self.resultList.setEnabled(False)
            else:
                self.statusLabel.setText("%s matching elements%s" % (cc, errorstr) )
                self.resultList.setFocus()
                self.resultList.setCurrentItem(self.resultList.firstChild())
        finally:
            CoralModeler.getInstance().removeContinuousObserver(self)


    def searchGenerator( self ):
        searched = {}
        for s in coral.getAllProjects():
            if s in searched:
                continue
            searched[s] = True
            for r in s.contents():
                for e in r.__getAllParts__():
                    yield e

    def getSearchletLabels(self):
        """action"""
        labelList=[]
        for s in self.searchlets:
            labelList.append(s.getLabel())
        return labelList

    def getState(self):
        """action"""
        return self.state

    def getExpression(self,searchletLabel=None):
        """action"""
        for s in self.searchlets:
            if searchletLabel==s.getLabel():
                return s.getExpression()
        return ""

    def getResults(self):
        """action"""
        return self.results
    

    def initActions(self):
        return [CoralAction(self,"searchGetLabelList",
                            "search.getSearchletLabels",
                            "Get Labels",
                            QIconSet(iconcache.getIcon("misc")),
                            0),
                
                CoralAction(self,"searchStart",
                            "search.startSearch",
                            "Start Search",
                            QIconSet(iconcache.getIcon("misc")),
                            0),
    
                CoralAction(self,"searchStop",
                            "search.endSearch",
                            "Stop Search",
                            QIconSet(iconcache.getIcon("misc")),
                            0),
    
                CoralAction(self,"searchIsActive",
                            "search.getState",
                            "Get Search State",
                            QIconSet(iconcache.getIcon("misc")),
                            0),
                CoralAction(self,"searchGetExpression",
                            "search.getExpression",
                            "Get Current Experession",
                            QIconSet(iconcache.getIcon("misc")),
                            0),
                CoralAction(self,"searchGetResults",
                            "search.getResults",
                            "Get Search Results",
                            QIconSet(iconcache.getIcon("misc")),
                            0),
                ]

class Searchlet:

    def getLabel( self ):
        return ""
    
    def prepareSearch( self,expression="" ):
        pass
    
    def criterion( self, e ):
        """Returns true if @e matches, false if it doesn't.
        Returns error string otherwise."""
        return False

    def getWidget( self ):
        return None

    def getExpression( self ):
        return ""

##class NameSearchlet( Searchlet ):

##    def __init__( self ):
##        self.nameText = ""
##        self.widget = NameSearchletDlg()

##    def getLabel( self ):
##        return "Name"

##    def prepareSearch( self ):
##        self.nameText = unicode(self.widget.nameEdit.text())
    
##    def criterion( self, e ):
##        return (e.__hasSlotByName__("name") and e.name==self.nameText)

##    def getWidget( self ):
##        return self.widget

class RegExpSearchlet( Searchlet ):

    def __init__( self ):
        self.nameText = ""
        self.widget = RegExpSearchletDlg()

    def getLabel( self ):
        return "Name"

    def prepareSearch( self,expression="" ):
        if expression:
            self.nameText=expression
        else:
            self.nameText = unicode(self.widget.reEdit.text())
        try:
            self.pat = re.compile(self.nameText, re.IGNORECASE)
        except re.error, e:
            logger.error("Error in regular expression: "+str(e))
            QMessageBox.critical(self.widget,
                                 "Regular Expression Error",
                                 "Please revise your regular expression:\n" + str(e))

    def criterion( self, e ):
        return (e.__hasSlotByName__("name") and bool(re.match(self.pat, unicode(e.name))))#e.name==self.nameText)

    def getWidget( self ):
        return self.widget

    def getExpression( self ):
        return self.nameText

class TypeSearchlet( Searchlet ):

    def __init__( self ):
        self.widget = ExprSearchletDlg()
        self.typeText = ""
        
    def getLabel( self ):
        return "Type Name"
    
    def prepareSearch( self,expression="" ):
        if expression:
            self.typeText=expression
        else:
            self.typeText = unicode(self.widget.exprEdit.text())
        self.type = str(self.typeText)
        
    def criterion( self, e ):
        return e.__type__().getName() == self.type

    def getWidget( self ):
        return self.widget

    def getExpression( self ):
        return self.typeText
    
class ExprSearchlet( Searchlet ):

    def __init__( self ):
        self.widget = ExprSearchletDlg()
        self.exprText = ""
        
    def getLabel( self ):
        return "Expression"
    
    def prepareSearch( self,expression="" ):
        if expression:
            self.exprText=expression
        else:
            self.exprText = unicode(self.widget.exprEdit.text())
        try:
            g = {}
            exec("import coral; f = lambda self:"+self.exprText,g)
            self.exprFunc = g['f']
        except Exception,ex:
            raise SearchError(unicode(ex))

        
    def criterion( self, e ):
        try:
            return self.exprFunc(e)
        except Exception,ex:
            s = "Search error:" + unicode(ex).encode('ascii','replace')
            logger.error(s)
            return s

    def getWidget( self ):
        return self.widget

    def getExpression( self ):
        return self.exprText


class QuerySearchlet( Searchlet, QuerySearchletDlg ):
    def __init__(self):
        QuerySearchletDlg.__init__(self)
        self.query=None
        
    def getLabel( self ):
        return "Query"
    
    def prepareSearch( self, query ):
        if self.query == None:
            raise SearchError(unicode("Query not selected"))
        
    
    def criterion( self, e ):
        #Only search in root elements
        if e.__parent__() != None:
            return False

        #### Results are added here
        result_list=self.parent().parent().parent().parent().resultList
        for match in coral.util.search.cquery.csearch(self.query, e, self.checkBox1.isOn()):
            QueryMatch(result_list, match, e)

        # Always return false
        return False


    def getWidget( self ):
        return self

    def getExpression( self ):
        return str(query)

    def cmp_elem_name(self, a, b):
        if not a.name and not b.name:
            return 0
        if not a.name:
            return 1
        if not b.name:
            return -1
        
        return cmp(a.name,b.name)
        
    def update_query_box(self):
        self.query=None
        self.querySelect.clear()
        self.queryElements=[]
        for project in coral.getAllProjects():
            for root in project.contents():
                for elem in root.__getAllParts__().select(lambda p: p.__type__() == coral.lang.CQUERY10.Query):
                    self.queryElements.append(elem)

        self.queryElements.sort(self.cmp_elem_name)
        
        for query in self.queryElements:
            if query.name:
                self.querySelect.insertItem(unicode(query.name)+" (Query)")
            else:
                self.querySelect.insertItem("(noname) (Query)")

        #Select the first query by default
        if self.queryElements:
            self.query=self.queryElements[0]
            
    def selectedQuery(self, num):
        self.query=self.queryElements[num]
        
class QueryMatch( QListViewItem ):
    def __init__(self,parent,match,element):
        QListViewItem.__init__(self,parent,"match in "+str(element))
        self.element=element
        for k,v in match.items():
            QueryMapping(self, k, v)



class QueryMapping( QListViewItem ):
    def __init__(self, parent, query_elem, matched_elem):
        slot=query_elem.__getSlotByOppositeName__(coral.lang.CQUERY10.QueryElement,"abstractElement")
        QListViewItem.__init__(self,parent,str(slot[0].name)+"->"+str(matched_elem))
        self.element=matched_elem
    
