package Lego::Ldraw;

use 5.008004;
use strict;
use warnings; no warnings qw/uninitialized/;

use Carp;
use Lego::Ldraw::Line;

use File::Basename;

use overload
    '@{}' => \&lines,
    '""' => \&stringify;

our $VERSION = "0.5.6";

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self  = {};

    $self->{lines} = [];
    $self->{file}  = undef;
    $self->{name}  = undef;
    $self->{description}  = undef;
    $self->{dir}   = undef;

    bless ($self, $class);
    return $self;
}

sub DESTROY {
  my $self = shift;
  $_->{model} = undef for @{$self->lines};
  $self = undef;
}

sub new_from_file {
    my $self = shift->new;
    my $file = shift || \*STDIN;

    unless (ref $file eq 'GLOB') {
	croak "Error $? opening $file" unless -e $file;
	open LDRAW, $file or croak "Error $? opening $file";
	$self->{file} = $file;
	$self->{name} = basename($file);
	$self->{dir}  = dirname($file);
	$file = \*LDRAW;
    }

    while (<$file>) {
	chomp;
	unless (/^\s*$/) {
	    my $line = Lego::Ldraw::Line->new_from_string($_);
	    $self->{description} = $line->command unless $.;
	    $self->add($line);
	}
    }
    close $file unless ref $file;
    return $self;
}

sub copy {
  my $self = shift;
  my $copy = Lego::Ldraw->new;

  $copy->{file} = $self->{file};
  $copy->{name} = $self->{name};
  $copy->{dir} = $self->{dir};

  for (@$self) {
    my $line = Lego::Ldraw::Line->new_from_string("$_");
    $copy->add($line);
  }
  return $copy;
}

sub add {
    my $self = shift;
    my $line = shift;
    my $pos = shift;

    $pos = 0 unless @{$self->{lines}};

    if ($pos) {
	splice @{$self->{lines}}, $pos, 0, $line;
    } else {
	push @{$self->{lines}}, $line;
    }
    $self->{tree}->{$line->part}->{$line->color}++;
    $line->dir($self->dir);
}

sub splice {
    my $self = shift;
    my ($what, $offset, $length) = @_;

    for (ref $what) {
      /^Lego::Ldraw::Line$/ && do {
	splice @{$self->{lines}}, $offset, $length, $what;
	last;
      };
      /^Lego::Ldraw$/ && do {
	splice @{$self->{lines}}, $offset, $length, @{$what->{lines}};
	last;
      };
    }
}

sub lines {
    my $self = shift;
    return wantarray ? @{$self->{lines}} : $self->{lines};
}

sub stringify {
    my $self = shift;
    return join "\n", @{$self};
}

sub length {
    my $self = shift;
    return scalar @{$self->{lines}};
}

sub subparts {
  my $self = shift;
  return grep { $_->type == 1 } @{ $self };
}

sub colors {
  my $self = shift;
  my %colors;
  for ($self->lines) {
    next unless my $c = $_->color;
    $colors{$c}++;
  }
  return sort keys %colors;
}

###################################################
# quick fixes for file, name, tree
###################################################

sub file {
  return shift->{file};
}

sub name {
  return shift->{name};
}

sub tree {
  return shift->{tree}
}

sub parts {
  return sort keys %{ shift->{tree} };
}

sub dir {
  return shift->{dir}
}

sub description {
  my $self = shift;
  $self->{description} = shift if @_;
  return $self->{description}
}

sub partsdirs {
  return Lego::Ldraw::Line->partsdirs;
}

sub basedir {
  return Lego::Ldraw::Line->basedir;
}


##################################################
# experimental part: build tree with callback
##################################################

sub Lego::Ldraw::build_tree {
  my $ldraw = shift;
  my $callback = shift;
  my $test = shift;

  my $b; # part tree: $d->{$part}->{$subpart}         means $subpart is used in $part;
  my $d; # reverse part tree: $d->{$subpart}->{$part} means $subpart is used in $part;
  my $s; # recursed parts list. Value is 1 if list is ready to be built for subpart

  $ldraw->recurse_part_tree(\$b, \$d, \$s, $test);

  # while there are parts ready for list building
  while (my @l = grep { $d->{$_} } keys %{$s}) {
    for my $p (@l) {
      # if part has no subparts
      if ($s->{$p}) {
	# build a list for the part
	print STDERR "traversing tree for $p";
	# Lego::Ldraw::Display->build_list($p);
	&{$callback}($p);
	# delete part from list of parts that need building lists
	# delete $s->{$p}; # = 0;
	# set parts that use it as ready for list building
	my @r = keys %{$d->{$p}}; # all parts that use this subpart
	for (@r) {
	  delete $b->{$_}->{$p}; # delete subpart from part tree
	  $s->{$_} = 1 unless keys %{$b->{$_}} # is there are no subparts list can be built
	}
	# delete part reverse tree
	delete $d->{$p};
      }
    }
  }
  my $t;
  $b = undef;
  $d = undef;
  $s = undef;
}

sub Lego::Ldraw::recurse_part_tree {
  my $tree = shift->copy;
  my ($b, $d, $s, $test) = @_;
  $test = $test || sub { shift->type == 1 };

  $$s->{$tree->name} = 1;
  for (@$tree) {
    if (&{$test}($_)) {
      $$b->{$tree->name}->{$_->name}++;
      $$d->{$_->name}->{$tree->name}++;
      $$s->{$tree->name} = 0;
      unless (defined $$s->{$_->name}) {
	my $x = $_->explode;
	$x->recurse_part_tree($b, $d, $s);
      }
    }
  }
  $b = undef;
  $d = undef;
  $s = undef;
  $tree = undef;
}

1;
__END__
