package SPVM::Math::BigDecimal {
  use SPVM::List; # TODO: Replace SPVM::Text
  use SPVM::Byte;

  # { digits: "12345", exp: 3 } => "1.2345e3"
  has digits : private ro string; # leading & trailing 0 are not included.
  has exp : private ro int;

  sub _validate_and_set : void ($self : self, $val : string) {
    my $length = length $val;
    unless ($length > 0) {
      die "val must not be empty";
    }
    if ($val->[0] == '.') {
      die "Cannot start with '.'";
    }
    if ($length > 2 && $val->[0] == '0' && $val->[1] != '.') {
      die "If the first character is '0', the next character must be '.'";
    }
    if ($val->[$length - 1] == '.') {
      die "'.' cannot exist at the end of digits";
    }

    my $digits = SPVM::List->new;
    my $dot_pos = -1;
    my $exp_pos = -1;
    for (my $i = 0; $i < $length; ++$i) {
      if ('0' <= $val->[$i] && $val->[$i] <= '9') {
        $digits->push(SPVM::Byte->new($val->[$i]));
      }
      elsif ($val->[$i] == '.' && $dot_pos == -1) {
        $dot_pos = $i;
      }
      elsif (($val->[$i] == 'e' || $val->[$i] == 'E') && $exp_pos == -1) {
        ++$i;
        $exp_pos = $i;
        unless ($i < $length) {
          die "digits must exist after e/E";
        }
        if ($val->[$i] == '+') {
          ++$i;
          $exp_pos = $i;
        }
        elsif ($val->[$i] == '-') {
          ++$i;
        }
        unless ($i < $length) {
          die "digits must exist after e/E";
        }
        while ($i < $length) {
          unless ('0' <= $val->[$i] && $val->[$i] <= '9') {
            die "Not digit or '+'/'-' characters are allowed after 'e'/'E'";
          }
          ++$i;
        }
        last;
      }
      else {
        die "Invalid decimal: '$val'";
      }
    }
    $self->{exp} = 0;
    if ($dot_pos >= 0) {
      $self->{exp} += $dot_pos - 1;
    }
    if ($exp_pos >= 0) {
      my $exp_str = sliceb((byte [])$val, $exp_pos, $length - $exp_pos);
      $self->{exp} += stoi($exp_str);
    }

    my $digits_length = $digits->length;

    # remove leading 0
    for (my $i = 0; $i < $digits_length; ++$i) {
      if (((SPVM::Byte)($digits->get(0)))->val == '0') {
        --$self->{exp};
        $digits->shift;
      }
      else {
        last;
      }
    }

    # remove trailing 0
    $digits_length = $digits->length;
    for (my $i = 0; $i < $digits_length; ++$i) {
      if (((SPVM::Byte)($digits->get($digits->length - 1)))->val == '0') {
        $digits->pop;
      }
      else {
        last;
      }
    }

    my $byte_digits = new byte [$digits->length];
    for (my $i = 0; $i < @$byte_digits; ++$i) {
      $byte_digits->[$i] = $digits->shift;
    }
    $self->{digits} = (string)$byte_digits;
  }

  sub new : SPVM::Math::BigDecimal ($val : string) {
    my $self = new SPVM::Math::BigDecimal;
    $self->_validate_and_set($val);
    return $self;
  }

  sub to_str : string ($self : self) {
    if ($self->{exp} == 0) {
      my $digits_length = length($self->{digits});
      my $ret = new byte [$digits_length + 1];
      $ret->[0] = $self->{digits}->[0];
      $ret->[1] = '.';
      for (my $i = 0; $i < $digits_length - 1; ++$i) {
        $ret->[$i + 2] = $self->{digits}->[$i + 1];
      }
      return $ret;
    }
    else {
      my $exp_str = "" . $self->{exp};
      my $digits_length = length($self->{digits});
      my $exp_length = length($exp_str);
      if ($digits_length == 1) {
        my $ret = new byte [$exp_length + 2];
        $ret->[0] = $self->{digits}->[0];
        $ret->[1] = 'e';
        for (my $i = 0; $i < $exp_length; ++$i) {
          $ret->[$i + 2] = $exp_str->[$i];
        }
        return $ret;
      }
      else {
        my $ret = new byte [$digits_length + $exp_length + 2];
        $ret->[0] = $self->{digits}->[0];
        $ret->[1] = '.';
        for (my $i = 0; $i < $digits_length - 1; ++$i) {
          $ret->[$i + 2] = $self->{digits}->[$i + 1];
        }
        $ret->[$digits_length + 1] = 'e';
        for (my $i = 0; $i < $exp_length; ++$i) {
          $ret->[$i + $digits_length + 2] = $exp_str->[$i];
        }
        return $ret;
      }
    }
  }

  # TODO: numeric operators: add, minus, mul, divide, mod
}
