### 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/>
###

fs = require('fs')
url = require('url')
Path = require('path')
gt = require('gettext')
async = require('async')
LiftState = require('lift')
{ Highlight } = require('highlight')
slug = require('slug')
forms = require('../forms')
config = require('../config')
{ request, debug_log } = require('../helper')
User = require('../models/user')
Project = require('../models/project')
Activity = require('../models/activity')
Repository = require('../models/repository')
appconfig = require('../appconfig')
Controller = require('./skeleton')
db = require('../db')

[router, routes] = [null, null]
process.nextTick ->
    router = require('../routes')
    routes = router.controller

µ = (next) -> next()

class Projects extends Controller
    setting_form: (args...) =>
        routes.main.settings.apply(0, ['project'].concat(args))

    git_failed: (err, req, res) =>
        return no unless err
        console.error {git:err}
        req.flash("error", ""+err)
        @render req, res, 'gitfail'
            title: gt._("git failed!")
        return yes

    pages: (req, res) =>
        async.waterfall [µ
        ,(next) =>
            p = req.params
            query = key:[slug(p.owner), slug(p.id)]
            db.view 'user/projects', query, (err, got) =>
                return @home(req, res) if @db_failed(err, req)
                unless got.rows.length
                    req.flash('error', gt._("project not found"))
                    return @home(req, res)
                project = new Project(got[0].value)
                next(null, project)
        ,(project, next) =>
            project.pages.load_empty =>
                next(null, project)
        ,(project, next) =>
            return next(null, project) unless project.pages.empty
            project.body = 'empty-pages'
            @render req, res, 'projectpage',
                current_path: ""
                css: ['page', 'project']
                headline:
                    text: project.name
                    href: @show.url(
                        owner: project.owner.id
                        id: project.id)
                descline: gt._("static file server")
                gitline: project.pages.clone_url
                project: project
                path: @show.url
                    owner: project.owner.id
                    id: project.id
                activities: []
                user: {}
        ,(project, next) =>
            project.pages.load_tree (err) =>
                if err
                    req.flash('error', gt._("tree not found"))
                    return @home(req, res)
                next(null, project)
        ,(project, next) =>
            req.path = ""+req.params
            req.path = req.path[1 ..] if req.path.charAt(0) is "/"
            if req.path.charAt(req.path.length-1) is "/"
                req.path = req.path[...-1]
            if req.path.length
                entry = project.pages.tree.all[req.path]
            else
                entry = project.pages.tree
            @parse_entry(entry, project, res)
        ], (err) =>
            console.error {pages: err}
            req.flash('error', "FATAL:pages → #{err}")
            @home(req, res)

    parse_entry: (entry, project, res) =>
        if not entry
            res.send(gt._("nothing found."), 404)
        else if entry.type is 'blob'
            project.pages.cat entry.path, (data) =>
                mime_type = entry.mime_type or "text/html"
                res.send(data, 'Content-Type': mime_type)
        else if entry.type is 'tree'
            res.write("#{entry.basename}/\n")
            for subentry in entry
                name = entry.basename
                name += "/" if subentry.type is 'tree'
                res.write("\t#{name}\n")
            res.end()
        else if entry.type is 'submodule'
            res.send("Submodule!") # TODO submodule support
        else if Array.isArray entry
            @parse_entry(entry[-1 ..][0], project, res)
        else
            res.send(gt._("unknown found."), 400)

    list: (req, res) =>
        { skip, limit } = req.query
        Project.list limit, skip, (err, projects) =>
            return if @check_err(err, req, res)
            @render(req, res, 'projectlist', { projects })

    create: (req, res) =>
        return routes.authentication.login(req, res) if @not_logged_in(req)
        req.form or= forms.create_project(req, res).bind()
        req.form.action = @New.url()
        return @render req, res, 'project_create', form: req.form

    # create new project
    New: (req, res) =>
        # you need to be logged in
        return routes.authentication.login(req, res) if @not_logged_in(req)
        userid = req.session.auth.user.name

        req.form or= forms.create_project(req, res).bind(req.body)
        req.form.validate(() ->)
        return @create(req, res) unless req.form.isValid()
        project = req.form.clear_data(req)

        async.waterfall [µ
        ,(next) =>
            User.fetch userid, (err, user) =>
                return next(err) if @db_failed(err, req)
                project.owner = user
                project = new Project(project)
                next(err)

        # TODO roll back on error
        ,(next) =>
            fs.mkdir project.path, 16877, (err) =>
                return if @git_failed(err, req, res)
                project.source.description = project.description
                init = (project[repo].init for repo in appconfig.repositories)
                async.parallel init, (err) =>
                    return if @git_failed(err, req, res)
                    next()

        ,(next) =>
            project.owner.projects.push(project)
            activity = new Activity('new project', project)
            project.save (err0) =>
                project.owner.save (err1) =>
                    activity.save (err2) =>
                        next(err0 or err1 or err2)

        ,(next) =>
            req.flash('ok', gt._("new project successfully created."))
            res.redirect @show.url
                owner: project.owner.id
                id: project.id
        ], (err) =>
            console.error {_new: err}
            req.flash('error', "FATAL:_new → #{err}")
            @create(req, res)

    commit: (req, res, next) =>
        { sha } = req.params
        @show req,res,next, ({diff,static,project,user,next,state,path,owner,id}) =>
            diff.sha = sha if sha
            if static and not project.source.empty and sha
                cmt =  if -1 is sha.indexOf ".." then "#{sha}^..#{sha}" else sha
                project.source.git.diffs cmt, (files) ->
                    diff.files = files

                    state.get 'diff', (template) -> # coffeekup scope!
                        html = ""
                        for file in diff.files
                            html += template
                                file: file
                                project: {owner, id, sha}
                        return html

                    err = null # FIXME
                    next(err, user, project)
            else
                next(null, user, project)

    commits: (req, res, next) =>
        @show req, res, next, ({static,state,user,project,next}) =>
            return next(null, user, project) unless static
            # FIXME only when requesting user haz no js
            state.get 'commit', (template) -> # coffeekup scope!
                html = ""
                for commit in @commits
                    html += template
                        author: commit.author
                        short_message: commit.short_message
                        short_sha: commit.sha.slice(0,10)
                        uri: "/#{@project.owner.id}/#{@project.id}/commit/#{commit.sha}"
                return html
            next(null, user, project)

    blob: (req, res, next, blob_renderer = Highlight) =>
        { sha } = req.params
        @show req,res,next, ({blob,path,static,branch,project,user,state,next}) =>
            unless static
                unless project.source.empty
                    blob._lifted = yes
                    blob.path = path
                    blob.sha = sha
                return next(null, user, project)
            unless project.source.empty
                rev = {}
                rev[sha] = path
                project.source.git.cat rev, (data) ->
                    data = data.toString('utf8')
                    blob.sha = sha
                    blob.path = path
                    if blob_renderer
                        data = blob_renderer(data)
                    blob.lines = data.split '\n'

                    iter_blob = (template) ->
                        html = ""
                        for line, index in blob.lines
                            html += template {line, index}
                        html
                    state.get 'blob-index',   iter_blob
                    state.get 'blob-content', iter_blob

                    err = null # FIXME
                    next(err, user, project)
            else
                next(null, user, project)

    show: (req, res, next, bodybuilder) =>
        path = "#{req.params}" or '.'
        path = path[...-1] if path[-1..] == '/'
        unless bodybuilder # show readme
            params = req.params
            req.params.sha = "HEAD"
            req.params = new String(Path.join(path, "README"))
            req.params[k] = v for k, v of params
            return @blob(req, res, next, null)
        { id, owner, branch } = req.params
        return next() if id in appconfig.user_blacklist
        branch or= "master"
        diff = {}
        blob = {}

        static = off # FIXME
        state = new LiftState
        async.waterfall [µ
        ,(next) ->
            if static
                Project.fetch_and_load(owner, id, source:branch, next)
            else
                Project.fetch(owner, id, next)
        ,(user, project, _..., next) ->
            if project.source?
                project.source.load_empty (err) ->
                    next(err, user, project)
            else
                next(null, user, project)
        ,(user, project, next) =>
            project.href = "/#{project.owner.id}/#{project.id}/" # FIXME
            #project.href = @show.url
            #    owner: project.owner.id
            #    id: project.id
            https = config.https and "s" or ""
            project.gitline =
                http: "http#{https}://#{config.url}/#{owner}/#{id}.git"
                git :  "git://#{config.url}/#{owner}/#{id}.git"
            bodybuilder {static,state,blob,diff,branch,path,id,owner,user,project,next}
        ,(user, project) =>
            diff = undefined if Object.keys(diff).length is 0
            blob = undefined if Object.keys(blob).length is 0
            path = Path.dirname(path) if blob
            if static
                # FIXME srsly, this should be shared code with the client side stuff
                state.get 'tree', (template) -> # coffeekup scope

                    tree_obj = @project.source.trees.by_path[path]
                    if tree_obj
                        if tree_obj.filetype == 'folder'
                            current_dir = tree_obj.path
                        else
                            current_dir = tree_obj.dirname
                    else
                        current_dir = '.'

                    cmp = (a, b) -> if a > b then 1 else if a < b then -1 else 0 # XXX a common helper
                    if current_dir != '.'
                        back =
                            filetype: 'folder'
                            path    : current_dir.split('/').slice(0, -1).join('/') or '.'
                            dirname : current_dir
                            basename: '..'

                    comparator = (tree) ->
                        ft = tree.filetype
                        ft = { folder: 1, file: 2 }[ft] or 3
                        ft + tree.basename.toLowerCase()
                    filter = (tree) ->
                        if tree.dirname == current_dir then tree else undefined

                    trees = (tree for tree in @project.source.trees when filter tree)
                    trees.sort (a, b) -> cmp comparator(a), comparator(b)
                    trees.unshift back if back
                    html = (template tree for tree in trees).join ''

            expose =
                current_dir: path
                owner  : project?.owner?.id
                project: project?.id
                branch: branch
                sha: diff?.sha or blob?.sha
                commits: n: 20
            expose.blob_path = blob.path if blob
            res.expose(expose, "expose")

            @render req, res, 'projectpage',
                static      : static
                current_path: path
                project     : project
                path        : project.href #href[1..]
                diff        : diff
                blob        : blob
                user        : user
                me          : req.user
                lift        : state
        ], (err) => @git_failed(err, req, res)

    settings: (req, res) =>
        return @routes.users.login(req, res) if @not_logged_in(req)
        { user } = req
        args =
            id: req.params.id
            user: req.params.owner
        async.waterfall [async.apply(Project.fetch, args.user, args.id)
        ,(_, project, _..., next) =>
            @setting_form req, res, ""+req.params, project, (err, form) =>
                req.flash('error', err) if err
                if form._exists
                    if form.isValid()
                        data = form.clear_data
                        data.clean?()
                        project.set(data)
                        return project.save (err) =>
                            unless @db_failed(err, req)
                                req.flash('info', gt._("data saved"))
                            next(null, project, form)
                    else
                        req.flash('warn', gt._("invalid data"))
                next(null, project, form or null)
        ,(project, form) =>
            url_prefix = @settings.url
                owner: project.owner.id
                id: project.id
            url_prefix += '/'
            @render req, res, 'settings', {
                url_prefix
                project
                form
                user
            }
        ], (err) =>
            @db_failed(err, req)
            @home(req, res)

# exports

module.exports = Projects

