# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#    * Neither the name of the Christopher Gamble nor the names of its 
#      contributors may be used to endorse or promote products derived 
#      from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.

from twisted.web import resource, server
from txsockjs.protocols.base import Stub
from txsockjs.protocols.eventsource import EventSource
from txsockjs.protocols.htmlfile import HTMLFile
from txsockjs.protocols.jsonp import JSONP, JSONPSend
from txsockjs.protocols.static import Info, IFrame
from txsockjs.protocols.websocket import RawWebSocket, WebSocket
from txsockjs.protocols.xhr import XHR, XHRSend, XHRStream

class SockJSFactory(server.Site):
    def __init__(self, factory, options = None):
        server.Site.__init__(self, SockJSResource(factory, options))

class SockJSMultiFactory(server.Site):
    def __init__(self):
        server.Site.__init__(self, resource.Resource())
    
    def addFactory(self, factory, prefix, options = None):
        self.resource.putChild(prefix, SockJSResource(factory, options))

class SockJSResource(resource.Resource):
    def __init__(self, factory, options = None):
        resource.Resource.__init__(self)
        self._factory = factory
        self._sessions = {}
        self._options = {
            'websocket': True,
            'cookie_needed': False,
            'heartbeat': 25,
            'timeout': 5,
            'streaming_limit': 128 * 1024,
            'encoding': 'cp1252', #Latin1
            'sockjs_url': 'https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.js',
            'proxy_header': None
        }
        if options is not None:
            self._options.update(options)
        # Just in case somebody wants to mess with these
        self._methods = {
            'xhr': XHR,
            'xhr_send': XHRSend,
            'xhr_streaming': XHRStream,
            'eventsource': EventSource,
            'htmlfile': HTMLFile,
            'jsonp': JSONP,
            'jsonp_send': JSONPSend,
        }
        self._writeMethods = ('xhr_send','jsonp_send')
        # Static Resources
        self.putChild("info",Info())
        self.putChild("iframe.html",IFrame())
        self.putChild("websocket",RawWebSocket())
        # Since it's constant, we can declare the websocket handler up here
        self._websocket = WebSocket()
        self._websocket.parent = self
    
    def getChild(self, name, request):
        # Check if it is the greeting url
        if not name and not request.postpath:
            return self
        # Hacks to resove the iframe even when people are dumb
        if len(name) > 10 and name[:6] == "iframe" and name[-5:] == ".html":
            return self.children["iframe.html"]
        # Sessions must have 3 parts, name is already the first. Also, no periods in the loadbalancer
        if len(request.postpath) != 2 or "." in name or not name:
            return resource.NoResource("No such child resource.")
        # Extract session & request type. Discard load balancer
        session, name = request.postpath
        # No periods in the session
        if "." in session or not session:
            return resource.NoResource("No such child resource.")
        # Websockets are a special case
        if name == "websocket":
            return self._websocket
        # Reject invalid methods
        if name not in self._methods:
            return resource.NoResource("No such child resource.")
        # Reject writes to invalid sessions, unless just checking options
        if name in self._writeMethods and session not in self._sessions and request.method != "OPTIONS":
            return resource.NoResource("No such child resource.")
        # Generate session if doesn't exist, unless just checking options
        if session not in self._sessions and request.method != "OPTIONS":
            self._sessions[session] = Stub(self, session)
        # Delegate request to appropriate handler
        return self._methods[name](self, self._sessions[session] if request.method != "OPTIONS" else None)
    
    def putChild(self, path, child):
        child.parent = self
        resource.Resource.putChild(self, path, child)
    
    def setBaseHeaders(self, request, cookie=True):
        origin = request.getHeader("Origin")
        headers = request.getHeader('Access-Control-Request-Headers')
        if origin is None or origin == "null":
            origin = "*"
        request.setHeader('access-control-allow-origin', origin)
        request.setHeader('access-control-allow-credentials', 'true')
        request.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
        if headers is not None:
            request.setHeader('Access-Control-Allow-Headers', headers)
        if self._options["cookie_needed"] and cookie:
            cookie = request.getCookie("JSESSIONID") if request.getCookie("JSESSIONID") else "dummy"
            request.addCookie("JSESSIONID", cookie, path="/")
    
    def render_GET(self, request):
        self.setBaseHeaders(request,False)
        request.setHeader('content-type', 'text/plain; charset=UTF-8')
        return "Welcome to SockJS!\n"
