#
# This file is part of GNU Enterprise.
#
# GNU Enterprise is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 3, or (at your option) any later version.
#
# GNU Enterprise is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with program; see the file COPYING. If not,
# write to the Free Software Foundation, Inc., 59 Temple Place
# - Suite 330, Boston, MA 02111-1307, USA.
#
# Copyright 2000-2009 Free Software Foundation
#
# FILE:
# uidrivers/html/GFServer.py
#
# DESCRIPTION:
#
# NOTES:

import pickle
import Queue
import threading, thread

from gnue.common import events

from UISplashScreen import SplashScreen
from gnue.forms.GFInstance import GFInstance
from gnue.common.apps import GConfig, errors
from gnue.forms.uidrivers.html.common import textEncode, textDecode
import Generator


# =============================================================================
# GNUe Forms HTML UI driver -- Session (contains one instance)
# =============================================================================

class gnueSession:
    '''
    Each Session is represented by a gnueSession object. A session holds the following
    variables:
       identifier     - session id
       backend_state  - one of (0=idle, 1=hasContenttoDeliver, 2=willshortlyDeliverContent,
                                3=waiting_for_additional_request, 4=shutting_down

       new HTML request will be delayed for a short time,

    '''
    IDLE_STATE = 0
    LOADING_STATE = 1
    BUSY_STATE = 2
    WILL_DELIVER = 3
    LATE_DELIVERY = 4
    SHUTTING_DOWN = 9

    instance = None
    # split updates between different forms
    _updates = []
    _state = LOADING_STATE

    _max_session_threads = 3 # maximum number of threads allowed to be executed in the backend in parallel

    def __init__(self, controller, id):
        assert gDebug (4, "Creating new session")
        self._id=id
        self._controller = controller
        self._state_lock = threading.Lock()
        self._update_lock = threading.Lock()
        self._usageCounter = threading.Semaphore(self._max_session_threads)

        # is just one event sufficient ?
        # TODO: check, What happens if two requests come in in parallel?
        # use a per thread event instead?
        self.updates_available = threading.Event()

    def setup(self, baseform, userParameters):

        # Create the instance that will control the loaded form(s) for this session
        assert gDebug (4, "Creating GFInstance object")
        # HACK: there should be a better way to get ui and connection object
        gfclient=self._controller.gfclient

        self.instance = GFInstance (self, gfclient.connections, gfclient._ui,
                                    False, userParameters)

        # Assign the proper login handler based upon the user interface choice
        # TODO: we need a login handler per session! How can we achieve this?
        # by separate connection pools?
        loginHandler = gfclient._ui.UILoginHandler ()
        loginHandler.uiDriver = self.instance._uiinstance
        gfclient.getConnectionManager ().setLoginHandler (loginHandler)

        # disable standart html drivers mainloop
        gfclient._ui.GFhtmlApp.GFhtmlApp._mainLoop = 1

        self.form=self.instance.run_from_file(baseform, parameters=None)
        self._setState(self.IDLE_STATE)
        print "GNUe Forms Session %s setup" % self._id

        assert gLeave (4)

    def executeCmd(self, cmd, args):

        assert self.instance != None
        if self.getState()==self.SHUTTING_DOWN:
            pass

        self._usageCounter.acquire()

        self._setState(self.WILL_DELIVER)

        updates=[]

        try:

            if cmd == "event":
                # TODO: directly call event functions
                # id = int(args["id"])
                event = args["event"]
                e = events.Event(event, \
                     _form=self.instance._main_form)
                self.instance.dispatchEvent(e)

            elif cmd == "onChange":
                id = int(args["id"])
                text = args["text"]
                if hasattr(args,"pos"):
                    position = args["pos"]
                else:
                    position = 0

                UIObject = self.instance._uiinstance._WidgetToUIObj [id]
                # FIXME: take care that id matches the current index
                UIObject._on_text_changed (text, position)

            elif cmd == "onFocus":
                id = int(args["id"])
                UIObject = self.instance._uiinstance._WidgetToUIObj [id]
                UIObject._on_focus_in (id)

            elif cmd == "btn":
                id = int(args["id"])

                gfObject = self.instance._uiinstance._WidgetToGFObj [id]
                gfObject._event_fire ()

            elif cmd == "pagechange":
                page = args["page"]
                id = int(args["id"][5:]) # convert "page_1" to 1
                self.form.uiWidget._on_switch_page(id)

            else:
                updates = [["status","","Unknown command '%s' was called!" % cmd]]
        except Exception, e:
            try:
                if hasattr(e,"getDetail"):
                    detail = e.getDetail()
                else:
                    import sys, traceback, string
                    detail = u"Non GNUE Traceback:"
                    detail += u"%s" % string.join(traceback.format_exception (*sys.exc_info ()))

                updates = [["alert","",u"Error %s \n\n %s" % (e,detail)]]
            except Exception, e:
                updates = [["alert","",u"Error in Exception handling %s" % e]]

        # refresh updates
        updates.extend(self.form.uiWidget.get_updates())
        print "pushing updates %s" % updates
        self._pushUpdates(updates)
        self.updates_available.set()  # inform listeners, that new updates are available
        self.updates_available.clear()
        self._usageCounter.release()

    def getHtml(self, form):
        print "getHtml was called for form '%s'." % form
        if self._state==self.LOADING_STATE:
            return Generator.build_loading_page("Please wait until loaded")
        else:
            # FIXME: do we need to acquire a semaphore here?
            self._usageCounter.acquire()
            html =  self.form.uiWidget.get_html()
            self._usageCounter.release()
            return html

    def getState(self):
        return self._state

    def fetchUpdates(self):
        self._update_lock.acquire()
        updates = self._updates
        self._updates = []
        print "getting updates %s" % updates
        self._update_lock.release()
        return updates

    def _setState(self, state):
        self._state_lock.acquire()
        self._state=state
        self._state_lock.release()

    def _pushUpdate(self, update):
        self._update_lock.acquire()
        self._updates.append(update)
        self._update_lock.release()

    def _pushUpdates(self, updates):
        self._update_lock.acquire()
        self._updates.extend(updates)
        self._update_lock.release()

class backendThread ( threading.Thread ):
    '''
    This type of thread is changeing gnue forms and executing specific actions.

    It is different from the frontendthread (as started by the webserver) that it
    will just trigger actions. The task of the frontendthread is pass commands from
    the client to the backendthread and to check if any UI updates or UI commands should be
    passed back to the frontend.
    '''
    shutdown = False
    job_pool = None

    def run ( self ):

        while not self.shutdown:
            job = self.job_pool.get()

            if job != None:
                print "Doing backendJob %s" % job
                job[0].executeCmd(job[1],job[2])
                print "Job done: %s" % job

# =============================================================================
# GNUe Forms HTML UI driver -- ComController (Singleton pattern)
# =============================================================================


class ComController:
    '''
    This controller should control how the incoming request from the web app are computed.

    A list of commands should require the form to wait for an answer and not allow further commands or
    input. At least Commit, Search, ... should have this behavior.

    # todo: add garbage collector,  add session time out to each session

    '''

    __single = None

    max_threads = 2
    threads = []    # create threadpool to handle jobs
    backend_jobs = None

    sessions = {}        # create sessions
    base_form = ""

    def __init__( self ):
        """
        """
        # Singleton pattern
        if ComController.__single:
            raise ComController.__single
        ComController.__single = self

        # create queue to transfer data to backend tasks
        self.backend_jobs = Queue.Queue ( 0 )

        # TODO: convert fixed size threadpool into dynamic pool, i.e. just startup threads if required
        x=0
        while x<self.max_threads:
            x+=1
            th=backendThread()
            self.threads.append(th)
            th.setName("Backend_%s" % x)
            th.job_pool = self.backend_jobs
            th.start()

    def pullHtml(self, session_id, form):
        if not self.sessions.has_key(session_id):
            return Generator.build_error_page("restart", "Wrong session or Session timed out!")

        # TODO: get_html call should be thread safe and shouldn't change anything
        if form=="base":
            response=Generator.build_basic_dialog()
        else:
            return self.sessions[session_id].getHtml(form)

        return response


    def handleRequest(self, session_id, command, arguments):
        if not self.sessions.has_key(session_id):
            print "Request for invalid session '%s'!" % session_id
            return  [["status","","This session is invalid. Please restart!"]]

        # TODO: check for health of thread pool, and if necessary startup new threads or print warning
        print "processing %s - %s" % (command, arguments)
        self.backend_jobs.put([self.sessions[session_id], command, arguments])
        self.sessions[session_id].updates_available.wait(5)  # 5 sec
        # return results
        return self.pullCommands(session_id)


    def pullCommands(self, session_id):
        return self.sessions[session_id].fetchUpdates()

    def startSession(self, session_id, params):
        self.sessions[session_id] = gnueSession(self, session_id)

        # TODO make setup to backendtasks
        self.sessions[session_id].setup(self.base_form, params)
        # self.backend_jobs.put([self.sessions[session_id], "setup"]


    def delSession(self, session_id):
        # TODO check for still working tasks, use locking
        del self.sessions[session_id]
        # self.gfclient.getConnectionManager ().closeAll ()

    def shutdown(self, urgency):
        '''

        @param urgency: 0=high (System will shutdown at once t<10sec)
                        1=standart (normal shutdown)
                        2=low (system will not accept new clients, but will shutdown
        '''

        for th in threads:
            th.shutdown=True
