package SPVM::Builder::DependencyAnalyzer;

use strict;
use warnings;
use Carp 'confess';
use JSON::PP ();
use File::Basename 'basename';

use SPVM::Builder;
use SPVM::Builder::CC;
use SPVM::Builder::Util;

use SPVM::Builder::ScriptInfo;

# Fields
sub script_name {
  my $self = shift;
  if (@_) {
    $self->{script_name} = $_[0];
    return $self;
  }
  else {
    return $self->{script_name};
  }
}

sub with_version {
  my $self = shift;
  if (@_) {
    $self->{with_version} = $_[0];
    return $self;
  }
  else {
    return $self->{with_version};
  }
}

sub excluded_class_names {
  my $self = shift;
  if (@_) {
    $self->{excluded_class_names} = $_[0];
    return $self;
  }
  else {
    return $self->{excluded_class_names};
  }
}

# Class Methods
sub new {
  my $class = shift;
  
  my $self = {
    excluded_class_names => [],
    @_,
  };
  
  return bless $self, ref $class || $class;
}

sub to_lines {
  my ($class, $array) = @_;
  
  my $lines = '';
  
  for my $elem (@$array) {
    $lines .= "$elem\x0A";
  }
  
  return $lines;
}

# Instance Methods
sub dump_resource_info {
  my ($self) = @_;
  
  my $script_name = $self->{script_name};
  
  my $script_info = SPVM::Builder::ScriptInfo->new(script_name => $script_name);
  
  my $class_names = [grep { $script_info->is_resource_loader($_) } @{$script_info->get_class_names}];
  
  my $resource_info = "";
  
  for my $class_name (@$class_names) {
    my $config_file = $script_info->get_config_file($class_name);
    
    $resource_info .= <<"EOS";
[$class_name]
# $config_file
# Loaded Resources:
EOS
    
    for my $resource_name (@{$script_info->get_config($class_name)->get_resource_names}) {
      $resource_info .= "#    $resource_name\n";
    }
    
    my $config_content = $script_info->get_config_content($class_name);
    
    $resource_info .= "$config_content\n";
  }
  
  return $resource_info;
}

sub to_class_infos {
  my ($self) = @_;
  
  my $script_name = $self->{script_name};
  
  my $excluded_class_names = $self->{excluded_class_names};
  
  my $script_info = SPVM::Builder::ScriptInfo->new(script_name => $script_name);
  
  my $runtime = $script_info->runtime;
  
  my $class_names = $script_info->get_class_names;
  
  my $class_infos = [];
  
  for my $class_name (sort @$class_names) {
    
    my $skip = 0;
    for my $excluded_class_name (@$excluded_class_names) {
      
      if ($excluded_class_name =~ /[^\w:\*]/) {
        Carp::confess("--exclude option in spvmdeps command contains invalid characters.");
      }
      
      my $excluded_class_name_re = $excluded_class_name;
      
      $excluded_class_name_re =~ s/\*/.*/g;
      
      $excluded_class_name_re = "^$excluded_class_name_re\$";
      
      if ($class_name =~ /$excluded_class_name_re/) {
        $skip = 1;
        last;
      }
    }
    
    next if $skip;
    
    my $basic_type = $runtime->get_basic_type_by_name($class_name);
    
    my $class_info = {};
    
    $class_info->{class_name} = $class_name;
    
    my $version_string = $basic_type->get_version_string;
    
    my $basic_type_in_version_from = $basic_type->get_basic_type_in_version_from;
    
    if (defined $version_string) {
      $class_info->{version} = $version_string;
    }
    elsif ($basic_type_in_version_from) {
      my $basic_type_name_in_version_from = $basic_type_in_version_from->get_name;
      $class_info->{version_from} = $basic_type_name_in_version_from;
    }
    
    push @$class_infos, $class_info;
  }
  
  return $class_infos;
}

sub to_json {
  my ($self) = @_;
  
  my $script_name = $self->{script_name};
  
  my $with_version = $self->{with_version};
  
  my $script_info = SPVM::Builder::ScriptInfo->new(script_name => $script_name);
  
  my $class_infos = $self->to_class_infos;
  
  my $class_names = [];
  
  my $json = "[\x0A";
  
  for (my $i = 0; $i < @$class_infos; $i++) {
    my $class_info = $class_infos->[$i];
    
    my $class_name = $class_info->{class_name};
    
    my $version = $class_info->{version};
    
    my $version_from = $class_info->{version_from};
    
    $json .= "  {";
    
    $json .= "\"class_name\":\"$class_name\"";
    if ($with_version) {
      if (defined $version) {
        $json .= ",\"version\":\"$version\"";
      }
      elsif ($version_from) {
        $json .= ",\"version_from\":\"$version_from\"";
      }
    }
    $json .= "}";
    
    if ($i < @$class_infos - 1) {
      $json .= ",";
    }
    
    $json .= "\x0A";
  }
  
  $json .= "]\x0A";
  
  return $json;
}

sub to_class_names {
  my ($self) = @_;
  
  my $script_name = $self->{script_name};
  
  my $with_version = $self->{with_version};
  
  my $script_info = SPVM::Builder::ScriptInfo->new(script_name => $script_name);
  
  my $class_infos = $self->to_class_infos;
  
  my $class_names = [];
  
  for my $class_info (@$class_infos) {
    
    my $class_name_only = $class_info->{class_name};
    
    my $version = $class_info->{version};
    
    my $version_from = $class_info->{version_from};
    
    my $class_name = "$class_name_only";
    
    if ($with_version) {
      if (defined $version) {
        $class_name .= " $version";
      }
      elsif ($version_from) {
        $class_name .= " (version_from $version_from)";
      }
    }
    
    push @$class_names, $class_name;
  }
  
  return $class_names;
}

sub to_cpanm_commands {
  my ($self) = @_;
  
  my $script_name = $self->{script_name};
  
  my $with_version = $self->{with_version};
  
  my $script_info = SPVM::Builder::ScriptInfo->new(script_name => $script_name);
  
  my $class_infos = $self->to_class_infos;
  
  my $cpanm_commands = [];
  
  for my $class_info (@$class_infos) {
    
    my $class_name = $class_info->{class_name};
    
    my $version = $class_info->{version};
    
    if (defined $version) {
      my $cpanm_command = "cpanm ";
      
      if ($class_name eq "SPVM") {
        $cpanm_command .= "SPVM";
      }
      else {
        $cpanm_command .= "SPVM::$class_name";
      }
      
      if ($with_version) {
        if (defined $version) {
          $cpanm_command .= "\@$version";
        }
      }
      
      push @$cpanm_commands, $cpanm_command;
    }
  }
  
  return $cpanm_commands;
}

sub to_cpanfile_commands {
  my ($self) = @_;
  
  my $script_name = $self->{script_name};
  
  my $with_version = $self->{with_version};
  
  my $script_info = SPVM::Builder::ScriptInfo->new(script_name => $script_name);
  
  my $class_infos = $self->to_class_infos;
  
  my $cpanfile_commands = [];
  
  for my $class_info (@$class_infos) {
    
    my $class_name = $class_info->{class_name};
    
    my $version = $class_info->{version};
    
    if (defined $version) {
      my $cpanfile_command = "requires ";
      
      if ($class_name eq "SPVM") {
        $cpanfile_command .= "'SPVM'";
      }
      else {
        $cpanfile_command .= "'SPVM::$class_name'";
      }
      
      if ($with_version) {
        if (defined $version) {
          $cpanfile_command .= ", '== $version'";
        }
      }
      
      $cpanfile_command .= ";";
      
      push @$cpanfile_commands, $cpanfile_command;
    }
  }
  
  return $cpanfile_commands;
}

1;

=head1 Name

SPVM::Builder::DependencyAnalyzer - Utility for Analyzing Class Dependencies

=head1 Copyright & License

Copyright (c) 2023 Yuki Kimoto

MIT License
