import spidermonkey
from xml.dom import minidom
import os
import sys
import traceback
import py
import dom

here = py.magic.autopath().dirpath()

# exception definition, raised when there's a problem evaluating the JS, in
# which case the JSPyError below is used as a container for the exception data
class PyJSError(Exception):
    def __init__(self, name, message, stack):
        self.args = (name, message, stack)
        self.name = name
        self.message = message
        self.stack = stack


class JSPyError:
    def __init__(self, name, message, stack):
        self.args = (name, message, stack)
        self.name = name
        self.message = message
        self.stack = stack.split('\n')


def get_exception():
    exc, e, tb = sys.exc_info()
    ret = JSPyError(str(exc), str(e), ''.join(traceback.format_tb(tb)))
    del tb
    return ret

# objects which are passed to the JS context - note that extra care must be
# taken to catch exceptions, as an exception raised here results in the script
# being stopped entirely, without raising a JS exception
class window:
    navigator = {
        'appName': 'Netscape',
        'appVersion': '5.0 (X11; en-US)',
        'appCodeName': 'Mozilla',
        'platform': 'Python',
    }

    events = []

    def __init__(self, browser, html):
        self._browser = browser
        self._doc = doc = minidom.parseString(html)
        self.document = dom.NodeWrapper(self, doc)
        titleels = doc.getElementsByTagName('title')
        if titleels:
            self.title = dom.text_from_node(titleels[0])
        else:
            self.title = title
        scripttags = doc.getElementsByTagName('script')
        for st in scripttags:
            if st.hasAttribute('src'):
                browser.load(st.getAttribute('src'))
            else:
                content = dom.text_from_node(st)
                browser.eval(context)

    def printline(self, *args):
        msg = []
        for a in args:
            if isinstance(a, unicode):
                a = a.encode('UTF-8')
            else:
                a = str(a)
            print a,


class document:
    def __init__(self, window, doc):
        self._window = window
        self._doc = dom.NodeWrapper(self, doc)

    def __getattr__(self, name):
        return getattr(self._doc, name)


class fakebrowser:
    def __init__(self, webrootpath, webpath):
        self.webrootpath = webrootpath
        self.webpath = webpath
        self.runtime = spidermonkey.Runtime()
        self.context = self.runtime.new_context()

        self.context.bind_class(fakebrowser, bind_constructor=False)
        self.context.bind_class(JSPyError, bind_constructor=True)
        self.context.bind_object('__fakebrowser__', self)

    def initwindow(self, html=None):
        self.currpath = cpath = self.webpath.split('/')
        self.htmlfile = hfile = cpath.pop()
        if not hfile:
            self.htmlfile = hfile = 'index.html'
        if not html:
            try:
                html = self.webrootpath.join(*cpath).join(hfile).read()
            except py.error.ENOENT:
                html = '<html><body></body></html>'
        self.context.bind_class(window, bind_constructor=False)
        self.context.bind_class(document, bind_constructor=False)
        self.context.bind_class(dom.NodeWrapper, bind_constructor=True)
        self.context.bind_class(dom.DOMFunctionWrapper, bind_constructor=True)
        self.window = w = window(self, html)
        self.context.bind_object('__window', w)
        self.context.bind_object('__document', w.document)
        self.eval(here.join('fakebrowser.js').read())

    def load(self, path):
        """ load and evaluate a JS file

            path is a web-path, either relative from the 'current position'
            (the web page's parent in case this is called directly, the
            JS file's parent in case it's called from a JS file using load())
            or absolute from the web root
        """
        items = path.split('/')
        if items[0] != '':
            items = self.currpath + items
        abspath = self.webrootpath.join(os.path.sep.join(items))
        old_currpath = self.currpath
        self.currpath = abspath.dirpath().strpath.split('/')
        print 'reading file', path, ', currpath', '/'.join(self.currpath)
        self.eval(abspath.read())
        self.currpath = old_currpath

    def eval(self, js):
        s = ('''
            try {
                eval(%r);
            } catch(e) {
                var s = e.toString();
                if (s.indexOf(': ') > -1) {
                    var namemsg = e.toString().split(': ');
                    new JSPyError(namemsg[0], namemsg[1], e.stack || '');
                };
            };
        ''' % (js,))
        ret = self.context.eval_script(s)
        if isinstance(ret, JSPyError):
            raise PyJSError(ret.name, ret.message, ret.stack)
        return ret

