package CType::Union;

use 5.6.0;
use strict;
use warnings;

use CType;
use CExpr::Integer;
use CType::Structural;

our @ISA = qw/CType::Structural CType/;

sub new
  {
    my $this = shift;
    my $class = ref($this) || $this;
    my $members = shift;
    my $attributes = shift;
    my $location = shift;

    my $self = {members => $members,
                attributes => $attributes,
                file => $location->{file},
                line => $location->{line},
                pos => $location->{pos},
                alignment_exprs => [],
               };
    bless $self, $class;

    $self->process_attributes($attributes);

    return $self;
  }

sub layout
  {
    my $self = shift;
    my $accept_incomplete = shift;
    my $namespace = shift;

    return if defined $self->{width};

    $_->layout($accept_incomplete, $namespace) foreach @{$self->{members}};
    if ($accept_incomplete and grep {not $_->complete} @{$self->{members}})
      {
        # This type is incomplete and we don't care
        return;
      }

    my $width = 0;
    foreach my $member (@{$self->{members}})
      {
        $width = $member->width if $member->width > $width;
        $self->add_alignment($_) foreach $member->alignment_exprs;
      }
    $self->{width} = $width;
    $self->{alignment} = $self->compute_alignment;
  }

sub complete
  {
    my $self = shift;

    return defined $self->{width} ? 1 : 0;
  }

sub describe
  {
    my $self = shift;

    my @members = map {$_->describe} @{$self->{members}};

    return "union {" . join(', ', @members) . "}";
  }

sub dump_c
  {
    my $self = shift;
    my $skip_cpp = shift;
    my $tag = shift;

    my $str = "";

    $str .= $self->dump_location($skip_cpp);

    my $qualifiers = $self->dump_c_qualifiers;
    $str .= 'union';
    if ($qualifiers)
      {
        $str .= ' ';
        $str .= $qualifiers;
      }

    if ($tag)
      {
        $str .= ' ';
        $str .= $tag;
      }

    $str .= "\n";
    $str .= "  {\n";

    my @members = map {split /\n/, $_->dump_c($skip_cpp)} @{$self->{members}};
    $str .= "    $_\n" foreach @members;

    $str .= "  }\n";

    return $str;
  }

sub get_refs
  {
    my $self = shift;
    return (map {$_->get_refs} @{$self->{members}});
  }

sub _check_interface
  {
    my $self = shift;
    my $other = shift;

    return 'both' unless $other->isa('CType::Union');

    my %self_member_name_index;
    foreach my $member (@{$self->{members}})
      {
        $self_member_name_index{$member->identifier} = $member;
      }

    my %other_member_name_index;
    foreach my $member (@{$other->{members}})
      {
        $other_member_name_index{$member->identifier} = $member;
      }

    my @ret;

    if ($self->{width} and $other->{width})
      {
        if ($self->{width} != $other->{width})
          {
            print "ABI mismatch: size of $self->{width} versus $other->{width}\n";
            push @ret, {abi_forward => 1, abi_backward => 1};
          }
      }
    elsif ($self->{width})
      {
        print "Can't check type (old version is incomplete)\n";
        return {abi_forward => 1, abi_backward => 1, api_forward => 1, api_backward => 1};
      }
    elsif ($other->{width})
      {
        print "Can't check type (new version is incomplete)\n";
        return {abi_forward => 1, abi_backward => 1, api_forward => 1, api_backward => 1};
      }

    if ($self->alignment != $other->alignment)
      {
        print "ABI mismatch: alignment of " . $self->alignment . " versus " . $other->alignment . "\n";
        push @ret, {abi_forward => 1, abi_backward => 1};
      }

    # First, we take out all the things with matching identifiers
    foreach my $member (sort {$a->identifier cmp $b->identifier} values %self_member_name_index)
      {
        my $other_member = $other_member_name_index{$member->identifier};
        next unless $other_member;
        push @ret, $member->check_interface($other_member);
        delete $self_member_name_index{$member->identifier};
        delete $other_member_name_index{$member->identifier};
      }

    # Now we hit all the stuff that's been added or removed
    foreach my $member (sort {$a->identifier cmp $b->identifier} values %other_member_name_index)
      {
        print "API and ABI removal: member " . $member->identifier . " is gone\n";
        push @ret, {api_backward => 1, abi_backward => 1};
      }

    foreach my $member (sort {$a->identifier cmp $b->identifier} values %self_member_name_index)
      {
        print "API and ABI addition: member " . $member->identifier . " is new\n";
        push @ret, {api_forward => 1, abi_forward => 1};
      }

    return @ret;
  }

1;
