################################################################################
#
#  kPerl Database Concepts
#  Library for Table Data Management
#
#  copyright (c)
#    Institute of Molecular Biotechnology Jena, Dept. Genome Analysis, 1998-2001
#    Karol Szafranski and Inst. Physiol. Chem., Univ. Düsseldorf, 1998
#  author
#    Karol Szafranski, karol.szafranski@leibniz-fli.de
#
################################################################################
#
#  DESCRIPTION
#
# - individual description of functions at the beginning of the code blocks
#
################################################################################
#
#  FUNCTIONS, DATA
#
#   @EXPORT
#   %_LibParam
#
# - column labels
#   $_LibParam{ColLabel}
#   &ColumnLabels
#
# - conversion of table representations
#   definition of table data structure formats
#   standard behaviour of table converters
#   %TableConverter
#   &TableConvert
#   &TableColOrder
#
# - indices
#   hash index concept
#   array index concept
#   $_LibParam{index}
#   %CreatArrayIndex
#   &ArrayIndex
#
# - sorting
#   &Plot2dSort
#   &PlotCooSort
#
# - selection
#   [to follow]
#
################################################################################
#
#  DEBUG, CHANGES, ADDITIONS
#
# - look also for notes in the header of each function block
#
################################################################################

package database::Table;

# includes
#use strict;  # OK 2003xxxx use warnings;
use Math::kCalc;
use MainLib::Data qw(&DataPrint);

# symbol export
use Exporter qw(import);
our @EXPORT = qw (
  &ColumnLabels 
  %TableConverter &TableConvert
  &ArrayIndex
  &Plot2dSort &Plot2dToCoo &PlotCooSort &PlotCooTo2D
  );

# package-wide constants and variables
my %_LibParam;


################################################################################
# column labels
################################################################################


# default column labels
#
$_LibParam{ColLabel} = [ A..Z ];


# derive effective column labels from library defaults and specification
#
# INTERFACE
# - argument 1: reference to array of user-defined column labels
#               
# - options:
#   -debug      [STD]
#   -default    use referenced array of column labels instead of library
#               defaults
#
# - return val: reference to effective column labels
#
sub ColumnLabels {
  my ($pLabelDef, %opt) = @_;
  my ($debug, @col);
  my ($CtI);

  # default labels
  @col = $opt{-default} ?
    @{$opt{-default}} : @{$_LibParam{ColLabel}};

  # enter label definitions
  if ($pLabelDef) {
    for ($CtI=0; $CtI<@$pLabelDef; $CtI++) {
      $col[$CtI] = $$pLabelDef[$CtI];
    }
  }
  
  # return from SUB  
  return \@col;
};


################################################################################
# conversion of table representations
################################################################################


# definition of table data structure formats
#
# following abbreviations of the data representation concepts are used to
# name the functions provided in this library.
#
# A1     line-array of single-field lines (simply an array)
#        if there're additional fields according to the specified delimiting
#        concept they're ignored.
# A1y    like representation type 'A1' it's an array of single-field lines.
#        Implicitly, the field values are function values corresponding
#        to an array of x values in the range (0 .. $#ValArray), i.e. like
#        a short-cut representation for a special two-column AC table data
#        type. So, for all table converter actions this A1y type will first
#        be converted to an AC type.
# AA     line-array of field-arrays
#        This is the default representation type for most applications
#        though it doesn't hold field labels in the minimal implementation.
# AC     colunm-array of field-arrays
# AH     line-array of field-hashes
#        The first array element is a pointer to an array of the column
#        labels in original sorting order.
# HCA    column-hash of field arrays
# HI2    hash of single-field lines (simply a hash)
#        As a consequence data volume is restricted to or will be reduced
#        to two columns only
# HIA    hash index on field-arrays:
#        each first column values (or column specified via -column)
#        references an array containing the remaining fields
# HIH    hash index on field-hashes:
#        each first column values (or column specified via -column)
#        references a hash containing the remaining fields
#


# standard behaviour of table converters
#               
# INTERFACE
# - argument 1: reference to source table
#               source data is unchanged
#               
# - options:
#   -debug      [STD]
#   -ColLabel   reference to array of column labels that are to be used in
#               creating hash type target types.
#               For input table data types keeping data lines in hashes
#               this option will direct the order of columns in output
#               tables with array-kept data lines. You may also create
#               new empty columns if a column of a referenced name
#               doesn't exist initially.
#               See also return value behaviour in case of wantarray.
#   -ColIdx     see table converter AA->HIA
#   -LineLabel  reference to array of line labels that are to be used in
#               creating hash type target types. Full analogy to option
#               -ColLabel
#
# - return val: - reference to table of target type
#                 the target data structure is independent from the input
#                 data structure.
#               - wantarray:
#                 - reference to table of target type
#                   (for details see above)
#                 - reference to array of column labels
#                   though the actual column labelling may be out of interest
#                   in case they're not related to the table format in
#                   question this array may be useful cause its size reflects
#                   the maximum field number for the whole table.
#               - undef if an error occurs
#
# DESCRIPTION
# - converters which show differences from the following behaviour will
#   have an additional description in the function header.
#
# $TableConverter{'source type'}{'target type'} = sub { ... }


# convert table data type A1 to type AC
#
# INTERFACE
# - argument 1: reference to table data type A1 (array of Y values)
#               source data is unchanged, but beware! The source data is
#               part of the returned structure.
#
$TableConverter{A1}{AC} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@col);

  # function parameters
  $debug = $opt{-debug};
  $debug and print  STDERR "database::Table::TableConverter\{A1y}{AA}. entered\n";

  # return converted structure
  # just put array of values into super-array of columns
  if (wantarray) {
    @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0];
    return ([$pTabSrc], \@col);
  } else {
    return  [$pTabSrc];
  }
};


# convert table data type A1y (array of Y values) to type AA
#
$TableConverter{A1y}{AA} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@col);
  my (@TabTgt, $CtLine);

  # function parameters
  $debug = $opt{-debug};
  $debug and print  STDERR "database::Table::TableConverter\{A1y}{AA}. entered\n";

  # loop over array entries
  # - enter line counter to first field
  # - enter line only if y value is defined, otherwise it's non-informative
  for ($CtLine=0; $CtLine<@$pTabSrc; $CtLine++) {
    defined $$pTabSrc[$CtLine] and 
      push @TabTgt, [ $CtLine, $$pTabSrc[$CtLine] ];
  }

  # return converted structure
  if (wantarray) {
    @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0..1];
    return (\@TabTgt, \@col);
  } else {
    return  \@TabTgt;
  }
};


# convert table data type A1y (array of Y values) to type AC
#
# INTERFACE
# - argument 1: reference to table data type A1y (array of Y values)
#               source data is unchanged, but beware! The source data is
#               part of the returned structure.
#
$TableConverter{A1y}{AC} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@col);
  my (@ValX);

  # function parameters
  $debug = $opt{-debug};
  $debug and print  STDERR "database::Table::TableConverter\{A1y}{AC}. entered\n";

  # just count the array entries and enter to first column
  # time performance bonus against line loop with push is only 1.01 !!!
  @ValX = (0..$#$pTabSrc);

  # return converted structure
  if (wantarray) {
    @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0..1];
    return ([\@ValX, $pTabSrc], \@col);
  } else {
    return  [\@ValX, $pTabSrc];
  }
};


# convert table data type A1y (array of Y values) to type AH
#
$TableConverter{A1y}{AH} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@col);
  my (@TabTgt, $CtLine);

  # function parameters
  $debug = $opt{-debug};
  @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0..1];
  $debug and printf STDERR "database::Table::TableConverter\{A1y}{AH}. determined column labels: %s\n", join('  ',@col);

  # loop over array entries
  # - enter line counter to first field
  # - enter line only if y value is defined
  for ($CtLine=0; $CtLine<@$pTabSrc; $CtLine++) {
    defined $$pTabSrc[$CtLine] and 
      push @TabTgt, { $col[0]=>$CtLine, $col[1]=>$$pTabSrc[$CtLine] };
  }

  # return converted structure
  if (wantarray) {
    return (\@TabTgt, \@col);
  } else {
    return  \@TabTgt;
  }
};


# convert table data type AA to type A1
#
$TableConverter{AA}{A1} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@col);
  my (@TabTgt, $CtLine);

  # debug
  $debug = $opt{-debug};
  $debug and print  STDERR "database::Table::TableConverter\{AA}{A1}. entered\n";

  # reformat
  $#TabTgt = $#$pTabSrc;
  for ($CtLine=0; $CtLine<@$pTabSrc; $CtLine++) {
    $TabTgt[$CtLine] = $$pTabSrc[$CtLine][0];
  }

  # return converted structure
  if (wantarray) {
    @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0];
    return (\@TabTgt, \@col);
  } else {
    return  \@TabTgt;
  }
};


# convert table data type AA to type AC
#
$TableConverter{AA}{AC} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@col);
  my (@TabTgt, $CtLine, $CtField);

  # function parameters
  $debug = $opt{-debug};
  $debug and print  STDERR "database::Table::TableConverter\{AA}{AC}. entered\n";

  # reformat
  for ($CtLine=0; $CtLine<@$pTabSrc; $CtLine++) {
    for ($CtField=0; $CtField<@{$$pTabSrc[$CtLine]}; $CtField++) {
      $TabTgt[$CtField][$CtLine] = $$pTabSrc[$CtLine][$CtField];
    }
  }

  # return converted structure
  if (wantarray) {
    @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0..$#TabTgt];
    return (\@TabTgt, \@col);
  } else {
    return  \@TabTgt;
  }
};


# convert table data type AA to type AH
#
$TableConverter{AA}{AH} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@col);
  my (@TabTgt, $CtI);

  # function parameters
  $debug = $opt{-debug};
  @col = @{ &ColumnLabels ($opt{-ColLabel}) };
  $debug and printf STDERR "database::Table::TableConverter\{AA}{AH}. determined column labels: %s\n", join('  ',@col);

  # loop over array entries (counted)
  # construct new array
  for ($CtI=0; $CtI<@$pTabSrc; $CtI++) {

    # use minimal subset of labels for particular data line
    @{$TabTgt[$CtI]}{@col[0 .. $#{$$pTabSrc[$CtI]}]} =
      @{$$pTabSrc[$CtI]};
  }

  # return converted structure
  # wantarray makes much sense here (you'll need an ordered list of
  #   the column labels)
  if (wantarray) {
    return (\@TabTgt, \@col);
  } else {
    return  \@TabTgt;
  }
};


# convert table data type AA to type HIA
#
# INTERFACE
# - options additional to the standard behaviour:
#   -ColIdx     which column (specify array number) shall be used for
#               indexation, default: 0
#   -ColKeep    keep column used for indexation also in array of
#               values in data line, default: 0
#
# DESCRIPTION
# - The column fields which hold the index values will be removed from
#   the data lines unless you use switch -ColKeep.
#
$TableConverter{AA}{HIA} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my ($ColIdx, $ColMax, @col);
  my (%TabTgt, $CtLine, $pLine);

  # function parameters
  $debug = $opt{-debug};
  $ColIdx = $opt{-ColIdx} || 0;
  $debug and print  STDERR "database::Table::TableConverter\{AA}{AH}. entered\n";

  # reformat
  for ($CtLine=0; $CtLine<@$pTabSrc; $CtLine++) {
    $pLine = [ @{$$pTabSrc[$CtLine]} ];
    unless ($opt{-ColKeep}) {
      splice @$pLine, $ColIdx, 1;
    }
    $TabTgt{$$pTabSrc[$CtLine][$ColIdx]} = $pLine;
  }

  # return converted structure
  if (wantarray) {
    $ColMax = &Max (map { int @$_ } @$pTabSrc);
    @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0..$ColMax];
    return (\%TabTgt, \@col);
  } else {
    return  \%TabTgt;
  }
};


# convert table data type AA to type HIH
#
# INTERFACE
# - options additional to the standard behaviour:
#   -ColIdx       which column (specify array number) shall be used for
#                 indexation, default: 0
#
$TableConverter{AA}{HIH} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug, $ColIdxNum, $ColMax, @col);
  my (%TabTgt, $CtLine, $pLine);

  # function parameters
  $debug = $opt{-debug};
  $ColMax = &Max (map { int @$_ } @$pTabSrc);
  @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0..$ColMax];
  $ColIdxNum = $opt{-ColIdx} ? 
    ((grep { $col[$_] eq $opt{-ColIdx}} 0 .. $#col)[0] || 0) : 0;
  $debug and printf STDERR "database::Table::TableConverter\{AA}{AH}. determined column labels: %s\n", join('  ',@col);

  # reformat
  for ($CtLine=0; $CtLine<@$pTabSrc; $CtLine++) {
    $pLine = { };
    @{$pLine}{@col} = @{$$pTabSrc[$CtLine]};
    $TabTgt{$$pTabSrc[$CtLine][$ColIdxNum]} = $pLine;
  }

  # return converted structure
  if (wantarray) {
    return (\%TabTgt, \@col);
  } else {
    return  \%TabTgt;
  }
};


# convert table data type AC to type AA
#
$TableConverter{AC}{AA} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@TabTgt, $NumLine, @col);
  my ($CtLine, $CtField);

  # function parameters
  $debug = $opt{-debug};
  $debug and print  STDERR "database::Table::TableConverter\{AC}{AA}. entered\n";

  # get maximum line number of lines in all present columns
  foreach (@$pTabSrc) {
    $NumLine = &Max ($NumLine, int @$_);
  }
  $#TabTgt = $NumLine - 1;

  # reformat
  for ($CtField=0; $CtField<@$pTabSrc; $CtField++) {
    for ($CtLine=0; $CtLine<$NumLine; $CtLine++) {
      $TabTgt[$CtLine][$CtField] = $$pTabSrc[$CtField][$CtLine];
    }
  }

  # return converted structure
  if (wantarray) {
    @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0..$#$pTabSrc];
    return (\@TabTgt, \@col);
  } else {
    return  \@TabTgt;
  }
};


# convert table data type AC to type HCA
#
$TableConverter{AC}{HCA} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@col);
  my (%TabTgt, $CtColumn);

  # function parameters
  $debug = $opt{-debug};
  @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0 .. $#$pTabSrc];
  $debug and printf STDERR "database::Table::TableConverter\{AC}{HCA}. determined column labels: %s\n", join('  ',@col);

  # reformat
  foreach $CtColumn (0 .. $#$pTabSrc) {
    @{$TabTgt{$col[$CtColumn]}} = @{$$pTabSrc[$CtColumn]};
  }

  # return converted structure
  if (wantarray) {
    return (\%TabTgt, \@col);
  } else {
    return  \%TabTgt;
  }
};


# convert table data type AH to type AA
#
$TableConverter{AH}{AA} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@col, %ColIdx, @ColNew);
  my (@TabTgt, $CtLine);

  # function parameters
  $debug = $opt{-debug};
  @col = @{$opt{-ColLabel}};
  %ColIdx = map { ($_=>1) } @col;
  $debug and printf STDERR "database::Table::TableConverter\{AH}{AA}. determined primary column labels: %s\n", join('  ',@col);

  # reformat
  $#TabTgt = $#$pTabSrc;
  for ($CtLine=0; $CtLine<@$pTabSrc; $CtLine++) {

    # grep for new columns in current line
    if (@ColNew = grep { not $ColIdx{$_} } keys %{$$pTabSrc[$CtLine]}) {
      push @col, @ColNew;
      @ColIdx{@ColNew} = (1) x (int @ColNew);
      $debug and printf STDERR "database::Table::TableConverter\{AH}{AA}. found new column labels: %s\n", join ('  ', @ColNew);
    }

    # enter fields of current line
    # use column order from @col
    @{$TabTgt[$CtLine]} = @{$$pTabSrc[$CtLine]}{@col};
  }

  # return converted structure
  if (wantarray) {
    return (\@TabTgt, \@col);
  } else {
    return  \@TabTgt;
  }
};


# convert table data type AH to type HCA
#
$TableConverter{AH}{HCA} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (%ColIdx, @col);
  my (%TabTgt, $CtKey, $CtLine);

  # function parameters
  $debug = $opt{-debug};
  map {
    @ColIdx{keys %$_} = ((1) x (int keys %$_));
  } @$pTabSrc;
  @col = sort keys %ColIdx;
  $debug and printf STDERR "database::Table::TableConverter\{AH}{HCA}. found column labels: %s\n", join('  ',@col);

  # dimension arrays
  foreach $CtKey (@col) {
    $#{$TabTgt{$CtKey}} = $#$pTabSrc;
  }

  # reformat
  for ($CtLine=0; $CtLine<@$pTabSrc; $CtLine++) {
    foreach $CtKey (@col) {
      $TabTgt{$CtKey}[$CtLine] = $$pTabSrc[$CtLine]{$CtKey};
    }
  }

  # return converted structure
  if (wantarray) {
    return (\%TabTgt, \@col);
  } else {
    return  \%TabTgt;
  }
};


# convert table data type HCA to type AA
#
# INTERFACE
# - argument 1: reference to table data type HCA
#               source data is unchanged, but beware! The source data is
#               part of the returned structure.
# - return val: reference to table data type AA
#
$TableConverter{HCA}{AA} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (%ColIdx, @col);
  my ($NumField, @TabTgt);
  my ($CtField, $CtLine);

  # function parameters
  $debug = $opt{-debug};
  $debug and printf STDERR "database::Table::TableConverter\{HCA}{AA}. entered\n";

  # used column labels in source table
  %ColIdx = map { ($_=>1) } keys %$pTabSrc;
  $debug and printf STDERR "database::Table::TableConverter\{HCA}{AA}. determined primary column labels: %s\n", join('  ',keys(%$pTabSrc));

  # user-defined column label order
  foreach my $ItLbl (@{$opt{-ColLabel}}) { delete $ColIdx{$ItLbl}; }
  @col = ( @{$opt{-ColLabel}||[]}, sort(keys(%ColIdx)) );
  $debug and printf STDERR "database::Table::TableConverter\{HCA}{AA}. found column labels: %s\n", join('  ',@col);

  # get maximum line number in all columns
  foreach (values %$pTabSrc) {
    $NumLine = &Max ($NumLine, int(@$_));
  }
  $#TabTgt = $NumLine - 1;

  # reformat
  for ($CtField=0; $CtField<@col; $CtField++) {
    for ($CtLine=0; $CtLine<$NumLine; $CtLine++) {
      $TabTgt[$CtLine][$CtField] = $$pTabSrc{$col[$CtField]}[$CtLine];
    }
  }

  # return converted structure
  if (wantarray) {
    return (\@TabTgt, \@col);
  } else {
    return  \@TabTgt;
  }
};


# convert table data type HIA to type AA
#
# INTERFACE
# - argument 1: reference to table data type HIA
#               source data is unchanged, but beware! The source data is
#               part of the returned structure.
#
$TableConverter{HIA}{AA} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (@TabTgt, $NumField, @col);

  # function parameters
  $debug = $opt{-debug};
  $debug and printf STDERR "database::Table::TableConverter\{HIA}{AA}. entered\n";

  # order according to supplied line labels
  # ...

  # reformat
  @TabTgt = values %$pTabSrc;

  # return converted structure
  if (wantarray) {
    foreach (@TabTgt) {
      $NumField = &Max ($NumField, int @$_);
    }
    @col = (@{ &ColumnLabels ($opt{-ColLabel}) })[0..$NumField];
    return (\@TabTgt, \@col);
  } else {
    return  \@TabTgt;
  }
};


# convert table data type HIH to type AH
#
# INTERFACE
# - argument 1: reference to table data type HIH
# - return val: reference to table data type AH
#
$TableConverter{HIH}{AH} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug);
  my (%ColIdx, $ColExtra, @col);
  my ($ItLabel, $pLine);
  my (@TabTgt);

  # function parameters
  $debug = $opt{-debug};
  $debug and print  STDERR "database::Table::TableConverter\{HIH}{AH}\n";

  # used column labels in source table
  foreach $pLine (values %$pTabSrc) {
    foreach $ItLabel (keys %$pLine) {
      $ColIdx{$ItLabel} = 1;
    }
  }
  $debug and printf STDERR "database::Table::TableConverter\{HIH}{AH}. column labels in input:\n  %s\n", join ('  ', sort keys %ColIdx);

  # extra column label for index field
  # - one of labels in @{$opt{-ColLabel}} that's not in $pTabSrc
  # - one of default labels: id index IdxXX
  $ColExtra = ( grep { ! $ColIdx{$_} }
    @{$opt{-ColLabel}||[]}, qw(id index IdxXX)
    )[0];
  $ColIdx{$ColExtra} = 1;
  $debug and printf STDERR "database::Table::TableConverter\{HIH}{AH}. extra column label: %s\n", $ColExtra;

  # column label order
  map { delete $ColIdx{$_}; } @{$opt{-ColLabel}};
  @col = ( @{$opt{-ColLabel}||[]}, sort keys %ColIdx );
  $debug and printf STDERR "database::Table::TableConverter\{HIH}{AH}. column labels, combined:\n  %s\n", join('  ',@col);

  # order lines according to supplied line labels
  foreach $ItLabel (@{$opt{-LineLabel}||[]}) {
    $pLine = { %{$$pTabSrc{$ItLabel}} };
    delete $$pTabSrc{$ItLabel};
    $$pLine{$ColExtra} = $ItLabel;
    push @TabTgt, $pLine;
  }

  # add remaining lines unordered
  foreach $ItLabel (keys %$pTabSrc) {
    $pLine = { %{$$pTabSrc{$ItLabel}} };
    $$pLine{$ColExtra} = $ItLabel;
    push @TabTgt, $pLine;
  }

  # return converted structure
  if (wantarray) {
    return (\@TabTgt, \@col);
  } else {
    return  \@TabTgt;
  }
};


# interface to table conversion
#
# INTERFACE
# - argument 1: table source type
# - argument 2: table target type
# - argument 3: reference to source table data
#
# - options:    see standard behaviour of table converters
#
# - return val: see standard behaviour of table converters
#               - in case the target type equals the source type the array
#                 of column labels will be undefined.
#
sub TableConvert {
  my ($TypeSrc, $TypeTgt, $pTabSrc, %opt) = @_;
  my ($ActionR1, $ActionR2, $pTabTgt);
  my ($pActionNow, $pCol);
  
  # function parameters
  if (ref($TypeSrc) or ref($TypeTgt) or ! ref($pTabSrc)) {
    printf STDERR "%s. ERROR: bad argument type\n", (caller(0))[3], $TypeSrc||"''", $TypeTgt||"''";
    exit 1;
  }
  
  # conversion needed?
  if ($TypeSrc ne $TypeTgt) {
  
    # single-step conversion possible?
    if ($ActionR1 = $TableConverter{$TypeSrc}{$TypeTgt}) { }
  
    # look for first available two-step conversion
    else {
      foreach (keys %{$TableConverter{$TypeSrc}}) {
        if ($ActionR2 = $TableConverter{$_}{$TypeTgt}) {
          $ActionR1 = $TableConverter{$TypeSrc}{$_};
          last;
        }
      }
      unless ($ActionR1 and $ActionR2) {
        printf STDERR "%s. ERROR: no table converter available for %s -> %s type conversion\n", (caller(0))[3], $TypeSrc||"''", $TypeTgt||"''";
        exit 1;
      }
    }
  }
  
  # do conversion
  $pTabTgt = $pTabSrc;
  $debug and printf STDERR "%s. table data structure prior to conversion:\n", (caller(0))[3],
  $debug and &DataPrint ($pTabTgt, -handle=>\*STDERR);
  foreach $pActionNow ($ActionR1, $ActionR2) {
    unless (ref($pActionNow) eq 'CODE') { next }
    ($pTabTgt,$pCol) = &$pActionNow ($pTabTgt, %opt);
    $opt{-ColLabel} ||= $pCol;
    $debug and printf STDERR "%s. table data structure after conversion:\n", (caller(0))[3],
    $debug and &DataPrint ($pTabTgt, -handle=>\*STDERR);
  }

  # return converted structure
  if (wantarray) {
    return ($pTabTgt, $pCol);
  } else {
    return  $pTabTgt;
  }
}


################################################################################
# indices
################################################################################


# hash index concept
#
# DESCRIPTION
# - just see the descriptions for the table data format type HIA.
#   There's no further handling needed/implemented.
#


# array index concept
#
# DESCRIPTION
# - an index has the structure of an array of hashes with following
#   fields:
#   val  index key value. For sorted indices the index will be ordered
#        according to this value.
#   ref  reference to data line 
#
# - an index creation function is one of the functions available in
#   %CreatArrayIndex and has following standard behaviour
#   - argument 1: reference to table data
#   - options:
#     -debug      [STD]
#     -sort       sort index entries. opt value may be either true
#                 scalar value (=> use standard sort routine 
#                 $_LibParam{index}{sort}) or reference to sorting
#                 code. Sorting code will find the index values in
#                 ${$_[0]}{val} and ${$_[1]}{val}, respectively.
#     -UseColumn  number (array type data lines) or name (hash type data
#                 lines) of the column to be indexed, default: column 0.
#                 There's no default for hash type data lines)
#   - return val: reference to index data structure
#
# - note that the referenced table data won't be changed or copied. If
#   the data is changed after creating the index, the index is probably
#   outdated.
#
# DEBUG, CHANGES, ADDITIONS
# - use a code reference along with new option -sort instead of -SortCrit
#   and -SortDesc and may be even more switches!
#   


# standard sort routine
$_LibParam{index}{sort} = sub { ${$_[0]}{val} cmp ${$_[1]}{val}; };


# create array index for table data type HIA
#
$CreatArrayIndex{HIA} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug, $col, $SortAction);
  my (@index);

  # function parameters
  $col = $opt{-UseColumn} || 0;
  if ($opt{-sort}) {
    $SortAction = (ref($opt{-sort}) eq 'CODE') ? 
      $opt{-sort} :
      $_LibParam{index}{sort};
  }

  # create index
  foreach (values %$pTabSrc) {
    push @index, { val=>$$_[$col], ref=>$_ };
  }

  # change index sorted?
  if ($opt{-sort}) {
    @index = sort { &$SortAction($a,$b) } @index;
  }

  # return result
  return  \@index;
};


# create array index for table data type AA
#
$CreatArrayIndex{AA} = sub {
  my ($pTabSrc, %opt) = @_;
  my ($debug, $col, $SortAction);
  my (@index);

  # function parameters
  $col = $opt{-UseColumn} || 0;
  if ($opt{-sort}) {
    $SortAction = (ref($opt{-sort}) eq 'CODE') ? 
      $opt{-sort} :
      $_LibParam{index}{sort};
  }

  # create index
  foreach (@$pTabSrc) {
    push @index, { val=>$$_[$col], ref=>$_ };
  }

  # change index sorted?
  if ($opt{-sort}) {
    @index = sort { &$SortAction($a,$b) } @index;
  }

  # return result
  return  \@index;
};


# create index on data lines of a table data structure
#
# INTERFACE
# - argument 1: table data type
# - argument 2: reference to table data
#
# - options: beside the options of the index creation function
#   -debug      [STD]
#
# - return val: - reference to index data structure
#               - undef if an error occurred
#
# DESCRIPTION
# - referenced data (table) remains unchanged
#
sub ArrayIndex {
  my ($TableType, $pTable, %opt) = @_;
  
  # use index creator if defined
  if ($pAction = $CreatArrayIndex{$TableType}) {
    return &$pAction ($pTable, %opt);
  } else { return undef }
}


################################################################################
# sorting
################################################################################


# sort data plot type 'AC' by X values ascending
#
# DESCRIPTION
# - argument 1: reference to X data array
# - argument 2: reference to Y data array
# - return val: - array of:
#                 - reference to X value array (sorted)
#                 - reference to Y data array (sorted)
#
# DESCRIPTION
# - referenced data remains unchanged
#
sub Plot2dSort {
  my ($px, $py, %opt) = @_;
  my ($pTable);
  
  # convert to table type AH
  $pTable = &TableConvert ('AC', 'AH', [$px,$py], -ColLabel=>['x','y']);
  
  # sort data in temporary Coo2D representation
  $pTable = &PlotCooSort ($pTable);
    
  # convert back to table type AC
  $pTable = &TableConvert ('AH', 'AC', $pTable, -ColLabel=>['x','y']);

  # exit SUB
  return (@$pTable);
}


# sort data plot type AH (coordinate array) by x and y value ascending
#
# INTERFACE
# - argument 1: reference to coordinate array
# - return val: reference to sorted coordinate array
#
# DESCRIPTION
# - data argument remains unchanged.
#
sub PlotCooSort {
  my ($pCoo, %opt) = @_;
  my (@CooSort);

  # create sorted copy of the coordinate array
  @CooSort = sort {
    ($a->{x} <=> $b->{x}) or ($a->{y} <=> $b->{y});
    } @$pCoo;

  # return
  return \@CooSort;
}


################################################################################
# selection
################################################################################


# ...


1;
# $Id: Table.pm,v 1.9 2018/06/05 18:02:56 szafrans Exp $
