#!/usr/bin/perl
# ----------------------------------------------------------------------------
# This software is Copyright (C) 2007, All Rights Reserved
# ----------------------------------------------------------------------------
# This program is protected by local and international copyright laws. Any
# use of this program is subject to the the terms of the license agreement
# included as part of this distribution archive. Any other uses are strictly
# prohibited without the written permission of the Vendor and all
# other rights are reserved.
# -----------------------------------------------------------------------------
# Warning : Modifying this program violates your license agreement!
# -----------------------------------------------------------------------------

package IT::DhtmlFieldLists;

use strict;

use IT::Globals;

# ------------------------------------------------------------------------
# Function    : _getQueryStringFromHash
# Description : convert a hash to an URL friendly query string
# Usage       : &_getQueryStringFromHash(\%in);
# ------------------------------------------------------------------------

sub _getQueryStringFromHash {
  my($hashref) = @_;
  if (ref $hashref ne "HASH") { &IT::Error::dieAsCaller("first arg must be hashref!"); }

  my @pairs;
  foreach (sort keys %$hashref) {
    my $thispair = &IT::Common::urlEncode($_) . '=' . &IT::Common::urlEncode($hashref->{$_});
    push(@pairs,  $thispair);
  }

  my $querystring = join '&', @pairs;

  return $querystring;
}

# ----------------------------------------------------------------------------
# Function    : _decodeUrlEncodedQueryPair
# Description :
# Usage       : my($name, $value) = &_decodeUrlEncodedQueryPair($pair);
# ----------------------------------------------------------------------------

sub _decodeUrlEncodedQueryPair {
  my $pair = shift;
  my ($name, $value) = split(/=/, $pair);

  $name  =~ tr/+/ /;
  $value =~ tr/+/ /;

  $name  =~ s/%([A-F0-9]{2})/pack('C', hex($1))/egi;  # url decode
  $value =~ s/%([A-F0-9]{2})/pack('C', hex($1))/egi;

  $name  =~ s/\r\n/\n/g;
  $value =~ s/\r\n/\n/g;

  $name  =~ s/\x00//g;                                # remove null bytes
  $value =~ s/\x00//g;

  return ($name, $value);
}

# ----------------------------------------------------------------------------
# Function    : getListStructFromFormData
# Description : my $listStruct = &IT::DhtmlFieldLists::getListStructFromFormData($listname);
# ----------------------------------------------------------------------------

sub getListStructFromFormData {
  my $listname = shift || die "no listname field name specified!";

  # error checking
  if (!$listname)                                            { die "no listname specified!"; }
  if (not exists $FORM->{$listname})                         { die "no form input received for field '$listname'!"; }
  if (not exists $FORM->{"$listname:background_classnames"}) { die "no '$listname:background_classnames' field specified!"; }
  if (not exists $FORM->{"$listname:maximum_rows_allowed"})  { die "no '$listname:maximum_rows_allowed' field specified!"; }
  if (not exists $FORM->{"$listname:highest_record_number"}) { die "no '$listname:highest_record_number' field specified!"; }

  my $encoded_row_array = $FORM_ARRAYS->{$listname};

  # define list structure
  my %listStruct = (
    config => {},
    rows   => {},
  );

  # load listStruct rows
  my $row_querystring;
  foreach $row_querystring (@$encoded_row_array) {

    # decode row
    my %thisrow;
    my @pairs = split(/&/, $row_querystring);
    my $pair;
    foreach $pair (@pairs) {
      my($name, $value) = &_decodeUrlEncodedQueryPair($pair);
      if (exists $listStruct{$name}) { die "listStruct contains multiple fields with same name: '$name'."; }
      $thisrow{$name} = $value;
    }

    # error checkng
    foreach (qw(_num _order _erased)) {
      if (not exists $thisrow{$_}) { die "Error, list row does not contain required field '$_'"; };
    }

    $listStruct{'rows'}{ $thisrow{'_num'} } = \%thisrow;    # add row to listStruct
  }

  # load listStruct config values
  $listStruct{'config'}{'background_classnames'} = $FORM->{"$listname:background_classnames"};
  $listStruct{'config'}{'maximum_rows_allowed'}  = $FORM->{"$listname:maximum_rows_allowed"};
  $listStruct{'config'}{'highest_record_number'} = $FORM->{"$listname:highest_record_number"};

  # return data
  return \%listStruct;
}


# ------------------------------------------------------------------------------
# Function    : convertListStructToString
# Description : Convert list rows and highestRecordNumber to a string for storing in database
# Usage       : my $listString = &IT::DhtmlFieldLists::convertListStructToString($listStruct);
# Format      : $highestRecordNumber\t$recordAsQueryString\t$recordAsQueryString\t ...
# ------------------------------------------------------------------------------

sub convertListStructToString {
  my $listStruct   = shift || die "no list hash specified!";

  my $listRows   = $listStruct->{'rows'}   || die "list hash doesn't contain rows key!";
  my $listConfig = $listStruct->{'config'} || die "list hash doesn't contain config key!";

  # add highestRecordNumber
  my $highestRecordNumber = int($listConfig->{'highest_record_number'}) || 1;
  my $listString = "$highestRecordNumber";

  # add list rows
  foreach my $rowNum (sort { $a <=> $b } keys %$listRows) {
    my $encodedRow = &_getQueryStringFromHash( $listRows->{$rowNum} );
    $listString    .= "\t$encodedRow";
  }

  return $listString;
}

# ------------------------------------------------------------------------------
# Function    : convertListStringToStruct
# Description : Covert list string back to hash (when retreiving it from a database)
# Usage       : my $listStruct = &IT::DhtmlFieldLists::convertListStringToStruct($listString);
# Format      : See convertListStructToString()
# ------------------------------------------------------------------------------

sub convertListStringToStruct {
  my $listString   = shift || die "no list string specified!";

  my @listElements = split /\t/, $listString;
  my $listStruct     = {};

  # add highestRecordNumber
  my $highestRecordNumber = int(shift @listElements);
  $listStruct->{'config'}{'highest_record_number'} = $highestRecordNumber;

  # add list rows
  foreach my $encodedRow (@listElements) {
    my $thisRow = {};
    my @pairs = split /&/, $encodedRow;
    foreach my $pair (@pairs) {
      my($name, $value) = &_decodeUrlEncodedQueryPair($pair);
      $thisRow->{$name} = $value;
    }

    my $rowNum = $thisRow->{'_num'} || die "no row number!";
    $listStruct->{'rows'}{$rowNum} = $thisRow;
  }

  return $listStruct;
}

# ----------------------------------------------------------------------------
# Function    : createHiddenRowFields
# Description :
# Note        : We don't want this function to output hidden config fields
#               because the user may want to hardcode some of those values.
# Usage       : my $hiddenFieldsHtml = &IT::DhtmlFieldLists::createHiddenRowFields($listName, $listStruct);
# ----------------------------------------------------------------------------

sub createHiddenRowFields {
  my $listname = shift || &IT::Error::dieAsCaller("no listStruct field name specified!");
  my $listStruct = shift;

  # return nothing if no listStruct
  if (!$listStruct || !keys %$listStruct) { return ''; }

  # error checking
  if (!$listname)                                        { &IT::Error::dieAsCaller("no listname specified!"); }
  if (ref $listStruct ne 'HASH')                         { &IT::Error::dieAsCaller("listStruct hashref must be second argument!"); }
  if (ref $listStruct->{'rows'} ne 'HASH')               { &IT::Error::dieAsCaller("listStruct rows not defined!"); }

  # create row fields
  my($rownum);
  my $rowHTML = '';
  foreach $rownum (keys %{$listStruct->{'rows'}}) {
    my $rowdata = &_getQueryStringFromHash( $listStruct->{'rows'}{$rownum} );
    $rowHTML .= qq|<input type="hidden" name="$listname" value="$rowdata" />\n|;
  }

  return $rowHTML;
}

# ------------------------------------------------------------------------
# Function    : getHighestRecordNumber
# Description :
# Usage       : my $valueHash = &IT::DhtmlFieldLists::getHighestRecordNumber($listStruct);
# ------------------------------------------------------------------------

sub getHighestRecordNumber {
  my $listStruct = shift || die "no listStruct specified!";

  # error checking
  unless (exists $listStruct->{'config'}{'highest_record_number'}) { die "listStruct doesn't have a highest_record_number value!"; }

  my $highestRecordNumber = $listStruct->{'config'}{'highest_record_number'};

  return $highestRecordNumber;
}

# ------------------------------------------------------------------------
# Function    : getHashFromFormData
# Description : return hash of two field values from form data
# Usage       : my $valueHash = &IT::DhtmlFieldLists::getHashFromFormData($listname, $keyField, $valueField);
# ------------------------------------------------------------------------

sub getHashFromFormData {
  my $listname   = shift || die "no listname specified!";
  my $keyField   = shift || die "no keyField specified!";
  my $valueField = shift || die "no valueField specified!";

  my $listStruct  = &IT::DhtmlFieldLists::getListStructFromFormData($listname);
  my $hash      = &IT::DhtmlFieldLists::getHashFromListStruct($listStruct, $keyField, $valueField);

  return $hash;
}

# ------------------------------------------------------------------------
# Function    : getHashFromListStruct
# Description : return hash of two field values from listStruct
# Usage       : my $valueHash = &IT::DhtmlFieldLists::getHashFromListStruct($listStruct, $keyField, $valueField);
# ------------------------------------------------------------------------

sub getHashFromListStruct {
  my $listStruct   = shift || die "no listStruct specified!";
  my $keyField   = shift || die "no keyField specified!";
  my $valueField = shift || die "no valueField specified!";

  my $hash = {};
  foreach my $rowNum (sort { $a <=> $b } keys %{$listStruct->{'rows'}}) {
    my $name  = $listStruct->{'rows'}{$rowNum}{$keyField};
    my $value = $listStruct->{'rows'}{$rowNum}{$valueField};
    if ($name eq '' && $value eq '') { next; }  # skip blank rows
    $hash->{$name} = $value;
  }

  return $hash;
}



# ------------------------------------------------------------------------
# Function    : createListStructFromHash
# Description : create listStruct from a name/value hash
# Usage       : my $listStruct = &IT::DhtmlFieldLists::createListStructFromHash($hash, $keyFieldname, $valueFieldname);
# ------------------------------------------------------------------------

sub createListStructFromHash {
  my $hash           = shift || {};
  my $keyFieldname   = shift || die "no key fieldname specified!";
  my $valueFieldname = shift || die "no value fieldname specified!";

  my $rowCounter = 0;
  my $listStruct = {};
  $listStruct->{'rows'} = {};

  foreach my $name (sort { lc($a) cmp lc($b) } keys %$hash) {
    $rowCounter++;
    $listStruct->{'rows'}{$rowCounter} = {  # this is the data structure required by DHTML_Lists
      '_num'                   => $rowCounter,
      '_order'                 => $rowCounter,
      '_erased'                => '0',
      $keyFieldname            => $name,
      $valueFieldname          => $hash->{$name},
    };
  }

  $listStruct->{'config'}{'highest_record_number'} = $rowCounter;

  return $listStruct;
}

# ----------------------------------------------------------------------------
# end of file
1;

