#!/usr/bin/env python
'''
Python based wizard implemented on top of the webkit in QT.
We start a HTTP listener on a random port; and then launch a WebKit panel against this.
Content is fed from the staticcontent folder.

Responses to GET's come from the staticcontent folder
'''
import os
import sys
import shutil
import string
import random
import json
import SimpleHTTPServer
import SocketServer
import threading
import types
import urlparse
import mimetypes

from PyQt4.QtWebKit import QWebView, QWebSettings, QWebPage
from PyQt4.QtGui import QApplication
from PyQt4.QtCore import QUrl

global enableLogs
enableLogs = False

class WizardContext:
    ''' Context for the wizard. We use the same object for the entire wizard
    This also implements a bunch pattern; all keyword arguments are accessible as attributes of the context.
    '''
    def __init__(self, wizard, steps, contentFolder, **kwargs):
        self.currentStep = 0
        self.data = {}
        self.wizard = wizard
        self.steps = steps
        self.contentFolder = contentFolder
        self.UIsteps = []
        # If this is true, we are running a silent install and no UI's should be shown.
        # All the needed variables are expected to come from the file used to drive the silent install
        self.silentInstall = False
        # Bunch pattern; all keywords are added as attributes of this wizard context instance. 
        self.__dict__.update(kwargs)

    def nextStep(self):
        self.currentStep = self.currentStep + 1
        if enableLogs:
            print "Next step in WizardContext", self.currentStep;
    def previousStep(self):
        self.currentStep = self.currentStep - 1
        if enableLogs:
            print "Previous step in WizardContext", self.currentStep;
    def skipNextStep(self):
        self.currentStep = self.currentStep + 2
        if enableLogs:
            print "Skipping next step in WizardContext", self.currentStep;
    def gotToStep(self, stepId):
        self.currentStep = stepId
        if enableLogs:
            print "Going to step in WizardContext", self.currentStep;
    def pushUIStep(self):
        '''The current step generated UI; so push it to the UI stack'''
        if self.currentStep not in self.UIsteps:
            self.UIsteps.append(self.currentStep)
    def previousUIStep(self):
        self.UIsteps.pop()
        self.currentStep = self.UIsteps[-1]
    def resetPreviousSteps(self):
        self.UIsteps = []
    def resetForSilentInstall(self, initialSteps):
        '''Reset the wizardContext for the next IOC/Item in a silent install.
        Be very careful to pass in copies of the steps list; otherwise we'll keep adding steps to the same list.
        This will make the list grow continuously and you'll get bizarre results.
        '''
        self.steps = initialSteps
        self.currentStep = 0
        self.UIsteps = []


class TemplateWithValues:
    '''Every action returns one of these. 
    The templateName is relative to the rootFolder for the wizard; contains a python String template.
    values is a name/value dict used in template replacement'''
    def __init__(self, templateName, values, wizardContext):
        self.templateName = templateName
        self.values = values
        self.context = wizardContext
    def fetchTemplateAndGenerateStaticContent(self, rootFolder):
        # We first offer the client the chance to override templates.
        contentFileName = os.path.join(self.context.contentFolder, self.templateName)
        if os.path.exists(contentFileName):
            if enableLogs:
                print "Serving template from client", contentFileName
            return self.templateReplace(contentFileName)
        contentFileName = os.path.join(os.path.dirname(__file__),  "wizardcontent", self.templateName)
        if os.path.exists(contentFileName):
            if enableLogs:
                print "Serving template from myself", contentFileName
            return self.templateReplace(contentFileName)
        raise Exception("Cannot find template " + self.templateName)
        
    def templateReplace(self, contentFileName):
        with open(contentFileName, 'r') as f:
            data = f.read()
            if enableLogs:
                print "List of previous UI steps", self.context.UIsteps
            valscopy = self.values.copy()
            valscopy.update(self.context.data)
            valscopy['currentStep'] = self.context.currentStep
            valscopy['previousStepExists'] = True if len(self.context.UIsteps) > 1 else False
            return data.replace("//{{jsvars}}", "var serverState = " + json.dumps(valscopy) + ";" )
        

class WizardStep:
    ''' Wizards are a list of steps; this represents such a step. 
    There are various type of steps; to create a wizard, you create a sequence of wizardsteps.
    '''
    def enter(self, wizardContext):
        '''Execute this step; when executing the step; the context's current step will be set to this step.'''
        pass

class CallableWizardStep(WizardStep):
    '''This lets you implement business logic using a step.
    Construct using a function and this will be called as entry into this step'''
    def __init__(self, callable):
        self.callable = callable
    def enter(self, wizardContext):
        return self.callable(wizardContext)
            

class MyHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    wizard = None
    def  do_GET(self):
        if self.path.startswith("/wizard.html"):
            content = next(self.wizard.execute())
            if content:
                self.send_response(200)
                self.send_header("Content-type", "text/html")
                self.send_header("Content-length", len(content))
                self.end_headers()
                self.wfile.write(content)
            else:
                if enableLogs:
                    print "All done. Shutting down the wizard"
                    print self.wizard.context.data
                self.browser.close()
        elif self.path.startswith("/updateState"):
            query_components = urlparse.parse_qs(urlparse.urlparse(self.path).query)
            for (k,v) in query_components.iteritems():
                if enableLogs:
                    print "Setting server state {0} to {1} in context".format(k, v[0])
                self.wizard.context.data[k] = v[0]
            self.sendOk()
        elif self.path.startswith("/addToState"):
            query_components = urlparse.parse_qs(urlparse.urlparse(self.path).query)
            for (k,v) in query_components.iteritems():
                if enableLogs:
                    print "Appending to server state {0} the value {1} in context".format(k, v[0])
                if k not in self.wizard.context.data:
                    self.wizard.context.data[k] = []
                self.wizard.context.data[k].append(v[0])
            self.sendOk()
        elif self.path.startswith("/nextStep"):
            if enableLogs:
                print "Client made call to go to next step"
            self.wizard.context.nextStep()
            self.sendOk()
        elif self.path.startswith("/previousStep"):
            if enableLogs:
                print "Client made call to go to previous step"
            self.wizard.context.previousUIStep()
            self.sendOk()
        else:
            contentFileName = os.path.join(os.path.dirname(__file__),  "staticcontent" + urlparse.urlparse(self.path).path)
            if(os.path.exists(contentFileName)):
                if enableLogs:
                    print "Serving", contentFileName
                self.sendFile(contentFileName)
                return
            contentFileName = os.path.join(self.wizard.context.contentFolder, "staticcontent" + urlparse.urlparse(self.path).path)
            if(os.path.exists(contentFileName)):
                if enableLogs:
                    print "Serving", contentFileName
                self.sendFile(contentFileName)
                return
            
            
    def sendOk(self):
        content = "Ok"
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-length", len(content))
        self.end_headers()
        self.wfile.write(content)
        
    def sendFile(self, contentFileName):
        with open(contentFileName, 'r') as f:
            self.send_response(200)
            self.send_header("Content-type", mimetypes.guess_type(contentFileName)[0])
            self.send_header("Content-length", os.path.getsize(contentFileName))
            self.end_headers()
            shutil.copyfileobj(f, self.wfile)
    def log_message(self, format, *args):
        if enableLogs:
            sys.stderr.write("%s - - [%s] %s\n" % (self.address_string(), self.log_date_time_string(), format%args))
        


class SeparateThreadHTTPServer:
    def startServer(self, PORT, wizard, browser):
        def launchHTTPServer(httpd):
            httpd.serve_forever()
        self.handler = MyHTTPHandler
        self.handler.wizard = wizard
        self.handler.browser = browser
        self.httpd = SocketServer.TCPServer(("", PORT), self.handler)
        print "HTTP server serving at port", PORT
        self.t1 = threading.Thread(target=launchHTTPServer, args=(self.httpd,))
        self.t1.start()
    def shutdown(self):
        self.httpd.shutdown()
        self.t1.join(10)

def showConsoleLogInTerminal(browser):
    ''' Display the browser log on the terminal. However, this seems to cause a core dump on exit'''
    class MyWebPage(QWebPage):
        def javaScriptConsoleMessage(self, msg, line, source):
            print '%s line %d: %s' % (source, line, msg)
    page = MyWebPage()
    browser.setPage(page)   

class TemplateWithValuesWizardStep(WizardStep):
    def __init__(self, pageName, values):
        self.pageName = pageName
        self.values = values
    def enter(self, wizardContext):
        return TemplateWithValues(self.pageName, self.values, wizardContext)

class Wizard:
    ''' Implements a web based wizard using a QTWebView.
    Pass in a list of steps and a folder with static content
    In addition, the keyword arguments into the wizard are passed to the wizardContext which implements a bunch pattern.
    '''
    def __init__(self, title, contentFolder, steps, verbose=False, **kwargs):
        global enableLogs
        self.context = WizardContext(self, steps, contentFolder, **kwargs)
        self.context.data['wizardTitle'] = title
        self.showConsoleOnTerminal = False
        enableLogs = verbose
    def execute(self):
        while self.context.currentStep < len(self.context.steps):
            curStepId = self.context.currentStep
            if enableLogs:
                print "Executing stepid", curStepId 
            curStep = self.context.steps[curStepId]
            print "Executing step #{0} - {1}".format(curStepId, curStep.__class__.__name__) 
            curTemplate = curStep.enter(self.context)
            if curTemplate == True:
                if enableLogs:
                    print "Step", curStepId, "returned True; moving on to the next step"
                self.context.nextStep()
                continue
            else:
                if self.context.silentInstall:
                    if enableLogs:
                        print "Skipping a UI step for silent install"
                    self.context.nextStep()
                    continue

                self.context.pushUIStep()
                yield curTemplate.fetchTemplateAndGenerateStaticContent(self.context.contentFolder)
        yield None
    def launchWizard(self):
        if self.context.silentInstall:
            next(self.execute())
            return
        
        PORT = random.randrange(15000, 16000)
        app = QApplication(sys.argv)
        QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
        browser = QWebView()
        browser.setFixedSize(800, 640);
        # Uncomment the following line if you want to see the console logs in the terminal.
        # Note that this causes QT to dump core.
        if self.showConsoleOnTerminal:
            showConsoleLogInTerminal(browser)
        
        server = SeparateThreadHTTPServer()
        server.startServer(PORT, self, browser)
        
        browser.load(QUrl("http://localhost:" + str(PORT) + "/wizard.html"))
        browser.show()
        app.exec_()
    
        server.shutdown()
        if enableLogs:
            print "Shutdown the server final context data follows"
            print json.dumps(self.context.data, sort_keys=True, indent=4, separators=(',', ': '))


    
    
if __name__ == '__main__':
    Wizard.launchWizard()