#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
| This file is part of the web2py Web Framework
| Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>,
| limodou <limodou@gmail.com> and srackham <srackham@gmail.com>.
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)

Debugger support classes
------------------------
"""

import logging
import pdb
import sys
from gluon._compat import Queue

logger = logging.getLogger("web2py")


class Pipe(Queue.Queue):
    def __init__(self, name, mode='r', *args, **kwargs):
        self.__name = name
        Queue.Queue.__init__(self, *args, **kwargs)

    def write(self, data):
        logger.debug("debug %s writing %s" % (self.__name, data))
        self.put(data)

    def flush(self):
        # mark checkpoint (complete message)
        logger.debug("debug %s flushing..." % self.__name)
        self.put(None)
        # wait until it is processed
        self.join()
        logger.debug("debug %s flush done" % self.__name)

    def read(self, count=None, timeout=None):
        logger.debug("debug %s reading..." % (self.__name, ))
        data = self.get(block=True, timeout=timeout)
        # signal that we are ready
        self.task_done()
        logger.debug("debug %s read %s" % (self.__name, data))
        return data

    def readline(self):
        logger.debug("debug %s readline..." % (self.__name, ))
        return self.read()


pipe_in = Pipe('in')
pipe_out = Pipe('out')

debugger = pdb.Pdb(completekey=None, stdin=pipe_in, stdout=pipe_out,)


def set_trace():
    """breakpoint shortcut (like pdb)"""
    logger.info("DEBUG: set_trace!")
    debugger.set_trace(sys._getframe().f_back)


def stop_trace():
    """stop waiting for the debugger (called atexit)"""
    # this should prevent communicate is wait forever a command result
    # and the main thread has finished
    logger.info("DEBUG: stop_trace!")
    pipe_out.write("debug finished!")
    pipe_out.write(None)
    #pipe_out.flush()


def communicate(command=None):
    """send command to debbuger, wait result"""
    if command is not None:
        logger.info("DEBUG: sending command %s" % command)
        pipe_in.write(command)
        #pipe_in.flush()
    result = []
    while True:
        data = pipe_out.read()
        if data is None:
            break
        result.append(data)
    logger.info("DEBUG: result %s" % repr(result))
    return ''.join(result)


# New debugger implementation using dbg and a web UI

import gluon.contrib.dbg as c_dbg
from threading import RLock

interact_lock = RLock()
run_lock = RLock()


def check_interaction(fn):
    """Decorator to clean and prevent interaction when not available"""
    def check_fn(self, *args, **kwargs):
        interact_lock.acquire()
        try:
            if self.filename:
                self.clear_interaction()
                return fn(self, *args, **kwargs)
        finally:
            interact_lock.release()
    return check_fn


class WebDebugger(c_dbg.Frontend):
    """Qdb web2py interface"""

    def __init__(self, pipe, completekey='tab', stdin=None, stdout=None):
        c_dbg.Frontend.__init__(self, pipe)
        self.clear_interaction()

    def clear_interaction(self):
        self.filename = None
        self.lineno = None
        self.exception_info = None
        self.context = None

    # redefine Frontend methods:

    def run(self):
        run_lock.acquire()
        try:
            while self.pipe.poll():
                c_dbg.Frontend.run(self)
        finally:
            run_lock.release()

    def interaction(self, filename, lineno, line, **context):
        # store current status
        interact_lock.acquire()
        try:
            self.filename = filename
            self.lineno = lineno
            self.context = context
        finally:
            interact_lock.release()

    def exception(self, title, extype, exvalue, trace, request):
        self.exception_info = {'title': title,
                               'extype': extype, 'exvalue': exvalue,
                               'trace': trace, 'request': request}

    @check_interaction
    def do_continue(self):
        c_dbg.Frontend.do_continue(self)

    @check_interaction
    def do_step(self):
        c_dbg.Frontend.do_step(self)

    @check_interaction
    def do_return(self):
        c_dbg.Frontend.do_return(self)

    @check_interaction
    def do_next(self):
        c_dbg.Frontend.do_next(self)

    @check_interaction
    def do_quit(self):
        c_dbg.Frontend.do_quit(self)

    def do_exec(self, statement):
        interact_lock.acquire()
        try:
            # check to see if we're inside interaction
            if self.filename:
                # avoid spurious interaction notifications:
                self.set_burst(2)
                # execute the statement in the remote debugger:
                return c_dbg.Frontend.do_exec(self, statement)
        finally:
            interact_lock.release()

# create the connection between threads:

parent_queue, child_queue = Queue.Queue(), Queue.Queue()
front_conn = c_dbg.QueuePipe("parent", parent_queue, child_queue)
child_conn = c_dbg.QueuePipe("child", child_queue, parent_queue)

web_debugger = WebDebugger(front_conn)                                     # frontend
dbg_debugger = c_dbg.Qdb(pipe=child_conn, redirect_stdio=False, skip=None)   # backend
dbg = dbg_debugger

# enable getting context (stack, globals/locals) at interaction
dbg_debugger.set_params(dict(call_stack=True, environment=True))

import gluon.main
gluon.main.global_settings.debugging = True
