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

require('colors')
async = require('async')
cradle = require('cradle')
sha1 = require('./helper').hash('sha1')
db_design = require('./db-design')
config = require('./config')
µ = (next) -> next()

class DB
    constructor: () ->
        cdb = config.db
        @conn = new cradle.Connection(cdb.server, cdb.port, cdb.options)

    save: (db, design, data) =>
        for own type, funcs of data
            continue if type is 'language'
            for own _, fun of funcs
                fun.hash = {}
                for method in ['map', 'reduce']
                    fun.hash[method] = sha1.hash(fun[method]) if fun[method]
        save = -> db.save.apply(db, arguments)
        async.apply(save, "_design/#{design}", data)

    remove: (db, design, _, rev) =>
        remove = -> db.remove.apply(db, arguments)
        async.apply(remove, "_design/#{design}", rev)

    check_design: (db, type, design, funcs, res) =>
        changed = no
        rev = res._rev
        old = res.doc?[type] or {}
        for own fn, func of funcs
            if not old[fn]
                console.log("#{design}/#{fn} added")
                changed = @save
                break
            unless type is 'views' or old[fn] == func.toString()
                console.log("#{design}/#{fn} changed")
                changed = @save
                break
            continue unless type is 'views'
            for method in ["map", "reduce"]
                continue unless old[fn].hash[method] or func[method]
                if not old[fn].hash[method] and func[method]
                    console.log("#{design}/#{fn}:#{method} added")
                    changed = @save
                    break
                if old[fn].hash[method] and not func[method]
                    console.log("#{design}/#{fn}:#{method} removed")
                    changed = @save
                    break
                hash = sha1.hash(func[method])
                if old[fn].hash[method] isnt hash
                    console.log("#{design}/#{fn}:#{method} changed")
                    changed = @save
                    break
            break if changed
        unless changed or not rev
            given = Object.keys(funcs)
            for own fn, func of old
                unless fn in given
                    console.log("#{design}/#{fn} removed")
                    changed = @remove
                    break
        data = language: 'javascript'
        data[type] = funcs
        data['views'] = {} unless type is 'views' # workaround for cradle
        return changed(db, design, data, rev) if changed

    check: (db, callback) =>
        [tasks, renew] = [[], []]
        for own type, handler of db_design
            for own design, funcs of handler
                do (type, design, funcs) =>
                    tasks.push (next) =>
                        db.get ["_design/#{design}"], (err, got) =>
                            return next(err) if err
                            change = @check_design(db,type,design,funcs,got[0])
                            renew.push(change) if change
                            next()
        async.parallel tasks, (err) ->
            return callback(err) if err
            async.parallel(renew, callback)

    init: (db, callback) =>
        tasks = []
        for own type, handler of db_design
            for own design, funcs of handler
                data = language: 'javascript'
                data[type] = funcs
                data['views'] = {} unless type is 'views' #workaround for cradle
                tasks.push(@save(db, design, data))
        async.waterfall [µ
        ,(next) =>
            db.exists (err, res) =>
                return next(err) if err
                return @check(db, callback) if res
                # running for the first time
                console.log("creating db …")
                db.create(next)
        ,(_..., next) ->
            async.parallel(tasks, next)
        ], callback

    connect: (callback) =>
        db = @conn.database(config.db.name)
        @init db, (err) ->
            return console.error("db init:".bold.magenta, err) if err
            callback(db)


database = start: (callback) ->
    (new DB()).connect (db) ->
        database[key] = value for key, value of db
        callback(database)

# exports

module.exports = database
