#!/usr/bin/env perl

use strict;
use warnings;
use File::Path qw(make_path);
use File::Basename qw(dirname);
use Getopt::Long qw(GetOptions);

# Parse options
my $force;
my $description;
my $input_file;
GetOptions(
  'force'           => \$force,
  'description=s'   => \$description,
  'input-file=s'    => \$input_file,
);

# Parse args
my ($class_name, $dir) = @ARGV;

unless (length $class_name && length $dir && $input_file) {
  die "Usage: $0 --input-file <file> [--force] [--description 'desc'] <class_name> <dir>\n";
}

# Set paths
my @class_parts = split('::', $class_name);
my $file_name = pop @class_parts;
my $class_name_dir = "$dir/SPVM/" . join('/', @class_parts);
my $class_name_path = join('/', @class_parts);

my $spvm_file = "$class_name_dir/$file_name.spvm";
my $c_file    = "$class_name_dir/$file_name.c";
my $pm_file   = "$class_name_dir/$file_name.pm";

# Init contents
my $class_name_c = $class_name;
$class_name_c =~ s/::/__/g;

my $spvm_content = "class $class_name {\n";
my $c_content = qq|#include "spvm_native.h"\n\nstatic const char* FILE_NAME = "$class_name_path.c\n\n";|;

# Name section with description
my $name_header = $class_name;
$name_header .= " - $description" if $description;
my $pm_content   = "=head1 Name\n\n$name_header\n\n=head1 Class Methods\n\n";

# Open input handle
open my $fh_input, '<', $input_file or die "Cannot open $input_file: $!";

# Read inputs
while (my $line = <$fh_input>) {
  chomp $line;
  next if $line =~ /^#/;
  next unless $line =~ /\S/;
  
  my ($constant_name, $type) = split(/\s*:\s*/, $line);
  $type ||= 'int';

  my $return_type;
  my $set_value;

  # Determine return type and value setter
  if ($type eq 'byte') {
    $return_type = 'byte';
    $set_value = "stack[0].bval = (int8_t)$constant_name;";
  }
  elsif ($type eq 'short') {
    $return_type = 'short';
    $set_value = "stack[0].sval = (int16_t)$constant_name;";
  }
  elsif ($type eq 'int') {
    $return_type = 'int';
    $set_value = "stack[0].ival = (int32_t)$constant_name;";
  }
  elsif ($type eq 'long') {
    $return_type = 'long';
    $set_value = "stack[0].lval = (int64_t)$constant_name;";
  }
  elsif ($type eq 'float') {
    $return_type = 'float';
    $set_value = "stack[0].fval = (float)$constant_name;";
  }
  elsif ($type eq 'double') {
    $return_type = 'double';
    $set_value = "stack[0].dval = (double)$constant_name;";
  }
  elsif ($type eq 'string') {
    $return_type = 'string';
    $set_value = "stack[0].sval = env->new_string_nolen(env, stack, $constant_name);";
  }
  else {
    warn "Unknown type: $type for $constant_name. Ignoring.\n";
    next;
  }

  # Append SPVM
  $spvm_content .= "  native static method $constant_name : $return_type ();\n\n";

  # Append C
  $c_content .= <<"EOS";
int32_t SPVM__${class_name_c}__$constant_name(SPVM_ENV* env, SPVM_VALUE* stack) {
#ifdef $constant_name
  $set_value
  return 0;
#else
  env->die(env, stack, "$constant_name is not defined on the system", __func__, FILE_NAME, __LINE__);
  return SPVM_NATIVE_C_BASIC_TYPE_ID_ERROR_NOT_SUPPORTED_CLASS;
#endif
}

EOS

  # Append PM
  $pm_content .= <<"EOS";
=head2 $constant_name

C<static method $constant_name : $return_type ();>

Returns the value of C<$constant_name>. If this constant is not defined on the system, L<Error::NotSupported|SPVM::Error::NotSupported> exception is thrown.

EOS
}
close $fh_input;

$spvm_content .= "}\n";

# Output files
generate_file($spvm_file, $spvm_content, $force);
generate_file($c_file, $c_content, $force);
generate_file($pm_file, $pm_content, $force);

# File generator
sub generate_file {
  my ($file, $content, $force_flag) = @_;
  
  my $dir_name = dirname($file);
  make_path($dir_name) unless -d $dir_name;
  
  if ($force_flag || !-f $file) {
    print "  [write]$file\n";
    open my $fh, '>', $file or die $!;
    print $fh $content;
    close $fh;
  }
  else {
    print "  [exists]$file\n";
  }
}

=head1 Name
  
  spvmdist-native-const - Generate native constants files
  
=head1 Usage

  ./util/spvmdist-native-const --input-file=.tmp/constants.txt --description='Constants for libuv' Go::UV::Constant .tmp/lib

  ./util/spvmdist-native-const --force --input-file=.tmp/constants.txt --description='Constants for libuv' Go::UV::Constant .tmp/lib
  
  # Onliner with curl
  curl -L https://raw.githubusercontent.com/yuki-kimoto/SPVM/refs/heads/master/util/spvmdist-native-const | perl - --input-file=.tmp/constants.txt --description='Constants for libuv' Go::UV::Constant .tmp/lib
  