## ------------------------------------------------------------------------- ##
## Class
## ------------------------------------------------------------------------- ##
## We assume that ^Class and ^Class.add_method are already defined
## ------------------------------------------------------------------------- ##

## class construction methods

^Class.add_method('new', -> %params { 
    self.bless('p6opaque', %params);
});

^Class.add_method('bless', -> $repr, %params { 
    $obj := $repr`eq('p6opaque')`cond(
        -> {
            $obj := self`create($repr, {});
            self.CREATE($obj, %params); 
            $obj;           
        },
        -> { self`create($repr, %params) }
    );
    $obj.BUILDALL(%params);         
    $obj;     
});

^Class.add_method('CREATE', -> $obj, %params {
    &dispatcher := self.dispatcher('descendant');
    -> {
        &redo  := &?SUB;
        $class := &WALKCLASS`(&dispatcher);
        -> {
            -> $key {
                $obj`set_attr($key, $class.get_attribute($key));
            }`do_for($class.get_attribute_list);
            &redo`();
        }`do_if($class`not_nil);
    }`();
});

^Class.add_method('BUILDALL', -> %params {
    &dispatcher := $?CLASS.dispatcher('descendant');
    -> {
        &redo   := &?SUB;
        $method := &WALKMETH`(&dispatcher, 'BUILD');
        -> {
            $method`(%params);
            &redo`();
        }`do_if($method`not_nil);
    }`();
});

^Class.add_method('BUILD', -> %params {
    -> $key {
        -> {
            self`set_attr($key, %params`fetch($key));
        }`do_if(self`has_attr($key))
    }`do_for(%params`keys);
});

## class destruction

^Class.add_method('DESTROYALL', -> { 
    &dispatcher := $?CLASS.dispatcher('ascendant');
    -> {
        &redo   := &?SUB;
        $method := &WALKMETH`(&dispatcher, 'DESTROY');
        -> {
            $method`($self);
            &redo`();
        }`do_if($method`not_nil);
    }`();
});

## informational methods

# NOTE:
# is_a is meant to check against class instances 
^Class.add_method('is_a', -> $class { 
    self`eq($class)`if_else(
        -> { true },
        -> {
            &dispatcher := self.dispatcher('descendant');
            -> {
                &redo     := &?SUB;
                $ancestor := &WALKCLASS`(&dispatcher);      
                -> {
                    $ancestor`eq($class)`if_else(
                        -> { true     },
                        -> { &redo`() }
                    );
                }`do_if($ancestor`not_nil)
            }`();
        }
    );
});

# NOTE:
# isa is meant to check against class names
^Class.add_method('isa', -> $class {
    self.name`eq($class)`if_else(
        -> { true },
        -> {
            &dispatcher := self.dispatcher('descendant');            
            -> {
                &redo     := &?SUB;
                $ancestor := &WALKCLASS`(&dispatcher);                        
                -> {
                    $ancestor.name`eq($class)`if_else(
                        -> { true     },
                        -> { &redo`() }
                    );
                }`do_if($ancestor`not_nil)
            }`();
        }
    );    
});

## (super|sub)classes

^Class.add_method('superclasses', -> { self`get_attr('@!superclasses') });
^Class.add_method('set_superclasses', -> @superclasses { 
    -> $super { 
        $super.add_subclass(self); 
    }`do_for(@superclasses);
    self`set_attr('@!superclasses', @superclasses);
    self`set_attr('@!MRO', self`mro_merge);    
});

^Class.add_method('MRO', -> {
    @mro := self`get_attr('@!MRO');
    @mro`is_empty`if_else(
        ->{ self`set_attr('@!MRO', self`mro_merge) },
        ->{ @mro }
    );
});

^Class.add_method('subclasses',   ->        { self`get_attr('@!subclasses') });
^Class.add_method('add_subclass', -> $class { 
    self`set_attr('@!subclasses', self`get_attr('@!subclasses')`push($class));
});

## method related methods

^Class.add_method('has_method', -> $name { 
    self`get_attr('%!methods')`exists($name);
});

^Class.add_method('get_method', -> $name { 
    self`get_attr('%!methods')`fetch($name); 
});

^Class.add_method('get_method_list', -> { 
    self`get_attr('%!methods')`keys(); 
});

^Class.add_method('remove_method', -> $name { 
    self`set_attr('%!methods', self`get_attr('%!methods')`delete($name));
});

## private-method related methods

^Class.add_method('add_private_method', -> $name, $method { 
    self`set_attr_hash('%!private_methods', $name, $method);
});

^Class.add_method('has_private_method', -> $name { 
    self`get_attr('%!private_methods')`exists($name);
});

^Class.add_method('get_private_method', -> $name { 
    self`get_attr('%!private_methods')`fetch($name); 
});

^Class.add_method('get_private_method_list', -> { 
    self`get_attr('%!private_methods')`keys(); 
});

^Class.add_method('remove_private_method', -> $name { 
    self`set_attr('%!private_methods', self`get_attr('%!private_methods')`delete($name)); 
});

## Method dispatching

^Class.add_method('dispatcher', -> $order {
    $order := $order`is_nil`or($order`eq('canonical'))`if_else(
        ->{ 'ascendant' }, 
        ->{ $order      }
    );
    $order`eq('descendant')`if_else(
        ->{ self!make_descendant_dispatcher; },
        ->{ $order`eq('ascendant')`if_else(
            ->{ self!make_ascent_dispatcher;             },
            ->{ self!unsupported_dispatch_order($order); }
        ) }
    );
});

^Class.add_private_method('make_dispatcher_iterator', -> @values {
    $counter := ^`create('p6scalar', 0);
    -> {
        $idx := $counter`fetch;
        $counter`store($idx`add(1));
        @values`fetch($idx);
    };
});

^Class.add_private_method('make_ascent_dispatcher', -> {
    self!make_dispatcher_iterator(self.MRO);
});

^Class.add_private_method('make_descendant_dispatcher', -> {
    self!make_dispatcher_iterator(self.MRO`reverse);
});


## attribute related methods

^Class.add_method('add_attribute', -> $name, $attr { 
    self`set_attr_hash('%!attributes', $name, $attr);
});

^Class.add_method('has_attribute', -> $name { 
    self`get_attr('%!attributes')`exists($name);
});

^Class.add_method('get_attribute', -> $name { 
    self`get_attr('%!attributes')`fetch($name); 
});

^Class.add_method('remove_attribute', -> $name { 
    self`set_attr('%!attributes', self`get_attr('%!attributes')`delete($name)); 
});

^Class.add_method('get_attribute_list', -> { 
    self`get_attr('%!attributes')`keys(); 
});

^Class.add_method('get_attributes', -> { 
    self`get_attr('%!attributes')`values(); 
});

## NOTE:
## we assume that when ^Class was defined, that it's attributes
## were also defined, but only in the Instance type, and not in 
## it's meta-self

^Class.add_attribute('@!MRO',             []);
^Class.add_attribute('@!superclasses',    []);
^Class.add_attribute('@!subclasses',      []);
^Class.add_attribute('%!private_methods', {});
^Class.add_attribute('%!methods',         {});
^Class.add_attribute('%!attributes',      {});
