# -*- mode: perl; coding: utf-8 -*-
package YATT::LRXML;
use strict;
use warnings FATAL => qw(all);

use YATT::Util qw(call_type);

require YATT::LRXML::Node;

sub Parser () { 'YATT::LRXML::Parser' }

use Carp;

# Returns YATT::LRXML::Cursor
sub read_string {
  my $pack = shift;
  my $parser = $pack->call_type(Parser => 'new');
  $parser->parse_string(@_);
}

sub read_handle {
  my $pack = shift;
  my $parser = $pack->call_type(Parser => 'new');
  $parser->parse_handle(@_);
}

sub read {
  my ($pack, $filename) = splice @_, 0, 2;
  my $fh;
  if (ref $filename) {
    $fh = $filename;
  } else {
    open $fh, '<', $filename or croak "Can't open '$filename': $!";
    unshift @_, filename => $filename;
  }
  $pack->read_handle($fh, @_);
}

#========================================

package YATT::LRXML::Scanner; # To scan tokens.
use strict;
use warnings FATAL => qw(all);
use base qw(YATT::Class::ArrayScanner);
use YATT::Fields
  (['^cf_linenum' => 1]
   , ['^cf_last_nol' => 0] # last number of lines
   , qw(cf_last_linenum
	cf_path cf_metainfo));

sub expect {
  (my MY $path, my ($patterns)) = @_;
  return unless $path->readable;
  my $value = $path->{cf_array}[$path->{cf_index}];
  my @match;
  foreach my $desc (@$patterns) {
    my ($toktype, $pat) = @$desc;
    next unless @match = $value =~ $pat;
    $path->after_read($path->{cf_index}++);
    return ($toktype, @match);
  }
  return;
}

sub number_of_lines {
  (my MY $path, my ($pos)) = @_;
  $pos = $path->{cf_index} unless defined $pos;
  return 0 unless @{$path->{cf_array}};
  defined (my $tok = $path->{cf_array}[$pos])
    or return undef;
  $tok =~ tr:\n::;
}

sub after_read {
  (my MY $path, my ($pos)) = @_;
  if (defined $pos) {
    $$path{cf_last_nol} = $path->{cf_array}[$pos] =~ tr:\n::;
  }
  $path->{cf_last_linenum} = $path->{cf_linenum};
  unless (defined $$path{cf_linenum}) {
    $$path{cf_linenum} = 1;
  } else {
    $$path{cf_linenum} += $$path{cf_last_nol} || 0;
  }
}

use YATT::Exception qw(Exception);

sub token_error {
  (my MY $self, my ($mesg)) = @_;
  $self->Exception->new(error_fmt => $mesg
			, file => $self->{cf_metainfo}->in_file
			, line => $self->{cf_linenum});
}

#========================================
package YATT::LRXML::Builder; # To build tree.
use strict;
use warnings FATAL => qw(all);
use base qw(YATT::Class::Configurable);
use YATT::Fields qw(^product ^parent ^is_switched
		    cf_endtag cf_startpos cf_startline cf_linenum);

use YATT::LRXML::Node qw(node_set_nlines);
sub Scanner () {'YATT::LRXML::Scanner'}

sub initargs {qw(product parent)}

sub new {
  my $pack = shift;
  my MY $path = $pack->SUPER::new;
  $path->init(@_) if @_;
  $path;
}

sub init {
  my MY $path = shift;
  @{$path}{qw(product parent)} = splice @_, 0, 2;
  $path->configure(@_) if @_;
  $path;
}

sub open {
  (my MY $parent, my ($product)) = splice @_, 0, 2;
  ref($parent)->new($product, $parent, $parent->configure
		    , startline => $parent->{cf_linenum}
		    , @_);
}

use YATT::Exception qw(Exception);

sub error {
  (my MY $self, my ($mesg, $param, @other)) = @_;
  $self->Exception->new(error_fmt => $mesg
			, error_param => $param
			, @other);
}

sub verify_close {
  (my MY $self, my ($tagname, $scan)) = @_;
  unless (defined $self->{cf_endtag}) {
    die $self->error("TAG '/%s' without open", [$tagname]
		     , file => $scan->cget('metainfo')->filename
		     , line => $scan->linenum);
  }
  unless ($tagname eq $self->{cf_endtag}) {
    die $self->error("TAG '%s' line %d closed by /%s"
		     , [$self->{cf_endtag}, $self->{cf_startline}, $tagname]
		     , file => $scan->cget('metainfo')->filename
		     , line => $scan->linenum);
  }
}

sub add {
  (my MY $self, my Scanner $scan) = splice @_, 0, 2;
  push @{$self->{product}}, @_;
  $self->{cf_linenum} = $scan->{cf_linenum};
  $self;
}

sub switch {
  (my MY $self, my ($elem)) = @_;
  unless ($self->{is_switched}) {
    $self->{is_switched} = $self->{product};
  }
  push @{$self->{is_switched}}, $elem;
  $self->{product} = $elem;
  $self;
}

sub DESTROY {
  my MY $self = shift;
  # switch した場合は?
  node_set_nlines($self->{product}
		  , $self->{cf_linenum} - $self->{cf_startline});
}

1;
