from django.conf import settings
from django.template import TemplateDoesNotExist
from django.template.loader import find_template_source
from django.core.cache import cache
from subprocess import Popen, PIPE
import sys, os, threading

def _getdefault(name, default=None):
    try:
        default = getattr(settings, name)
    except: pass
    return default

## these should be set in the settings.py file
PHP_BIN = _getdefault('PHP_BIN', '/usr/bin/php')
PHP_ARGS = _getdefault('PHP_ARGS', ['-q',])
PHP_IN_SHELL = _getdefault('PHP_IN_SHELL', True)
PHP_THREAD_IO = _getdefault('PHP_THREAD_IO', sys.platform == 'win32')
PHP_BUFFER_SIZE = _getdefault('PHP_BUFFER_SIZE',
                              sys.platform == 'win32' and -1 or 4096)
PHP_CACHE_SECONDS = _getdefault('PHP_CACHE_SECONDS', None)

class PipeThread(threading.Thread):
    """This is needed for Win32 where the buffer can lock if you
    pass the windows shell buffer size and do not read the output pending
    from the running app"""
    def __init__(self, fin, mode='read'):
        self.fin = fin
        self.sout = ""
        self.mode = mode
        threading.Thread.__init__(self)
    def run(self):
        if self.mode == 'read':
            self.sout = self.fin.read()
        else:
            self.fin.write(self.sout)
    def read(self):
        return self.sout
    def write(self, data):
        self.sout = data
    def close(self):
        ## WARNING! This can cause a crash on windows depending on the FD
        ##          If its an stdout or stderr FD then this will crash
        if self.mode != 'read':
            self.fin.close()
        
def runphp(source):
    php = Popen([PHP_BIN,] + PHP_ARGS, shell=PHP_IN_SHELL,
          bufsize=1<<12, universal_newlines=True, ## restriction of templates
          stdin=PIPE, stdout=PIPE, #stderr=PIPE,
          close_fds= sys.platform != 'win32' and True or False)

    ## RED_FLAG: check for returncode after reading stdout and if its an error
    ##           (and we are TEMPLATE_DEBUG), then raise a special error with
    ##           the contents of stderr... wee...
    if not PHP_THREAD_IO:
        php.stdin.write(source)
        php.stdin.close()  # or we block on stdout.read()
        page = php.stdout.read()
        php.stdout.close() # just in case we are windows
    else:
        phpin = PipeThread(php.stdin, 'write')
        phpin.write(source)
        phpin.start()
        phpout = PipeThread(php.stdout)
        phpout.start()
        phpin.close()
        retcode = php.wait()
        phpout.join(1)
        page = phpout.read()
    return page

def load_template_source(template_name, template_dirs=None):
    """all php template requests must start with 'php:' to keep namespaces
    distinct and as an added security measure
    (and it would be recursive otherwise)"""
    if len(template_name) <= 4 or template_name[:4] != 'php:':
        raise TemplateDoesNotExist, (
            "Not a PHP template request: %s" % template_name)
    if PHP_CACHE_SECONDS:
        page_and_origin_name = cache.get(template_name)
        if page_and_origin_name: return page_and_origin_name
    try:
        source, origin = find_template_source(template_name[4:], template_dirs)
    except TemplateDoesNotExist:
        raise TemplateDoesNotExist, template_name
    
    page = runphp(source)
    
    page_and_origin_name =  (page, origin and origin.name or template_name)
    if PHP_CACHE_SECONDS:
        cache.set(template_name, page_and_origin_name, PHP_CACHE_SECONDS)
    return page_and_origin_name
load_template_source.is_usable=True
