" @Author:      Tom Link (micathom AT gmail com?subject=[vim])
" @Website:     http://www.vim.org/account/profile.php?user_id=4037
" @License:     GPL (see http://www.gnu.org/licenses/gpl.txt)
" @Revision:    127

" :filedoc:
" Provides a prototype plus some OO-like methods.

let s:id_counter = 0
let s:prototype  = {'_class': ['object'], '_super': [], '_id': 0} "{{{2

" :def: function! tlib#Object#New(?fields={})
" This function creates a prototype that provides some kind of 
" inheritance mechanism and a way to call parent/super methods.
"
" The usage demonstrated in the following example works best when every 
" class/prototype is defined in a file of its own.
"
" The reason for why there is a dedicated constructor function is that 
" this layout facilitates the use of templates and that methods are 
" hidden from the user. Other solutions are possible.
"
" EXAMPLES: >
"     let s:prototype = tlib#Object#New({
"                 \ '_class': ['FooBar'],
"                 \ 'foo': 1, 
"                 \ 'bar': 2, 
"                 \ })
"     " Constructor
"     function! FooBar(...)
"         let object = s:prototype.New(a:0 >= 1 ? a:1 : {})
"         return object
"     endf
"     function! s:prototype.babble() {
"       echo "I think, therefore I am ". (self.foo * self.bar) ." months old."
"     }
"
" < This could now be used like this: >
"     let myfoo = FooBar({'foo': 3})
"     call myfoo.babble()
"     => I think, therefore I am 6 months old.
"     echo myfoo.IsA('FooBar')
"     => 1
"     echo myfoo.IsA('object')
"     => 1
"     echo myfoo.IsA('Foo')
"     => 0
"     echo myfoo.RespondTo('babble')
"     => 1
"     echo myfoo.RespondTo('speak')
"     => 0
function! tlib#Object#New(...) "{{{3
    return s:prototype.New(a:0 >= 1 ? a:1 : {})
endf


function! s:prototype.New(...) dict "{{{3
    let object = deepcopy(self)
    let s:id_counter += 1
    let object._id = s:id_counter
    if a:0 >= 1 && !empty(a:1)
        " call object.Extend(deepcopy(a:1))
        call object.Extend(a:1)
    endif
    return object
endf


function! s:prototype.Inherit(object) dict "{{{3
    let class = copy(self._class)
    " TLogVAR class
    let objid = self._id
    for c in get(a:object, '_class', [])
        " TLogVAR c
        if index(class, c) == -1
            call add(class, c)
        endif
    endfor
    call extend(self, a:object, 'keep')
    let self._class = class
    " TLogVAR self._class
    let self._id    = objid
    " let self._super = [super] + self._super
    call insert(self._super, a:object)
    return self
endf


function! s:prototype.Extend(dictionary) dict "{{{3
    let super = copy(self)
    let class = copy(self._class)
    " TLogVAR class
    let objid = self._id
    let thisclass = get(a:dictionary, '_class', [])
    for c in type(thisclass) == 3 ? thisclass : [thisclass]
        " TLogVAR c
        if index(class, c) == -1
            call add(class, c)
        endif
    endfor
    call extend(self, a:dictionary)
    let self._class = class
    " TLogVAR self._class
    let self._id    = objid
    " let self._super = [super] + self._super
    call insert(self._super, super)
    return self
endf


function! s:prototype.IsA(class) dict "{{{3
    return index(self._class, a:class) != -1
endf


function! s:prototype.IsRelated(object) dict "{{{3
    return len(filter(a:object._class, 'self.IsA(v:val)')) > 1
endf


function! s:prototype.RespondTo(name) dict "{{{3
    " return has_key(self, a:name) && type(self[a:name]) == 2
    return has_key(self, a:name)
endf


function! s:prototype.Super(method, arglist) dict "{{{3
    for o in self._super
        " TLogVAR o
        if o.RespondTo(a:method)
            " let self._tmp_method = o[a:method]
            " TLogVAR self._tmp_method
            " return call(self._tmp_method, a:arglist, self)
            return call(o[a:method], a:arglist, self)
        endif
    endfor
    echoerr 'tlib#Object: Does not respond to '. a:method .': '. string(self)
endf


function! tlib#Object#Methods(object, ...) "{{{3
    TVarArg ['pattern', '\d\+']
    let o = items(a:object)
    call filter(o, 'type(v:val[1]) == 2 && string(v:val[1]) =~ "^function(''\\d\\+'')"')
    let acc = {}
    for e in o
        let id = matchstr(string(e[1]), pattern)
        if !empty(id)
            let acc[id] = e[0]
        endif
    endfor
    return acc
endf

