package CParse::Declarator;

use 5.6.0;
use strict;
use warnings;

use CDecl;
use CType::BitField;
use CType::Fundamental;
use CType::Ref;

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

sub pointer
  {
    my $self = shift;
    return $self->{pointer};
  }

sub declarator
  {
    my $self = shift;
    return $self->{declarator};
  }

sub dump_c
  {
    my $self = shift;

    my $str = "";

    if ($self->{attributes1})
      {
        $str .= $self->{attributes1}->dump_c;
      }
    if (defined $self->{pointer})
      {
        $str .= $self->{pointer}->dump_c;
      }
    if (defined $self->{declarator})
      {
        $str .= " " if length $str;
        $str .= $self->{declarator}->dump_c;
      }
    if ($self->{attributes2})
      {
        $str .= " " if length $str;
        $str .= $self->{attributes2}->dump_c;
      }

    return "$str";
  }

sub get_identifier
  {
    my $self = shift;

    return undef unless $self->{declarator};
    return $self->{declarator}->get_identifier;
  }

sub get_type
  {
    my $self = shift;

    my $namespace = shift;
    my $base_type = shift;
    my $attributes = shift;

    my $type = $base_type;

    if ($self->{pointer})
      {
        $type = $self->{pointer}->get_type($type);
      }

    if ($self->{declarator})
      {
        $type = $self->{declarator}->get_type($namespace, $type, $attributes);
      }

    return $type;
  }

sub get_decl_type
  {
    my $self = shift;

    my $namespace = shift;
    my $specifiers = shift;
    my $decl_attributes = shift;
    my $parameter_name_magic = shift;

    my @attributes = (@$decl_attributes);
    push @attributes, $self->{attributes1}->attributes if $self->{attributes1};
    push @attributes, $self->{attributes2}->attributes if $self->{attributes2};

    my $maybe_identifier = undef;

    my %base_qualifiers;
    my @base_specifiers;
    my $base_type;
    foreach my $specifier (@$specifiers)
      {
        if ($specifier->isa('CParse::TypeQualifier'))
          {
            $base_qualifiers{$specifier->name} = 1;
          }
        elsif ($specifier->isa('CParse::TypeSpecifier'))
          {
            push @base_specifiers, $specifier->name;
          }
        elsif ($specifier->isa('CParse::Extension'))
          {
            # Ignore this
            next;
          }
        else
          {
            # Should be a type of some kind
            my $type;
            if ($specifier->isa('CParse::Identifier'))
              {
                # If we're doing the magic for function parameter
                # names, and this is an identifier for which we
                # already have a type, then we'll reclassify it as an
                # identifier
                if ($parameter_name_magic and (scalar @base_specifiers or $base_type))
                  {
                    # If we get more than one of these, the first is actually an error
                    if ($maybe_identifier)
                      {
                        die "Undefined identifier " . $maybe_identifier->name . "\n";
                      }
                    $maybe_identifier = $specifier;
                    next;
                  }
                # We'll check that it exists now, but we'll put a
                # reference in anyway, so that output later retains
                # this as a reference rather than duplicating the type
                # definition
                unless ($namespace->get('ordinary', $specifier->name))
                  {
                    die "Undefined identifier " . $specifier->name . "\n";
                  }
                $type = new CType::Ref 'ordinary', $specifier->name, $namespace;
              }
            elsif ($specifier->isa('CParse::Struct') or $specifier->isa('CParse::Union') or $specifier->isa('CParse::Enum'))
              {
                $type = $specifier->get_type($namespace, undef, []);
              }
            elsif ($specifier->isa('CParse::StructRef') or $specifier->isa('CParse::UnionRef') or $specifier->isa('CParse::EnumRef'))
              {
                $type = $specifier->get_type($namespace, undef, []);
              }
            else
              {
                die "Unhandled specifier $specifier\n";
              }

            if (defined $base_type)
              {
                die "Syntax error, multiple types in declaration\n";
              }
            $base_type = $type;
          }
      }

    if ($base_type and scalar @base_specifiers)
      {
        die "Syntax error, both type identifier and specifiers in declaration\n";
      }

    if (not $base_type and not scalar @base_specifiers)
      {
        die "Syntax error, no type in declaration\n";
      }

    if (not $base_type)
      {
        $base_type = new CType::Fundamental \@base_specifiers, \@attributes;
      }

    $base_type->set_qualifiers([keys %base_qualifiers]);

    return $self->get_type($namespace, $base_type, []);
  }

sub get_member
  {
    my $self = shift;

    my $namespace = shift;
    my $field_width = shift;
    my $specifiers = shift;
    my $decl_attributes = shift;

    my $identifier = $self->{declarator} ? $self->{declarator}->get_identifier : undef;

    if (not $identifier and scalar @$specifiers)
      {
        # Maybe our identifier got lost; the parser doesn't know what
        # is a valid type, so declarator identifiers can end up on the
        # end of the list of type specifiers instead
        my $specifier = $specifiers->[-1];
        if ($specifier->isa('CParse::Identifier') and not $namespace->get('ordinary', $specifier->name))
          {
            # This is an undefined identifier. We should be defining it; it's not part of our type
            $identifier = $specifier->name;
            pop @$specifiers;
          }
      }

    if (not $identifier)
      {
        # Bitfields can lack identifiers. Everything else must have them.
        if (not $field_width)
          {
            die "Member declaration lacks an identifier\n";
          }
      }

    my $type = $self->get_decl_type($namespace, $specifiers, $decl_attributes);

    if ($field_width)
      {
        $type = new CType::BitField $type, $field_width->get_expr($namespace);
      }

    return new CDecl 'member', $identifier, $type;
  }

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

    my $storage_class = shift;
    my $specifiers = shift;
    my $inline = shift;
    my $decl_attributes = shift;

    my $identifier = $self->{declarator}->get_identifier;

    if (not $identifier and scalar @$specifiers)
      {
        # Maybe our identifier got lost; the parser doesn't know what
        # is a valid type, so declarator identifiers can end up on the
        # end of the list of type specifiers instead
        my $specifier = $specifiers->[-1];
        if ($specifier->isa('CParse::Identifier') and not $namespace->get('ordinary', $specifier->name))
          {
            # This is an undefined identifier. We should be defining it; it's not part of our type
            $identifier = $specifier->name;
            pop @$specifiers;
          }
      }

    unless (defined $identifier)
      {
        die "Declaration lacks an identifier\n";
      }

    my $type = $self->get_decl_type($namespace, $specifiers, $decl_attributes);

    if ($type->isa('CType::Function'))
      {
        $type->set_inline($inline);
      }

    if ($storage_class and $storage_class->class eq 'typedef')
      {
        $type->set_location($CParse::current_location);
        $namespace->set('ordinary', $identifier, $type);
      }
    else
      {
        # Unclassified decls are extern, or near enough
        my $class = $storage_class ? $storage_class->class : 'extern';
        my $decl = new CDecl $class, $identifier, $type, $CParse::current_location;
        $namespace->set('ordinary', $identifier, $decl);
      }
  }

1;
