##############################################
# $Id: SetExtensions.pm 25286 2021-12-03 10:16:56Z rudolfkoenig $

package main;
use strict;
use warnings;
use AttrTemplate;

sub SetExtensions($$@);
sub SetExtensionsFn($);

sub
SetExtensionsCancel($)
{
  my ($hash) = @_;
  $hash = $defs{$hash} if( ref($hash) ne 'HASH' );

  return undef if( !$hash );
  return undef if( $hash->{InSetExtensions} );
  my $name = $hash->{NAME};

  if($hash->{TIMED_OnOff}) {      # on-for-timer, blink
    my $cmd = $hash->{TIMED_OnOff}{CMD};
    RemoveInternalTimer($hash->{TIMED_OnOff});
    delete $hash->{TIMED_OnOff};
  }

  for my $sfx ("_till", "_intervalFrom", "_intervalNext") {
    CommandDelete(undef, $name.$sfx) if($defs{$name.$sfx});
  }
  return undef;
}

sub
SE_DoSet(@)
{
  my $hash = $defs{$_[0]};
  $hash->{InSetExtensions} = 1;
  AnalyzeCommand($hash->{CL}, "set ".join(" ", @_)); # cmdalias (Forum #63896)
  delete $hash->{InSetExtensions};
  delete $hash->{SetExtensionsCommand};
}

sub
SetExtensions($$@)
{
  my ($hash, $list, $name, $cmd, @a) = @_;

  return AttrTemplate_Set($hash, $list, $name, $cmd, @a) if(!$list);

  my %se_list = (
    "on-for-timer"      => 1,
    "off-for-timer"     => 1,
    "on-till"           => 1,
    "off-till"          => 1,
    "on-till-overnight" => 1,
    "off-till-overnight"=> 1,
    "blink"             => 0,
    "intervals"         => 0,
    "toggle"            => 0
  );

  sub
  getCmd($$)
  {
    my ($list, $lCmd) = @_;
    my $uCmd = uc($lCmd);
    return ($list =~ m/(^| )$lCmd\b/ ? $lCmd :
           ($list =~ m/(^| )$uCmd\b/ ? $uCmd : ""));
  }

  # Must work with EnOceans "attr x eventMap BI:off B0:on"
  sub
  getReplCmd($$)
  {
    my ($name, $cmd) = @_;
    my (undef,$value) = ReplaceEventMap($name, [$name, $cmd], 0);
    return $cmd if($value ne $cmd);

    $cmd = uc($cmd);
    (undef,$value) = ReplaceEventMap($name, [$name, $cmd], 0);
    return $cmd if($value ne $cmd);
    return "";
  }

  my $onCmd  = getCmd($list, "on");
  my $offCmd = getCmd($list, "off");

  my $eventMap = AttrVal($name, "eventMap", undef);
  my $fixedIt;
  if((!$onCmd || !$offCmd) && $eventMap) {
    $onCmd  = getReplCmd($name, "on")  if(!$onCmd);
    $offCmd = getReplCmd($name, "off") if(!$offCmd && $onCmd);
    $fixedIt = 1;
  }

  # Forum #124505
  $list =~ s/:\{([^ ]+)\}/$cmdFromAnalyze=$1; ":".(eval $1)/ge if($cmd eq "?");

  if(!$onCmd || !$offCmd) { # No extension
    return AttrTemplate_Set($hash, $list, $name, $cmd, @a);
  }

  $cmd = ReplaceEventMap($name, $cmd, 1) if($fixedIt);

  if(!defined($se_list{$cmd})) {
    # Add only "new" commands
    my @mylist = grep { $list !~ m/\b$_\b/ } keys %se_list;
    return AttrTemplate_Set($hash, "$list ".join(" ", @mylist), $name, $cmd,@a);
  }
  if($se_list{$cmd} && $se_list{$cmd} != int(@a)) {
    return "$cmd requires $se_list{$cmd} parameter";
  }

  SetExtensionsCancel($hash);
  my $cmd1 = ($cmd =~ m/^on.*/i ? $onCmd : $offCmd);
  my $cmd2 = ($cmd =~ m/^on.*/i ? $offCmd : $onCmd);
  my $param = $a[0];


  $hash->{SetExtensionsCommand} = $cmd.(@a ? " ".join(" ",@a) : "");
  if($cmd eq "on-for-timer" || $cmd eq "off-for-timer") {
    return "$cmd requires a number as argument" if($param !~ m/^\d*\.?\d*$/);

    if($param) {
      $hash->{TIMED_OnOff} = {
        START=>time(), START_FMT=>TimeNow(), DURATION=>$param,
        CMD=>$cmd, NEXTCMD=>$cmd2, hash=>$hash
      };
      SE_DoSet($name, $cmd1);
      InternalTimer(gettimeofday()+$param,
                        "SetExtensionsFn", $hash->{TIMED_OnOff}, 0);
    }

  } elsif($cmd =~ m/^(on|off)-till/) {
    my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($param);
    return "$cmd: $err" if($err);

    my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
    if($cmd =~ m/-till$/) {
      my @lt = localtime;
      my $hms_now  = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
      if($hms_now ge $hms_till) {
        Log3 $hash, 4,
          "$name $cmd: won't switch as now ($hms_now) is later than $hms_till";
        return "";
      }
      if($hms_till ge "24") { # sunrise, #89985
        Log3 $hash, 4, "$name $cmd: won't switch as $hms_till is tomorrow";
        return "";
      }
    }
    SE_DoSet($name, $cmd1);
    CommandDefine(undef, "${name}_till at $hms_till set $name $cmd2");

  } elsif($cmd eq "blink") {
    my $p2 = $a[1]; # 3rd. parameter is used internally, to spcify the phase
    return "$cmd requires count as integer and duration as float"
        if(!defined($param) ||
           !defined($p2) ||
           $param !~ m/^\d+$/ ||
           $p2 !~ m/^\d*\.?\d*$/);

    if($param) { # count reached 0
      delete($hash->{SetExtensionsCommand}) if($param == 1 && $a[2]);
      SE_DoSet($name, $a[2] ? $offCmd : $onCmd);
      $param-- if($a[2]);
      if($param) {
        $hash->{TIMED_OnOff} = {
          START=>time(), START_FMT=>TimeNow(), DURATION=>$param,
          CMD=>$cmd, NEXTCMD=>"$cmd $param $p2 ".($a[2] ? "0" : "1"),
          hash=>$hash
        };
        InternalTimer(gettimeofday()+$p2,
                        "SetExtensionsFn", $hash->{TIMED_OnOff}, 0);
      }
    }

  } elsif($cmd eq "intervals") {

    my $intSpec = shift(@a);
    if($intSpec) {
      my ($from, $till) = split("-", $intSpec);

      my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($from);
      return "$cmd: $err" if($err);
      my @lt = localtime;
      my $hms_from = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
      my $hms_now  = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
      delete($hash->{SetExtensionsCommand});  # Will be set by on-till

      if($hms_from le $hms_now) { # By slight delays at will schedule tomorrow.
        SetExtensions($hash, $list, $name, "on-till", $till);

      } else {
        CommandDefine(undef,
                "${name}_intervalFrom at $from set $name on-till $till");

      }

      if(@a) {
        my $rest = join(" ", @a);
        my ($from, $till) = split("-", shift @a);
        CommandDefine(undef,
                "${name}_intervalNext at $from set $name intervals $rest");
      }
    }
    
  } elsif($cmd eq "toggle") {
    delete($hash->{SetExtensionsCommand});  # Need on/off in STATE
    my $value = Value($name);
    (undef,$value) = ReplaceEventMap($name, [$name, $value], 0) if($eventMap);

    $value = ($1==0 ? $offCmd:$onCmd) if($value =~ m/dim (\d+)/); # Forum #49391
    SE_DoSet($name, $value =~ m/^on/i ? $offCmd : $onCmd);

  }

  return undef;
}

sub
SetExtensionsFn($)
{
  my ($too) = @_;
  my $hash = $too->{hash};
  return if(!$hash || !$defs{$hash->{NAME}}); # deleted

  my $nextcmd = $hash->{TIMED_OnOff}{NEXTCMD};
  delete $hash->{TIMED_OnOff};
  SE_DoSet($hash->{NAME}, split(" ",$nextcmd));
}

1;
