package XML::Validator::Schema::RootNode;
use strict;
use warnings;

use base 'XML::Validator::Schema::ElementNode';

use XML::Validator::Schema::Util qw(_err);
use Carp qw(croak);

=head1 NAME

XML::Validator::Schema::RootNode - the root node in a schema document

=head1 DESCRIPTION

This is an internal module used by XML::Validator::Schema to represent
the root node in an XML Schema document.  Holds references to the
libraries for the schema document and is responsible for hooking up
named types to their uses in the node tree at the end of parsing.

=cut

sub new {
    my $pkg = shift;
    my $self = $pkg->SUPER::new(@_);

    # start up with empty libraries
    $self->{type_library}      = XML::Validator::Schema::TypeLibrary->new;
    $self->{element_library}   = XML::Validator::Schema::ElementLibrary->new;
    $self->{attribute_library} = XML::Validator::Schema::AttributeLibrary->new;

    return $self;
}

# finish typing and references
sub compile {
    my $self = shift;
    my $element_library = $self->{element_library};

    # put global elements into the library (could move this to ::ElementNode)
    foreach my $d ($self->daughters) {
        if (ref($d) eq 'XML::Validator::Schema::ElementNode') {
            $element_library->add(name => $d->{name},
                                  obj  => $d);
        }
    }


    # complete all element refs first, forming a complete tree
    foreach my $element ($self->descendants) {
        $self->complete_ref($element);
    }

    # completa all element types, including their attributes
    foreach my $element ($self->descendants) {
        $self->complete_type($element);
    }

}

sub complete_ref {
    my ($self, $ref) = @_;

    # handle any unresolved attribute types
    if ($ref->{attr}) {
        $self->complete_attr_ref($_) 
          for (grep { $_->{unresolved_ref} } (@{$ref->{attr}}));
    }

    # all done unless unresolved
    return unless $ref->{unresolved_ref};

    my $name = $ref->{name};
    my ($element) = $self->{element_library}->find(name => $ref->{name});
    _err("Found unresolved reference to element '$name'")
      unless $element;



    # replace the current element
    $ref->replace_with($element->copy_at_and_under);

    return;
}

sub complete_type {
    my ($self, $element) = @_;
    my $library = $self->{type_library};

    # handle any unresolved attribute types
    if ($element->{attr}) {
        $self->complete_attr_type($_) 
          for (grep { $_->{unresolved_type} } (@{$element->{attr}}));
    }

    # all done unless unresolved
    return unless $element->{unresolved_type};

    # get type data
    my $type_name = $element->{type_name};
    my $type = $library->find(name => $type_name);

    # isn't there?
    _err("Element '<$element->{name}>' has unrecognized type '$type_name'.") 
      unless $type;


    if ($type->isa('XML::Validator::Schema::ComplexTypeNode')) {
        # can't have daughters for this to work
        _err("Element '<$element->{name}>' is using a named complexType and has sub-elements of its own.  That's not supported.")
          if $element->daughters;
    
        # replace the current element with one based on the complex node
        my $new_node = $type->copy_at_and_under;
        $new_node->name($element->{name});
        $new_node->{attr} = [ @{ $new_node->{attr} || [] }, 
                              @{ $element->{attr} || [] } ];
        $element->replace_with($new_node);


    } elsif ($type->isa('XML::Validator::Schema::SimpleType')) {
        $element->{type} = $type;

    } else {
        croak("Library returned '$type'!");
    }

    # fixed it
    delete $element->{unresolved_type};
}

sub complete_attr_type {
    my ($self, $attr) = @_;

    my $type = $self->{type_library}->find(name => $attr->{type_name});
    _err("Attribute '<$attr->{name}>' has unrecognized ".
         "type '$attr->{type_name}'.")
      unless $type;

    $attr->{type} = $type;
    delete $attr->{unresolved_type};
}

sub complete_attr_ref {
    my ($self, $ref) = @_;

    my $attr = $self->{attribute_library}->find(name => $ref->{name});
    _err("Attribute reference '$ref->{name}' not found.")
      unless $attr;
    
    # clone, keep use
    my $use = $ref->{required};
    %$ref = %$attr;
    $ref->{required} = $use;

    return;
}



1;