package CParse::Enum;

use 5.6.0;
use strict;
use warnings;

use File::Spec;
use CType::Enum;
use CType::Ref;

sub new
  {
    my $this = shift;
    my $class = ref($this) || $this;
    my $tag = shift;
    my $enumerators = shift;
    my $attributes1 = shift;
    my $attributes2 = shift;
    my $self = {tag => $tag,
                enumerators => $enumerators,
                attributes1 => $attributes1,
                attributes2 => $attributes2,
               };
    bless $self, $class;
    return $self;
  }

sub dump_c
  {
    my $self = shift;

    my $str = "enum";
    if ($self->{attributes1})
      {
        $str .= " " . $self->{attributes1}->dump_c;
      }
    if ($self->{tag})
      {
        $str .= " $self->{tag}";
      }
    $str .= "\n{\n";
    foreach my $enumerator (@{$self->{enumerators}})
      {
        $str .= "  " . $enumerator->dump_c . ",\n";
      }
    $str .= "}";
    if ($self->{attributes2})
      {
        $str .= " " . $self->{attributes2}->dump_c;
      }

    return $str;
  }

sub construct_type
  {
    my $self = shift;
    my $namespace = shift;

    my @attributes;
    push @attributes, $self->{attributes1}->attributes if $self->{attributes1};
    push @attributes, $self->{attributes2}->attributes if $self->{attributes2};

    my @members = map {$_->get_member($namespace)} @{$self->{enumerators}};

    my $type = new CType::Enum \@members, \@attributes, $CParse::current_location;

    # An enum defines every enumerator as an ordinary identifier in
    # the containing scope
    foreach my $member (@members)
      {
        my $old = $namespace->get('ordinary', $member->{name});
        if ($old and
            (File::Spec->canonpath($old->{file}) ne File::Spec->canonpath($CParse::current_location->{file})
             or $old->{line} ne $CParse::current_location->{line}))
          {
            die "Redefinition of identifier $member->{name}\n (old definition at $old->{file}:$old->{line})\n";
          }

        # We aren't constructing values just yet, so what we do is
        # store a reference to the enum itself in the namespace. This
        # can be dereferenced later.
        $namespace->set('ordinary', $member->{name}, $type);
      }

    return $type;
  }

sub process
  {
    my $self = shift;

    my $namespace = shift;

    if ($self->{tag})
      {
        # Heuristic: we'll ignore redefinitions that occur at the same file:line
        my $old = $namespace->get('enum', $self->{tag});
        if ($old and
            (File::Spec->canonpath($old->{file}) ne File::Spec->canonpath($CParse::current_location->{file})
             or $old->{line} ne $CParse::current_location->{line}))
          {
            die "Redefinition of enum $self->{tag}\n (old definition at $old->{file}:$old->{line})\n";
          }

        my $type = $self->construct_type($namespace);
        $namespace->set('enum', $self->{tag}, $type);
      }
  }

sub get_type
  {
    my $self = shift;

    my $namespace = shift;

    if ($self->{tag})
      {
        return new CType::Ref 'enum', $self->{tag}, $namespace;
      }
    else
      {
        return $self->construct_type($namespace);
      }
  }

1;
