# -*- coding: utf-8 -*-
#
# Copyright (C) 2005 Edgewall Software
# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
# Copyright (C) 2005 Matthew Good <trac@matt-good.net>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Christopher Lenz <cmlenz@gmx.de>
#         Matthew Good <trac@matt-good.net>

import locale
import os
import sys
import urllib
import gc

from tram.util import get_project_list, get_allenv, debug, prepare_request, __open_environment
from tram.ticket.query import QueryModule
from tram.ticket.roadmap import RoadmapModule
from tram.versioncontrol.browser import BrowserModule
from tram.other.timeline import TimelineModule
from tram.projects import send_project_index, send_project_index_old_style, send_search_index
from trac import mimeview
from trac.core import *
from trac.perm import *
from trac.env import * 
from trac.util import get_last_traceback
from trac.util.html import html, Markup
from trac.util.text import to_unicode
from trac.web.auth import LoginModule
from trac.web.clearsilver import HDFWrapper
from trac.web.href import Href
from trac.wiki.web_ui import WikiModule
from trac.web.main import populate_hdf, open_environment, get_environments
from trac.web.main import RequestDispatcher
from trac.web.session import Session
from trac.web.api import *
from trac.web.chrome import Chrome

class TramRequestDispatcher(Component):
    """This really needs to be cleverer, using a match system similar to trac to find
       the right pugin
    """
    authenticators = ExtensionPoint(IAuthenticator)
    handlers = ExtensionPoint(IRequestHandler)

    def dispatch(self, environ, start_response, env_parent_dir, env_paths, req):
        path_info = environ.get('PATH_INFO', '').lstrip('/').split('/')
        env_name = path_info.pop(0)

        #req = Request(environ, start_response)
        req.authname = LoginModule(self.compmgr).authenticate(req);
        # I don't know why this function is called twice.
        req.authname = LoginModule(self.compmgr).authenticate(req);

        # prepare_request function is not working collectly.
        chrome = prepare_request(req, environ)

        config = get_allenv(environ, req.href).config
        need_auth = config.get('tram', 'check_auth') == 'true'
        auth_perm = config.get('tram', 'permission') or 'PROJECT_ACCESS'
        old_style_index = config.get('tram', 'old_style_index') or 'false'
        if old_style_index == 'false':
            if not env_name:
                env_name = 'all'

        projects = get_project_list(environ, Href(req.href('..')), authname=req.authname, require_auth=need_auth, permission=auth_perm)

        try:
            if env_name == '':
                send_project_index_old_style(environ, start_response, env_parent_dir, env_paths, req.authname)
            elif len(path_info) == 0 or (len(path_info) == 1 and path_info[0] == ''):
                #/all/ requested, show project index
                send_project_index(environ, start_response, env_parent_dir, env_paths, req.authname)
            elif path_info[0] == 'query':
                QueryModule().process_request(req, chrome, projects)
            elif path_info[0] == 'browser':
                BrowserModule().process_request(req, chrome, projects)
            elif path_info[0] == 'roadmap':
                RoadmapModule().process_request(req, chrome, projects)
            elif path_info[0] == 'timeline':
                TimelineModule(self.compmgr).process_request(req, chrome, projects)
            elif path_info[0] == 'search':
                send_search_index(environ, start_response, env_parent_dir, env_paths, req.authname)
        finally:
            if req.session:
                req.session.save()
                

def dispatch_request(environ, start_response):
    """Main entry point for the Trac web interface.
    
    @param environ: the WSGI environment dict
    @param start_response: the WSGI callback for starting the response
    """
    if 'mod_python.options' in environ:
        options = environ['mod_python.options']
        environ.setdefault('trac.env_path', options.get('TracEnv'))
        environ.setdefault('trac.env_parent_dir', options.get('TracEnvParentDir'))
        environ.setdefault('trac.env_index_template', options.get('TracEnvIndexTemplate'))
        environ.setdefault('trac.template_vars', options.get('TracTemplateVars'))
        environ.setdefault('trac.locale', options.get('TracLocale'))

        if 'TracUriRoot' in options:
            # Special handling of SCRIPT_NAME/PATH_INFO for mod_python, which
            # tends to get confused for whatever reason
            root_uri = options['TracUriRoot'].rstrip('/')
            request_uri = environ['REQUEST_URI'].split('?', 1)[0]
            if not request_uri.startswith(root_uri):
                raise ValueError('TracUriRoot set to %s but request URL '
                                 'is %s' % (root_uri, request_uri))
            environ['SCRIPT_NAME'] = root_uri
            environ['PATH_INFO'] = urllib.unquote(request_uri[len(root_uri):])

    else:
        environ.setdefault('trac.env_path', os.getenv('TRAC_ENV'))
        environ.setdefault('trac.env_parent_dir',
                           os.getenv('TRAC_ENV_PARENT_DIR'))
        environ.setdefault('trac.env_index_template',
                           os.getenv('TRAC_ENV_INDEX_TEMPLATE'))
        environ.setdefault('trac.template_vars',
                           os.getenv('TRAC_TEMPLATE_VARS'))
        environ.setdefault('trac.locale', '')

    locale.setlocale(locale.LC_ALL, environ['trac.locale'])

    # Allow specifying the python eggs cache directory using SetEnv
    if 'mod_python.subprocess_env' in environ:
        egg_cache = environ['mod_python.subprocess_env'].get('PYTHON_EGG_CACHE')
        if egg_cache:
            os.environ['PYTHON_EGG_CACHE'] = egg_cache

    env_parent_dir = environ.get('trac.env_parent_dir')
    env_paths = environ.get('trac.env_paths', [])
    import dircache
    paths = dircache.listdir(env_parent_dir)[:]
    dircache.annotate(env_parent_dir, paths)
    env_paths += [os.path.join(env_parent_dir, project) \
                  for project in paths 
                  if project[-1] == '/' and project != '.egg-cache/']


    # Determine the environment
    env_path = environ.get('trac.env_path')
    req = Request(environ, start_response)

    if not env_path:
        if env_parent_dir or env_paths:
            try:
                # The first component of the path is the base name of the
                # environment
                path_info = environ.get('PATH_INFO', '').lstrip('/').split('/')
                env_name = path_info.pop(0)
                if env_name == '':
                    env_name = 'all'
                if env_name == 'all':
                    try:
                        # go on...                       
                        #req = Request(environ, start_response)
                        dispatcher = TramRequestDispatcher(get_allenv(environ, Href(req.href('..'))))
                        dispatcher.dispatch(environ, start_response, env_parent_dir, env_paths, req)
                    except RequestDone, e:
                        return req._response or []
                    #except Exception, e:
                        #raise Exception(e)

                if env_name == 'chrome':
                    env_name = 'all'
                    path_info.insert(0,'chrome')

                # here we send tram chrome, denoted by using the chrome/tram/ parent
                possible_tram_chrome = '/'.join(path_info)
                if (possible_tram_chrome.startswith('chrome/tram/')):
                    file = possible_tram_chrome[12:]
                    path = os.path.dirname(__file__) + "/htdocs/" + file
                    if (os.path.isfile(path)):
                        #req = Request(environ, start_response)
                        try:
                            req.send_file(path, mimeview.get_mimetype(path))
                        except RequestDone, e:
                            return req._response or [];
                    else:
                        raise HTTPNotFound('File %s not found', file)

                if not env_name:
                    # We need to be in the 'all' environment for the auth cookies etc

                    #req = Request(environ, start_response)
                    req.redirect(req.abs_href() + '/all')

            except HTTPException, e:
                #req = Request(environ, start_response)
                prepare_request(req, environ)
                loadpaths = []
                loadpaths.insert(0, os.path.dirname(__file__) + "/templates/")
                data = {
                        'title': e.reason or 'Error',
                        'type': 'TracError',
                        #'message': e.message
                        'message': e.reason or 'Error'
                }
                try:
                    req.send_error(sys.exc_info(), status=e.code, env=Environment(env_path), data=data)
                except RequestDone, e:
                    return []

            except Exception, e:
                #req = Request(environ, start_response)
                prepare_request(req, environ)
                data = {
                        'title': to_unicode(e) or 'Error',
                        'type': 'internal',
                        'traceback': get_last_traceback()
                }
                try:
                    req.send_error(sys.exc_info(), status=500, data=data)
                except RequestDone, e:
                    #debug(to_unicode(e))
                    return []


            # To make the matching patterns of request handlers work, we append
            # the environment name to the `SCRIPT_NAME` variable, and keep only
            # the remaining path in the `PATH_INFO` variable.
            environ['SCRIPT_NAME'] = Href(environ['SCRIPT_NAME'])(env_name)
            environ['PATH_INFO'] = '/'.join([''] + path_info)

            if env_parent_dir:
                env_path = os.path.join(env_parent_dir, env_name)
            else:
                env_path = get_environments(environ).get(env_name)

            if not env_path or not os.path.isdir(env_path):
                start_response('404 Not Found', [])
                return ['Environment not found']

    if not env_path:
        raise EnvironmentError('The environment options "TRAC_ENV" or '
                               '"TRAC_ENV_PARENT_DIR" or the mod_python '
                               'options "TracEnv" or "TracEnvParentDir" are '
                               'missing. Trac requires one of these options '
                               'to locate the Trac environment(s).')
    try:
        env = __open_environment(env_path, run_once=environ['wsgi.run_once'])
    except Exception, e:
        prepare_request(req, environ)
        data = {
            'title': to_unicode(e) or 'Error',
            'type': 'internal',
            'traceback': get_last_traceback()
        }
        try:
            req.send_error(sys.exc_info(), status=500, data=data)
        except RequestDone, e:
            return []

    if env.base_url:
        environ['trac.base_url'] = env.base_url
        
    # normal project(not "all")
    args = req.args
    req = Request(environ, start_response)
    req.args = args
    try:
        try:
            try:
                dispatcher = RequestDispatcher(env)
                dispatcher.dispatch(req)
            except RequestDone:
                pass
            return req._response or []
        finally:
            if environ.get('wsgi.multithread', False):
                try:
                    env.shutdown(threading._get_ident())
                    unreachable = gc.collect()
                except Exception, e:
                    pass

    except HTTPException, e:
        env.log.warn(e)
        loadpaths = []
        loadpaths.insert(0, os.path.dirname(__file__) + "/templates/")
        data = {
                'title': e.reason or 'Error',
                'type': 'TracError',
                'message': to_unicode(e)
        }
        try:
            req.send_error(sys.exc_info(), status=e.code, env=Environment(env_path), data=data)
        except RequestDone:
            return []

    except Exception, e:
        env.log.exception(e)
        loadpaths = []
        loadpaths.insert(0, os.path.dirname(__file__) + "/templates/")
        data = {
                'title': to_unicode(e) or 'Error',
                'type': 'internal',
                'traceback': get_last_traceback()
        }
        try:
            req.send_error(sys.exc_info(), status=500, env=Environment(env_path), data=data)
        except RequestDone:
            return []

