#!/usr/bin/perl
#
# $Header: /cvsroot/ivtv/ivtv/ivtv/utils/ptune.pl,v 1.12 2003/07/16 03:30:39 pcxuser Exp $
#
use strict;

use Getopt::Long qw(:config no_ignore_case bundling);
use Video::Frequencies;
use Video::ivtv 0.12;
use Config::IniFiles;

my $version="1.10";

my %settings = (
  Channel           => 4,		# default to the ivtv default channel
  VideoDevice       => "/dev/video0",
  FrequencyTable    => "ntsc-cable",  # default to NTSC_CABLE mapping.
  Frequency         => "",
  ConfigFileName    => "$ENV{HOME}/.ivtvrc",
  UseConfigFile     => 0,
  UsingIvtvDriver   => 1,  # default to being able to use the ivtv "enhancements".
  # msp matrix settings
  SetMSPMatrix      => 1,
  MSPInput          => 3,
  MSPOutput         => 1,
  MSPSleep          => 2,  # number of seconds the card needs before we can set the msp matrix.
  # other settings
  Debug             => 0,
  TunerNum          => 0,
);

# map the Settings/Config file parameter to the command line variable that specifies it.
my %mappings = (
    "Channel"           => "c|channel",
    "VideoDevice"       => "d|input",
    "FrequencyTable"    => "f|freqtable",
    "Frequency"         => "F|frequency",
    "Debug"             => "debug",
    "TunerNum"          => "tuner-num",
    "SetMSPMatrix"      => "set-msp-matrix",
    "MSPInput"          => "msp-input",
    "MSPOutput"         => "msp-output",
    "MSPSleep"          => "msp-sleep",
  );

my @profileNames=(); # list of user defined sections to work with in the config file.
my %configIni;      # config hash we tie to for Config::IniFiles.
my $ivtvObj = Video::ivtv->new();

# check for devfs support
if ( -e "/dev/.devfsd" )
{
  $settings{VideoDevice} = "/dev/v4l/video0";
}

# check for the config file
if (-f $settings{ConfigFileName})
{
  $settings{UseConfigFile} = 1;

  # tie to it.
  tie %configIni, 'Config::IniFiles', (-file => $settings{ConfigFileName}) or die "Error: Opening config file '$settings{ConfigFileName}' failed! $!\n";

  my $profile = "defaults";
  if (exists $configIni{$profile})
  {
    # update the defaults stored.
    foreach my $arg (keys %mappings)
    {
      if (exists $configIni{$profile}{$arg})
      {
        $settings{$arg} = $configIni{$profile}{$arg};
        #print "settings{$arg} = '" . $settings{$arg} . "'\n";
      }
    }
  }
  else
  {
    print "Warning: config file '$settings{ConfigFileName}' exists but does not have the\n[$profile] section!  Use record_ivtv.pl to create it.\n\n";
  }
}
else  # create the config file
{
  print "Auto Creating config file $settings{ConfigFileName}...\n";
  my $profile = "defaults";

  # we have to create the config file and tie to it.
  tie %configIni, 'Config::IniFiles', () or die "Error: Initializing config file '$settings{ConfigFileName}' failed! $!\n";

  # now set the name to work with.
  tied(%configIni)->SetFileName($settings{ConfigFileName}) or die "Error: Setting config file to '$settings{ConfigFileName}' failed! $!\n";

  $configIni{$profile} = {};  # make sure the section exists.

  foreach my $arg (keys %mappings)
  {
    $configIni{$profile}{$arg} = $settings{$arg};
    print "configIni{$profile}{$arg} = '" . $settings{$arg} . "'\n" if $settings{Debug};
  }

  # write the config file out.
  tied(%configIni)->RewriteConfig or die "Error: Writing config file '$settings{ConfigFileName}' failed!  $!\n";
}

# build up the "custom" frequency table
my %customMap = ();
foreach my $profileName (keys %configIni)
{
  next if $profileName =~ /^(defaults)$/;

  if (exists $configIni{$profileName}{Frequency})
  {
    $customMap{$profileName} = $configIni{$profileName}{Frequency};
  }
}
$CHANLIST{custom} = \%customMap;

my $versionStr = "ptune.pl $version for use with http://ivtv.sf.net/";
my $usageStr = <<"END_OF_USAGE";
$versionStr

Usage: ptune.pl [--channel CHANNEL] [--input VIDEO_DEV]
       [--freqtable FREQENCY MAP] [--frequency FREQUENCY]
       [--set-msp-matrix BOOL] [--msp-sleep SLEEP]
       [--msp-input INPUT] [--msp-output OUTPUT]
       [--profile PROFILE] [--list-freqtable] [--list-channels]
       [--tuner-num TUNERNUM] [--help] [--version] [--debug]

  -c/--channel CHANNEL: channel number to switch to
      NOTE: You can also specify the channel by itself.
            Ex.  record-v4l2.pl 73
            would change to channel 73 using the default settings
            or the settings from your ~/.ivtvrc config file.
  -d/--input VIDEO_DEV: video device to work with
  -f/--freqtable FREQUENCY MAP: Specify the frequency mapping to use.
  -F/--frequency FREQUENCY: Specify the frequency to tune to.
                ex. 517250 = NTSC Cable 73 (SCiFi)
  --tuner-num TUNERNUM: Specify the tuner to use.
  --set-msp-matrix BOOL: 1 - set the msp matrix after Video Standard changes
                         0 - never set the msp matrix
                         Uses the --msp-input and --msp-output options.
  --msp-sleep SLEEP: number of seconds the card needs before we can program
                     the msp matrix.
  --msp-input INPUT: Specify the input parameter to program the msp matrix.
                     Valid values are from 1 - 8.
  --msp-output OUTPUT: Specify the output parameter to program the msp matrix.
                       Valid values are from 0 - 3.
  -L/--list-freqtable: list all available frequency mappings that
       Video::Frequencies knows
  --list-channels: lists all channels and their frequencies for the
       specified frequency table being used.
  -P/--profile PROFILE: Override defaults and command line values with the
              config entries in the section labeled [PROFILE] from the
              config file $settings{ConfigFileName}.
              Examples: -P NTSC, -P 22

              You can specify this option multiple times and each successive
              profile will overlay the defaults and any previous profiles.
  -h/--help: display this help
  -v/--version: display the version of this program
  --debug: turns on debug output

Notes:
  If you specify both -c/--channel and -F/--frequency
    then -F/--frequency will take precedence.

Defaults:
 --channel $settings{Channel} --input $settings{VideoDevice} --freqtable $settings{FrequencyTable}
 --tuner-num $settings{TunerNum} --set-msp-matrix $settings{SetMSPMatrix} --msp-sleep $settings{MSPSleep} --msp-input $settings{MSPInput} --msp-output $settings{MSPOutput}


 config file = '$settings{ConfigFileName}'

 Note:  This script relies on Perl Modules: Video::Frequencies, Video::ivtv,
 Config::IniFiles and Getopt::Long.
END_OF_USAGE

# handle user input here
my %opts;
#getopts('c:hd:vf:F:L', \%opts);
GetOptions(\%opts, "channel|c=s", "help|h", "input|d=s", "version|v", "freqtable|f=s", "frequency|F=i",
                   "list-freqtable|L", "list-channels", "debug", "profile|P=s@", "tuner-num=i",
                   "set-msp-matrix=i", "msp-input=i", "msp-output=i");
if (scalar keys %opts == 0 && @ARGV == 0)
{
  usage(0, "");
}
foreach my $option (sort keys %opts)
{
  my $found = 0;
  foreach my $mapName (keys %mappings)
  {
    if ($option =~ /^($mappings{$mapName})$/)
    {
      $settings{$mapName} = $opts{$option};
      $found = 1;
      print "$mapName = '$opts{$option}'\n" if $settings{Debug};
    }
  }
  if (!$found)
  {
    # handle the non-settings cases.
    if ($option =~ /^(L|list-freqtable)$/)
    {
      my $errStr = "\nAvailable Frequency Mappings:\n";
      foreach my $name (sort keys %CHANLIST)
      {
        $errStr .= "$name\n";
      }
      print "$versionStr\n$errStr";
      exit 0;
    }
    elsif ($option eq "list-channels")
    {
      my $errStr = "\nAvailable Channels for $settings{FrequencyTable}:\n";
      foreach my $name (sort { $a <=> $b } keys %{$CHANLIST{$settings{FrequencyTable}}})
      {
        $errStr .= "$name\t= $CHANLIST{$settings{FrequencyTable}}->{$name}\n";
      }
      print "$versionStr\n$errStr";
      exit 0;
    }
    elsif ($option =~ /^(P|profile)$/)
    {
      @profileNames = @{$opts{$option}};
    }
    elsif ($option =~ /^(v|version)$/)
    {
      print "$versionStr\n";
      exit 0;
    }
    elsif ($option =~ /^(h|help)$/)
    {
      usage(0, "");
    }
    else
    {
      usage(1, "-$option is an unknown option!");
    }
  }
}

if (@profileNames)
{
  # loop over all profiles the user specified.
  foreach my $profileName (@profileNames)
  {
    print "profile = '$profileName'\n" if $settings{Debug};
    # for now the profile can not be "defaults".
    if ($profileName eq "defaults")
    {
      error(1, "Profile = '$profileName' is invalid!");
    }
    if (exists $configIni{$profileName})
    {
      # update defaults/override command line arguments that exist in this profile.
      my $profile = $profileName;

      foreach my $arg (keys %mappings)
      {
        foreach my $option (split(/\|/, $mappings{$arg})) # handle the long/short command option versions
        {
          #print "arg = '$arg', option = '$option'\n" if $settings{Debug};
          if (exists $configIni{$profile}{$arg} && !(exists $opts{$option}))
          {
            $settings{$arg} = $configIni{$profile}{$arg};
            print "settings{$arg} = '" . $settings{$arg} . "'\n" if $settings{Debug};
            last;
          }
        }
      }
    }
    else
    {
      error(1, "Profile = '$profileName' does not exist!\n\nYou must use record-v4l2.pl and specify -S/--save to create it\nor manually edit the config file and add the section.");
    }
  }
}

# verify input

if (@ARGV)
{
  if (exists $opts{c} || exists $opts{channel})
  {
    print "Warning: ignoring channel argument and using '$ARGV[0]' instead.\n";
  }
  $settings{Channel} = $ARGV[0];
}

if ( ! -c "$settings{VideoDevice}")
{
  usage(1, "Video Dev = '$settings{VideoDevice}' is invalid!  $!");
}

if ($settings{TunerNum} !~ /^(\d)$/)
{
  error(1, "TunerNum = '$settings{TunerNum}' is invalid!");
}

if (!exists $CHANLIST{$settings{FrequencyTable}})
{
  usage(1, "Frequency Table = '$settings{FrequencyTable}' is invalid!");
}

my $freq;

if (exists $opts{F} || exists $opts{frequency} || $settings{Frequency}) # the user may have specified a Frequency in their config file
{
  if ($settings{Frequency} !~ /^(\d+)$/)
  {
    usage(1, "Frequency = '$settings{Frequency}' is invalid!");
  }
  $freq = $settings{Frequency};
}
# now verify that the channel exists in the frequency table!
else
{
  if (!$settings{Channel})
  {
    usage(1, "channel = '$settings{Channel}' is invalid!");
  }
  if (!exists $CHANLIST{$settings{FrequencyTable}}->{$settings{Channel}})
  {
    usage(1, "Channel = '$settings{Channel}' does not exist in Frequency Table '$settings{FrequencyTable}'!");
  }
  $freq = $CHANLIST{$settings{FrequencyTable}}->{$settings{Channel}};
}

my $driverf = ($freq * 16)/1000;
print "" . ((exists $opts{F} || exists $opts{frequency} || $settings{Frequency}) ? "Frequency" : "Ch.$settings{Channel}") . ": $freq $driverf\n";

open(my $tuner, "<$settings{VideoDevice}")
  or die "unable to open: $!";
my $tunerFD = fileno($tuner);

# get the current capabilities.
my @capabilities = $ivtvObj->getCapabilities($tunerFD);
if (@capabilities != keys %{$ivtvObj->{capIndexes}})
{
  error(1, "getCapabilities() failed!");
}
if ($capabilities[$ivtvObj->{capIndexes}{driver}] ne "ivtv")
{
  $settings{UsingIvtvDriver} = 0;  # we can't use the ivtv "enhancements".
  print "Warning:  V4l2 driver = '$capabilities[$ivtvObj->{capIndexes}{driver}]' does not support the ivtv \"enhancements\"!\n";
  print "          All codec related options will be ignored.\n\n";
}

if ($settings{UsingIvtvDriver})
{
  if ($settings{SetMSPMatrix} !~ /^(0|1)$/)
  {
    error(1, "SetMSPMatrix = '$settings{SetMSPMatrix}' is invalid!  It can only be 0 or 1.");
  }
  if ($settings{MSPInput} < 1 || $settings{MSPInput} > 8)
  {
    error(1, "MSPInput = '$settings{MSPInput}' is invalid!  It can only be 1 - 8.");
  }
  if ($settings{MSPOutput} < 0 || $settings{MSPOutput} > 3)
  {
    error(1, "MSPOutput = '$settings{MSPOutput}' is invalid!  It can only be 0 - 3.");
  }
}

if (!$ivtvObj->setFrequency($tunerFD, $settings{TunerNum}, $driverf))
{
  die "Error:  setFrequency($driverf) failed!\n";
}

# see if we have to re-program the msp matrix
if ($settings{UsingIvtvDriver})
{
  if ($settings{SetMSPMatrix})
  {
    print "Setting msp matrix: input = $settings{MSPInput}, output = $settings{MSPOutput}\n" if $settings{Debug};
    sleep ($settings{MSPSleep});  # sleep for 2 seconds to let card settle down.
    my $result = $ivtvObj->mspMatrixSet($tunerFD, $settings{MSPInput}, $settings{MSPOutput});
    if (not defined $result)
    {
      die "Error calling mspMatrixSet!\n";
    }
    if (!$result)
    {
      die "Error in mspMatrixSet ioctl call!\n";
    }
  }
}

close($tuner);

# usage(returnValue, ErrorString)
# returnValue = 1 or 0
# ErrorString = message you want to tell the user, but only if returnValue = 1.
sub usage
{
  my $errorCode = shift;
  my $error = shift;

  print $usageStr;
  print "\nError:  $error\n" if ($errorCode == 1);
  print "$error\n" if ($errorCode == 2);

  exit $errorCode;
}

# error(returnValue, ErrorString)
# returnValue = 1 or 0
# ErrorString = message you want to tell the user.
sub error
{
  my $errorCode = shift;
  my $error = shift;

  print "$versionStr";
  print "\nError:  $error\n";

  exit $errorCode;
}

# VIDIOCSFREQ = _IOW('v',15,ulong)
#.......o .......o .......o .......o
#01000000 00000100 01110110 00000111
#40 04 76 0f

__END__

# use Video::Capture::V4l;

# my $tuner = new Video::Capture::V4l
#  or die "unable to open device: $!";

# set channel
# set tuner std

# $tuner->freq($freq);
