The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# Copyright 2009 Kevin Ryde

# This file is part of Chart.
#
# Chart is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3, or (at your option) any later version.
#
# Chart is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with Chart.  If not, see <http://www.gnu.org/licenses/>.

package App::Chart::IndicatorInfo;
use 5.010;
use strict;
use warnings;
use Locale::TextDomain ('App-Chart');

use constant DEBUG => 0;

sub new {
  my ($class, $key) = @_;
  if (DEBUG) { say "IndicatorInfo $key"; }
  if ($key && $key =~ /^(GT|TA)_/p) {
    $key = ${^POSTMATCH};
    $class = "App::Chart::IndicatorInfo::$1";
  }
  if ($key && $key eq 'None') {
    $key = undef;
  }
  return bless { key => $key }, $class;
}

sub manual {
  my ($self) = @_;
  my $func = $self->module_func('manual') || return undef;
  return $func->();
}
sub parameter_info {
  my ($self) = @_;
  my $func = $self->module_func('parameter_info') || return [];
  return $func->();
}

sub module_func {
  my ($self, $funcname) = @_;
  my $module = $self->module_load || return;
  return $module->can($funcname);
}
my %warned;
sub module_load {
  my ($self) = @_;
  my $module = $self->module;
  require Module::Load;
  if (! eval { Module::Load::load($module); 1 }) {
    print "module_load(): Cannot load $module\n";
    $warned{$module} ||= do { warn "Cannot load $module: $@"; 1 };
    return undef;
  }
  return $module;
}
sub module {
  my ($self) = @_;
  return ($self->{'key'} && "App::Chart::Series::Derived::$self->{'key'}");
}

#------------------------------------------------------------------------------
package App::Chart::IndicatorInfo::GT;
use strict;
use warnings;
use Locale::TextDomain ('App-Chart');
our @ISA = ('App::Chart::IndicatorInfo');

# ENHANCE-ME: @DEFAULT_ARGS shows when OHLCV needed ...

use constant manual => __p('manual-node','Other Indicator Packages');

sub parameter_info {
  my ($self) = @_;
  my $module = $self->module_load || return;
  my @default_args = do { no strict 'refs'; @{"${module}::DEFAULT_ARGS"} };
  my @ret;
  foreach my $arg (@default_args) {
    if (Scalar::Util::looks_like_number ($arg)) {
      my $i = @ret;
      push @ret, { name    => "Arg$i",
                   key     => "GT-arg$i",
                   default => $default_args[$i],
                 };
    }
  }
  return \@ret;
}

sub module {
  my ($self) = @_;
  return 'GT::Indicators::' . $self->{'key'};
}

#------------------------------------------------------------------------------
package App::Chart::IndicatorInfo::TA;
use strict;
use warnings;
use Locale::TextDomain ('App-Chart');
our @ISA = ('App::Chart::IndicatorInfo');

use constant DEBUG => 0;

use constant { manual => __p('manual-node','Other Indicator Packages'),
               module => 'Finance::TA',
             };

sub parameter_info {
  my ($self) = @_;
  $self->module_load || return [];
  my @ret;

  my $func = $self->{'key'};
  my ($fh, $fi) = _funcbits ($self);
  foreach my $i (0 .. $fi->{'nbOptInput'} - 1) {
    my ($info, $retcode);
    ($retcode = Finance::TA::TA_GetOptInputParameterInfo ($fh, $i, \$info))
      == $Finance::TA::TA_SUCCESS
        or die "Function $func parameter $i ",_retcode_str ($retcode);

    my $elem = { key     => "TA_${func}_$info->{'paramName'}",
                 name    => $info->{'displayName'},
                 default => $info->{'defaultValue'},
                 type    => 'integer',
               };
    push @ret, $elem;

    my $dataset = $info->{'dataSet'};
    if (DEBUG) { say "dataSet $dataset"; }

    if ($info->{'type'} == $Finance::TA::TA_OptInput_RealRange) {
      $elem->{'type'} = 'float';
    }

    # TA_IntegerRange and TA_RealRange
    {
      ## no critic (RequireCheckingReturnValueOfEval)
      eval {
        $elem->{'minimum'} = $dataset->{'min'};
      };
      eval {
        # dummy 100_000 for no maximum
        if ((my $max = $dataset->{'max'}) != 100_000) {
          $elem->{'maximum'} = $max;
        }
      };
      # TA_RealRange
      eval {
        $elem->{'decimals'} = $dataset->{'precision'};
      };
    }

    # TA_RealRange and TA_IntegerRange 'suggested_increment',
    # 'suggested_start', 'suggested_end' are meant for mechanical searching
    # rather than user controls ...

    # TA_OPTIN_IS_PERCENT
    # TA_OPTIN_IS_DEGREE   angle
    # TA_OPTIN_IS_CURRENCY
    # TA_OPTIN_ADVANCED
  }
  return \@ret;
}

sub _funcbits {
  my ($self) = @_;
  my ($fh, $fi, $retcode);
  ($retcode = Finance::TA::TA_GetFuncHandle ($self->{'key'}, \$fh))
    == $Finance::TA::TA_SUCCESS
      or die "FuncHandle $self->{'key'} ",_retcode_str ($retcode);

  ($retcode = Finance::TA::TA_GetFuncInfo ($fh, \$fi))
    == $Finance::TA::TA_SUCCESS
      or die "FuncInfo $self->{'key'} ",_retcode_str ($retcode);

  return ($fh, $fi);
}
sub _retcode_str {
  my ($retcode) = @_;
  my $rci = Finance::TA::TA_RetCodeInfo->new ($retcode);
  return "[$rci->{'enumStr'}] $rci->{'infoStr'}";
}

#------------------------------------------------------------------------------
package App::Chart::IndicatorInfo::Undef;
use strict;
use warnings;

use constant
  { manual => undef,
    parameter_info => [],
  };

1;
__END__