### codetube
    Copyright (C) 2011 payload payload@lavabit.com
    Copyright (C) 2011 dodo dodo.the.last@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
###

Path = require('path')
gt = require('gettext')
require('express-expose')
express = require('express')
coffeekup = require('coffeekup')
connect =
    auth: require('connect-auth')
    cache: require('connect-cache')
login_form = require('./login-form-strategy')
routes = require('./routes')
config = require('./config')
db = require('./db')
User = require('./models/user')
stylus = require('stylus')
{ obj_merge, obj_overwrite, debug_log } = require('./helper')
browserify = require('browserify')

format = [
    "[:date]".grey
    ":status".green
    ":method".magenta
    ":url".yellow
].join(" ")


getUser = (req, res, next) ->
    userid = req.session?.auth?.user?.name
    if userid
        User.fetch userid, (err, user) ->
            if err
                helper.debug_log(err)
                req.user = null
            else
                req.user = user
            next(null, req, res)
    else
        req.user = null
        next(null, req, res)

response_time = (req, res, next) ->
    start = new Date
    return next() if req._response_time
    req._response_time = true
    end = res.end
    res.end = ->
        console.log response_time: new Date - start
        end.apply(this, arguments)
    next()

prelog = (req, res, next) ->
    msg = [
        "[#{(new Date).toUTCString()}]".grey
        "   "
        "#{req.method}".magenta
        "#{req.url}".yellow
    ].join(" ")
    console.log(msg)
    next()

query_validator = (req, res, next) ->
    default_limit = config.users.page_limit
    { skip, limit } = req.query
    skip  = if  skip?.match /^\d+$/ then +skip  else 0
    limit = if limit?.match /^\d+$/ then +limit else default_limit
    obj_overwrite(req.query, {skip, limit})
    debug_log req.query
    next()

wrap_prefix = (prefix, middleware) -> (req, res, next) ->
    if req.url.indexOf(prefix) == 0
        req = obj_merge req
        req.url = req.url[prefix.length..]
        middleware req, res, next
    else
        next()

class HTTPServer
    constructor: () ->
        options = []
        options.push(config.https) if config.https
        @server = express.createServer.apply(0, options)

    initialize: (@sessionstore) =>
        db.start () =>
            console.log("db started.")
            @configure = @configure()
            routes.load()
            @create()

    use: (name, args...) =>
        args = @log_use_chain(name, args) if false # TODO config debug variable?
        @server.use.apply(@server, args)
    set: => @server.set.apply(@server, arguments)
    register: => @server.register.apply(@server, arguments)

    cur_req: null
    use_cnt: 0
    log_use_chain: (name, args) =>
        if args.length = 1
            func = args[0]
            return args unless typeof func is 'function'
            fuu = (req, args...) =>
                if req != @cur_req
                    @use_cnt = 0
                    @cur_req = req
                    console.log { pid: process.pid, req: 'new' }
                ++@use_cnt
                console.log { pid: process.pid, name, @use_cnt }
                func(req, args...)
            args = [fuu]
        args

    configure: () =>
        views_path = Path.join(config.cwd, "views")
        public_path = Path.join(config.cwd, "public")
        public_js_path = Path.join(__dirname, "public")
        if config.debug.enable
            public_js_path = Path.join(config.cwd, 'src', 'public')
        debug: () =>
            @use 'response_time', response_time
            @use 'responeTime', express.responseTime()
            @use 'prelog', prelog
            @use 'logger', express.logger({format})
            @use 'profiler', require('./profiler') if config.debug.profiler

        cache: () =>
            @use 'cache', connect.cache
                loopback: "localhost:" + config.port
                rules: [regex: /.*\.js/, ttl: config.cache.ttl]

        view: () =>
            javascript = browserify
                mount  : '/.static/js/require.js'
                watch  : true
                require: [
                    'diff'
                    'formatdate'
                    jquery:'jquery-browserify'
                    'underscore'
                    'backbone'
                    'jquery.ui.core'
                    'jquery.ui.widget'
                    'jquery.ui.position'
                    Path.join(public_js_path, 'jquery')
                    Path.join(public_js_path, 'branches')
                    Path.join(public_js_path, 'commits')
                    Path.join(public_js_path, 'blobs')
                    Path.join(public_js_path, 'trees')
                    Path.join(public_js_path, 'diff')
                ]
                fastmatch: true

            if config.minification
                javascript.register 'post', require('uglify-js')

            # FFUUU ... that's dirty
            # we have to extend every javascript library that modifies/extends other libraries
            # that it assumes to be in their local variable scope. backbone assumes jquery to be
            # available for example
            backbone = Path.join(config.cwd, "node_modules", "backbone", "backbone.js")
            backbone_localstorage = Path.join(public_js_path, "Backbone-localstorage.js")
            javascript.register 'pre', () ->
                @files[backbone].body = @files[backbone].body.replace(
                    "var module = { exports : {} };",
                    "var module = { exports : {_:window._, jQuery:window.jQuery} };")
                @files[backbone_localstorage].body = @files[backbone_localstorage].body.replace(
                    "var module = { exports : {} };",
                    "var module = { exports : {_:window._, Backbone:window.Backbone} };")

            @use 'browserify', javascript

            @use 'favicon', express.favicon(Path.join(public_path, "img", "5src.ico"))
            stylus_conf =
                src  : public_path
                debug: config.debug.enable
                paths: [public_path]
            @use 'stylus', wrap_prefix('/.static',
                stylus.middleware stylus_conf)

            coffeekup_adapter =
                compile: (template, data) =>
                    data.hardcode =
                        obj_merge: obj_merge
                        partial: (tmpl, args...) ->
                            text @partial tmpl, obj_merge(this, args...)
                    data.locals ?= {}
                    data.locals.coffeekup = coffeekup
                    data.locals.compile = (template, locals) ->
                        coffeekup.compile template, {locals}
                    coffeekup.compile(template, data)
            @register('.coffee', coffeekup_adapter)
            @set('view engine', 'coffee')
            @set('view options', { layout: no, format: no })
            @set('views', views_path)

            @use 'static', wrap_prefix('/.static/',
                express.static public_path)

        session: () =>
            @use 'session', express.session
                secret: config.salt.session
                store: @sessionstore

        auth: () =>
            @use 'auth', connect.auth [
                connect.auth.Basic(validatePassword: login_form.basic())
                connect.auth.Basic
                    name: 'git'
                    validatePassword: login_form.basic(error: yes)
                login_form.strategy(routes.controller.authentication.login.url())
                connect.auth.Anonymous()
                ]

        expose: () =>
            @use 'getUser', getUser
            @use 'router', @server.router

        error: () =>
            @use 'errorHandler', express.errorHandler
                dumpExceptions: on
                showStack: yes

        request_parser: () =>
            @use 'cookieParser', express.cookieParser()
            @configure.session()
            @use 'bodyParser', express.bodyParser()
            @use 'query_validator', query_validator

        basic: () =>
            @configure.debug() if config.debug.enable
            @configure.cache() if config.cache.enable
            @configure.view()
            @configure.request_parser()
            @configure.auth()
            @configure.expose()

    create: () =>
        @server.configure () =>
            @configure.basic

        @server.configure 'development', () =>
            @configure.basic()
            @configure.error()

        @server.configure 'production', () =>
            @configure.basic()
            # FIXME better error handling

        routes.bind(@server)

# exports

module.exports = new HTTPServer

