#!/usr/bin/perl
#-*- Mode: perl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-

# Utility functions.
#
# Copyright (C) 2000-2001 Ximian, Inc.
#
# Authors: Hans Petter Jansson <hpj@ximian.com>
#          Arturo Espinosa <arturo@ximian.com>
#          Michael Vogt <mvo@debian.org> - Debian 2.[2|3] support.
#          David Lee Ludwig <davidl@wpi.edu> - Debian 2.[2|3] support.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Library General Public License as published
# by the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.


# --- Utilities for strings, arrays and other data structures --- #

package Utils::Util;

sub max
{
  return ($_[0] > $_[1])? $_[0]: $_[1];
}

# Boolean <-> strings conversion.

sub read_boolean
{
  my ($v) = @_;

  return 1 if ($v =~ "true" ||
               $v =~ "yes"  ||
               $v =~ "YES"  ||
               $v =~ "on"   ||
               $v eq "1");
  return 0;
}


sub print_boolean_yesno
{
  if ($_[0] == 1) { return "yes"; }
  return "no";
}


sub print_boolean_truefalse
{
  if ($_[0] == 1) { return "true"; }
  return "false";
}


sub print_boolean_onoff
{
  if ($_[0] == 1) { return "on"; }
  return "off";
}


# Pushes a list to an array, only if it's not already in there.
# I'm sure there's a smarter way to do this. Should only be used for small
# lists, as it's O(N^2). Larger lists with unique members should use a hash.

sub push_unique
{
  my $arr = $_[0];
  my $found;
  my $i;

  # Go through all elements in pushed list.

  for ($i = 1; $_[$i]; $i++)
  {
    # Compare against all elements in destination array.
	
    $found = "";
    for $elem (@$arr)
    {
      if ($elem eq $_[$i]) { $found = $elem; last; }
    }
	
    if ($found eq "") { push (@$arr, $_[$i]); }
  }
}


# Merges scr array into dest array.
sub arr_merge
{
  my ($dest, $src) = @_;
  my (%h, $i);

  foreach $i (@$a, @$b)
  {
    $h{$i} = 1;
  }

  @$a = keys %h;
  return $a;
}

# Given an array and a pattern, it returns the index of the
# array that contains it
sub find_array_index
{
    my($arrayRef, $pattern) = @_;
    my(@array)              = @{$arrayRef};
    my($numElements)        = scalar(@array);
    my(@indexes)            = (0..$numElements);
    my(@elements);
    
    @elements = grep @{$arrayRef}[$_] =~ /$pattern/, @indexes;
    return(wantarray ? @elements : $elements[0]);
}

    
sub ignore_line
{
  if (($_[0] =~ /^[ \t]*\#/) || ($_[0] =~ /^[ \t\n\r]*$/)) { return 1; }
  return 0;
}


# &gst_item_is_in_list
#
# Given:
#   * A scalar value.
#   * An array.
# this function will return 1 if the scalar value is in the array, 0 otherwise.

sub item_is_in_list
{
  my ($value, @arr) = @_;
  my ($item);

  foreach $item (@arr)
  {
    return 1 if $value eq $item;
  }

  return 0;
}


# Recursively compare a structure made of nested arrays and hashes, diving
# into references, if necessary. Circular references will cause a loop.
# Watch it: arrays must have elements in the same order to be equal.
sub struct_eq
{
  my ($a1, $a2) = @_;
  my ($type1, $type2);
  my (@keys1, @keys2);
  my ($elem1, $elem2);
  my $i;

  $type1 = ref $a1;
  $type2 = ref $a2;
  
  return 0 if $type1 != $type2;
  return 1 if $a1 eq $a2;
  return 0 if (!$type1); # Scalars
  
  if ($type1 eq "SCALAR") {
    return 0 if $$a1 ne $$a2;
  }
  elsif ($type1 eq "ARRAY")
  {
    return 0 if $#$a1 != $#$a2;

    for ($i = 0; $i <= $#$a1; $i++)
    {
      return 0 if !&struct_eq ($$a1[$i], $$a2[$i]);
    }
  }
  elsif ($type1 eq "HASH") {
    @keys1 = sort keys (%$a1);
    @keys2 = sort keys (%$a2);

    return 0 if !&struct_eq (\@keys1, \@keys2);
    foreach $i (@keys1)
    {
      return 0 if !&struct_eq ($$a1{$i}, $$a2{$i});
    }
  }
  else
  {
    return 0;
  }
    
  return 1;
}


# &gst_get_key_for_subkeys
#
# Given:
#   * A hash-table with its values containing references to other hash-tables,
#     which are called "sub-hash-tables".
#   * A list of possible keys (stored as strings), called the "match_list".
# this method will look through the "sub-keys" (the keys of each
# sub-hash-table) seeing if one of them matches up with an item in the
# match_list.  If so, the key will be returned.

sub get_key_for_subkeys
{
  my %hash = %{$_[0]};
  my @match_list = @{$_[1]};

  foreach $key (keys (%hash))
  {
    my %subhash = %{$hash{$key}};
    foreach $item (@match_list)
    {
      if ($subhash{$item} ne "") { return $key; }
    }
  }

  return "";
}


# &gst_get_key_for_subkey_and_subvalues
#
# Given:
#   * A hash-table with its values containing references to other hash-tables,
#     which are called "sub-hash-tables".  These sub-hash-tables contain
#     "sub-keys" with associated "sub-values".
#   * A sub-key, called the "match_key".
#   * A list of possible sub-values, called the "match_list".
# this function will look through each sub-hash-table looking for an entry
# whose:
#   * sub-key equals match_key.
#   * sub-key associated sub-value is contained in the match_list.

sub get_key_for_subkey_and_subvalues
{
  my %hash = %{$_[0]};
  my $key;
  my $match_key = $_[1];
  my @match_list = @{$_[2]};

  foreach $key (keys (%hash))
  {
    my %subhash = %{$hash{$key}};
    my $subvalue = $subhash{$match_key};

    if ($subvalue eq "") { next; }

    foreach $item (@match_list)
    {
      if ($item eq $subvalue) { return $key; }
    }
  }

  return "";
}


# --- IP calculation --- #


# ip_calc_network (<IP>, <netmask>)
#
# Calculates the network address and returns it as a string.

sub ip_calc_network
{
  my @ip_reg1;
  my @ip_reg2;

  @ip_reg1 = ($_[0] =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/);
  @ip_reg2 = ($_[1] =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/);

  $ip_reg1[0] = ($ip_reg1[0] * 1) & ($ip_reg2[0] * 1);
  $ip_reg1[1] = ($ip_reg1[1] * 1) & ($ip_reg2[1] * 1);
  $ip_reg1[2] = ($ip_reg1[2] * 1) & ($ip_reg2[2] * 1);
  $ip_reg1[3] = ($ip_reg1[3] * 1) & ($ip_reg2[3] * 1);

  return join ('.', @ip_reg1);
}


# ip_calc_broadcast (<IP>, <netmask>)
#
# Calculates the broadcast address and returns it as a string.

sub ip_calc_broadcast
{
  my @ip_reg1;
  my @ip_reg2;
  
  @ip_reg1 = ($_[0] =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/);
  @ip_reg2 = ($_[1] =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/);
 
  @ip_reg1 = ($cf_hostip =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/);

  $ip_reg1[0] = ($ip_reg1[0] * 1) | (~($ip_reg2[0] * 1) & 255);
  $ip_reg1[1] = ($ip_reg1[1] * 1) | (~($ip_reg2[1] * 1) & 255);
  $ip_reg1[2] = ($ip_reg1[2] * 1) | (~($ip_reg2[2] * 1) & 255);
  $ip_reg1[3] = ($ip_reg1[3] * 1) | (~($ip_reg2[3] * 1) & 255);
  
  return join ('.', @ip_reg1);
}

# Forks a process, running $proc with @args in the child, and
# printing the returned value of $proc in the pipe. Parent
# returns a structure with useful data about the process.
sub process_fork
{
  my ($proc, @args) = @_;
  my $pid;
  local *PARENT_RDR;
  local *CHILD_WTR;
  
  pipe (PARENT_RDR, CHILD_WTR);
  
  $pid = fork ();
  if ($pid)
  {
    # Parent
    close CHILD_WTR;
    return {"pid" => $pid, "fd" => *PARENT_RDR, "fileno" => fileno (*PARENT_RDR)};
  }
  else
  {
    my $ret;
    close PARENT_RDR;
    # Child
    $ret = &$proc (@args);
    my $type = ref ($ret);

    if (!$type)
    {
      print CHILD_WTR $ret;
    }
    elsif ($type eq 'ARRAY')
    {
      print CHILD_WTR "$_\n" foreach (@$ret);
    }

    close CHILD_WTR;
    exit (0);
  }
}


# Close pipe, kill process, wait for it to finish.
sub process_kill
{
  my ($proc) = @_;
  
  &Utils::File::close_file ($$proc{"fd"});
  kill 2, $$proc{"pid"};
  waitpid ($$proc{"pid"}, undef);
}


# Populate a bitmap of the used file descriptors.
sub process_list_build_fd_bitmap
{
  my ($procs) = @_;
  my ($bits, $proc);
  
  foreach $proc (@$procs)
  {
    vec ($bits, $$proc{"fileno"}, 1) = 1;
  }
  
  return $bits;
}


# Receives a seconds timeout (may be float) and a ref to
# a list of processes (each returned by gst_fork_process), and
# set the "ready" key to true in all the procs that are ready
# to return values, false otherwise. Returns time left before
# timeout.
sub process_list_check_ready
{
  my ($timeout, $procs) = @_;
  my ($bits, $bitsleft, $bitsready, $timestamp, $timeleft);

  $procs = [ $procs ] if ref ($procs) ne 'ARRAY';
  $bits = &process_list_build_fd_bitmap ($procs);
  
  # Check with timeout which descriptors are ready with info.
  $timeout = undef if $timeout == 0;
  $timeleft = $timeout;
  $bitsleft = $bits;
  while (($timeout eq undef) || ($timeleft > 0))
  {
    $timestamp = time;
    select ($bitsleft, undef, undef, $timeleft);
    $timeleft -= time - $timestamp if $timeout ne undef;
    
    $bitsready |= $bitsleft;
    $bitsleft = $bits & (~$bitsready);
    last if $bitsready eq $bits;
  }
  $bits = $bitsready;

  # For every process, set "ready" key to 1/0 depending on
  # its file descriptor bit.
  foreach $proc (@$procs)
  {
    $$proc{"ready"} = (ord ($bits) & (1 << $$proc{"fileno"}))? 1 : 0;
  }

  return $timeleft;
}


sub process_result_collect
{
  my ($proc, $func, @args) = @_;
  my ($value, $tmp, $lines);

  if ($$proc{"ready"})
  {
    my @list;

    $lines .= $tmp while (sysread ($$proc{"fd"}, $tmp, 4096));
    goto PROC_KILL unless $lines;
    if ($lines =~ /\n/)
    {
      @list = split ("\n", $lines);
    }
    else
    {
      push @list, $line;
    }

    $value = &$func (\@list, @args);
  }

 PROC_KILL:
  &process_kill ($proc);

  return $value;
}


1;
