# Movable Type (r) Open Source (C) 2001-2011 Six Apart, Ltd.
# This program is distributed under the terms of the
# GNU General Public License, version 2.
#
# $Id$

package MT::Template::Node;

use strict;

sub EL_NODE_NAME ()     { 0 }
sub EL_NODE_TEXT ()     { 1 }
sub EL_NODE_ATTR ()     { 1 }
sub EL_NODE_CHILDREN () { 2 }
sub EL_NODE_VALUE ()    { 3 }
sub EL_NODE_ATTRLIST () { 4 }
sub EL_NODE_PARENT ()   { 5 }
sub EL_NODE_TEMPLATE () { 6 }

sub NODE_TEXT ()     { 1 }
sub NODE_BLOCK ()    { 2 }
sub NODE_FUNCTION () { 3 }

use MT::Util qw( weaken );

sub mk_wref_accessor {
    my ($class, $field, $index) = @_;
    return sub {
        return $_[0]->[$index] unless @_ > 1;
        my $self = shift;
        weaken($self->[$index] = (@_ == 1 ? $_[0] : [@_]));
    };
}
*parentNode = __PACKAGE__->mk_wref_accessor('parentNode', EL_NODE_PARENT);
*template = __PACKAGE__->mk_wref_accessor('template', EL_NODE_TEMPLATE);

sub new {
   my $pkg = shift;
   my ($param) = ref $_[0] ? $_[0] : { @_ };
   my $self =
     $param->{tag} eq 'TEXT'
     ? [
       'TEXT',
       delete $param->{nodeValue},
       undef,  # unused for TEXT nodes
       undef,
       undef,
       delete $param->{parentNode},
       delete $param->{template},
     ]
     : [
       delete $param->{tag},
       delete $param->{attributes},
       delete $param->{childNodes},
       delete $param->{nodeValue},
       delete $param->{attribute_list},
       delete $param->{parentNode},
       delete $param->{template},
     ];
   bless $self, $pkg;
   weaken($self->[EL_NODE_PARENT]) if defined $self->[EL_NODE_PARENT];
   weaken($self->[EL_NODE_TEMPLATE]) if defined $self->[EL_NODE_TEMPLATE];
   $self;
}

sub nodeValue {
   my $node = shift;
   my $index = $node->[0] eq 'TEXT' ? EL_NODE_TEXT : EL_NODE_VALUE;
   $node->[$index] = shift if @_;
   $node->[$index];
}

sub childNodes {
    my $node = shift;
    return $node->[EL_NODE_CHILDREN] = shift if @_;
    return $node->[EL_NODE_CHILDREN] ||= [];
}

sub attributes {
    my $node = shift;
    return $node->[EL_NODE_ATTR] = shift if @_;
    return $node->[EL_NODE_ATTR] ||= {};
}

sub attribute_list {
    my $node = shift;
    return $node->[EL_NODE_ATTRLIST] = shift if @_;
    return $node->[EL_NODE_ATTRLIST] ||= [];
}

sub tag {
    return $_[1] ? $_[0]->[EL_NODE_NAME] = $_[1] : $_[0]->[EL_NODE_NAME];
}

sub setAttribute {
    my $node = shift;
    my ($attr, $val) = @_;
    if ($attr eq 'id') {
        # assign into ids
        my $tmpl = $node->template;
        my $ids = $tmpl->token_ids;
        my $old_id = $node->getAttribute("id");
        if ($old_id && $ids) {
            delete $ids->{$old_id};
        }
    }
    elsif ($attr eq 'class') {
        # assign into classes
        my $tmpl = $node->template;
        my $classes = $tmpl->token_classes;
        my $old_class = $node->getAttribute("class");
        if ($old_class && $classes->{$old_class}) {
            @{$classes->{$old_class}} = grep { $_ != $node }
                @{$classes->{$old_class}};
        }
        push @{$classes->{$val} ||= []}, $node;
    }
    ($node->[EL_NODE_ATTR] ||= {})->{$attr} = $val;
}

sub getAttribute {
    my $node = shift;
    my ($attr) = @_;
    ($node->attributes)->{$attr};
}

sub firstChild {
    my $node = shift;
    my $children = $node->childNodes;
    @$children ? $children->[0] : undef;
}

sub lastChild {
    my $node = shift;
    my $children = $node->childNodes;
    @$children ? $children->[scalar @$children - 1] : undef;
}

sub nextSibling {
    my $node = shift;
    my $siblings = $node->parentNode->childNodes;
    my $max = (scalar @$siblings) - 1;
    return undef unless $max;
    my $last = $siblings->[0];
    foreach my $n ($siblings->[1..$max]) {
        return $n if $node == $last;
        $last = $n;
    }
    return $siblings->[$max] if $node == $last;
    return undef;
}

sub previousSibling {
    my $node = shift;
    my $siblings = $node->parentNode->childNodes;
    my $last;
    foreach my $n (@$siblings) {
        return $last if $node == $n;
        $last = $n;
    }
    return undef;
}

sub ownerDocument { #template
    my $node = shift;
    return $node->template;
}

sub hasChildNodes {
    my $node = shift;
    my $children = $node->childNodes;
    $children && (@$children) ? 1 : 0;
}

sub nodeType {
    my $node = shift;
    if ($node->tag eq 'TEXT') {
        return NODE_TEXT();
    } elsif (defined $node->childNodes) {
        return NODE_BLOCK();
    } else {
        return NODE_FUNCTION();
    }
}

sub nodeName {
    my $node = shift;
    my $tag = $node->tag;
    if ($tag eq 'TEXT') {
        return undef;
    }
    # normalize:
    #    MTEntry => mt:entry
    #    MTAPP:WIDGET => mtapp:widget
    $tag = lc $tag;
    if (($tag !~ m/:/) && ($tag =~ m/^mt/)) {
        $tag =~ s/^mt/mt:/;
    }
    return $tag;
}

sub innerHTML {
    my $node = shift;
    if (@_) {
        my ($text) = @_;
        $node->nodeValue($text);
        require MT::Builder;
        my $builder = new MT::Builder;
        require MT::Template::Context;
        my $ctx = MT::Template::Context->new;
        $node->childNodes($builder->compile($ctx, $text));
        my $tmpl = $node->ownerDocument;
        if ($tmpl) {
            $tmpl->reset_markers;
            $tmpl->{reflow_flag} = 1;
        }
    }
    return $node->nodeValue;
}

# TBD: what about new nodes that are added with id elements?
sub appendChild {
    my $node = shift;
    my ($new_node) = @_;
    my $nodes = $node->childNodes;
    push @$nodes, $new_node;
    my $tmpl = $node->ownerDocument;
    if ($tmpl) {
        $tmpl->{reflow_flag} = 1;
    }
}

sub removeChild {
    my $node = shift;
}

## Decode all items
sub upgrade {
    my $node = shift;
    my $i;
    for my $v ( @$node ) {
        _upgrade(\$v);
        $i++;
        ## Don't decode parents.
        last if $i >= 5;
    }
}

sub _upgrade {
    my $ref = ref $_[0];
    if ( !$ref ) {
        Encode::_utf8_on($_[0])
            if !Encode::is_utf8($_[0]);
    }
    elsif ( $ref eq 'HASH' ) {
        for my $v ( values %{$_[0]} ) {
            _upgrade(\$v);
        }
    }
    elsif ( $ref eq 'ARRAY' ) {
        for my $v ( @{$_[0]} ) {
            _upgrade(\$v);
        }
    }
    elsif ( $ref eq 'SCALAR' ) {
        Encode::_utf8_on(${$_[0]})
            if !Encode::is_utf8(${$_[0]});
    }
    elsif ( $ref eq 'MT::Template::Node' ) {
        $_[0]->upgrade;
    }
    elsif ( $ref eq 'MT::Template::Tokens' ) {
        for my $n ( @{$_[0]} ) {
            $n->upgrade;
        }
    }
    elsif ( $ref eq 'REF' ) {
        _upgrade( ${ $_[0] } );
    }
}

*inner_html = \&innerHTML;
*append_child = \&appendChild;
*insert_before = \&insertBefore;
*remove_child = \&removeChild;

1;
__END__
