package CExpr::Cast;

use 5.6.0;
use strict;
use warnings;

use CExpr;
use Math::BigInt;

our @ISA = qw/CExpr/;

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

    my $type = shift;
    my $arg = shift;

    my $self = {type => $type,
                arg => $arg,
               };
    bless $self, $class;
    return $self;
  }

sub dump_c
  {
    my $self = shift;

    my $type = $self->{type}->dump_c;
    my $arg = $self->{arg}->dump_c;

    return "($type)($arg)";
  }

sub compute
  {
    my $self = shift;

    my $arg = $self->{arg}->compute;
    my $type = $self->{type};

    if ($type->isa('CType::Fundamental::Bool'))
      {
        return $arg ? 1 : 0;
      }

    if ($type->can_represent($arg))
      {
        return $arg;
      }

    if ($type->nature eq 'int')
      {
        unless ($type->signed)
          {
            return $arg & $type->max_value;
          }

        # Converting to signed. This is gcc-specific, and quite
        # painful

        # First, we strip away the excess bits
        my $mask = Math::BigInt->bone();
        $mask <<= $type->width;
        $mask--;
        my $v = $arg & $mask;

        # Now, it's been forced unsigned by the &, so we have to
        # reinterpret as a two's-complement signed integer

        # We need to know what the highest bit in the type is
        my $highest_bit = Math::BigInt->bone();
        $highest_bit <<= $type->width - 1;

        if ($v & $highest_bit)
          {
            # This should be negative. First take the two's
            # complement, to get the positive value
            $v->bxor($mask);
            $v->binc;
            # Then negate it
            $v->bneg;
          }

        return $v;
      }

    die;
  }

sub get_refs
  {
    my $self = shift;
    return ($self->{type}->get_refs, $self->{arg}->get_refs);
  }

sub layout
  {
    my $self = shift;
    my $accept_incomplete = shift;
    my $namespace = shift;
    $self->{type}->layout($accept_incomplete, $namespace);
    $self->{arg}->layout($accept_incomplete, $namespace);
  }

1;
