#ifdef RCS
static char rcsid[]="$Id: function.c,v 1.1.1.1 2000/11/13 02:42:42 holsta Exp $";
#endif
/******************************************************************************
 *                    Internetting Cooperating Programmers
 * ----------------------------------------------------------------------------
 *
 *  ____    PROJECT
 * |  _ \  __ _ _ __   ___ ___ _ __ 
 * | | | |/ _` | '_ \ / __/ _ \ '__|
 * | |_| | (_| | | | | (_|  __/ |   
 * |____/ \__,_|_| |_|\___\___|_|   the IRC bot
 *
 * All files in this archive are subject to the GNU General Public License.
 *
 * $Source: /cvsroot/dancer/dancer/src/function.c,v $
 * $Revision: 1.1.1.1 $
 * $Date: 2000/11/13 02:42:42 $
 * $Author: holsta $
 * $State: Exp $
 * $Locker:  $
 *
 * ---------------------------------------------------------------------------
 *****************************************************************************/

#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "regex.h"
#include "function.h"
#include "transfer.h"
#include "user.h"

#define RNDMAX ULONG_MAX
/* RNDSIZE must be an integral number of 2 (and at least 8) */
#define RNDSIZE 64
#define RNDMASK RNDSIZE-1


/* --- Global ----------------------------------------------------- */

extern time_t now;

extern char nickname[];
extern char nickstring[];

/* tolowertab, used by HashU(), Match(), MatchEsc() and IRCEqual functions */

#define IRC_tolower(c) (tolowertab[(u_char)(c)])

const unsigned char tolowertab[] =
{
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
  0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
  0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
  ' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')',
  '*', '+', ',', '-', '.', '/',
  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  ':', ';', '<', '=', '>', '?',
  '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
  'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
  't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
  '_',
  '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
  'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
  't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
  0x7f,
  0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
  0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
  0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
  0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
  0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,
  0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
  0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
  0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
  0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
  0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
  0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
  0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
  0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
  0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
  0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
  0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};


static ulong rnd1;
static ulong rnd2;
static ulong rndValues[RNDSIZE];

/* --- IsChannel -------------------------------------------------- */

inline bool IsChannel(char *name)
{
  return (name && (('#' == name[0]) || ('&' == name[0]) ||
                   ('+' == name[0]) || ('!' == name[0])));
}

/* --- IsServer --------------------------------------------------- */

inline bool IsServer(char *name)
{
  return (name && StrIndex(name, '.'));
}

/* --- TimeAgo ---------------------------------------------------- */

char *TimeAgo(time_t when)
{
  static char timebuf[MIDBUFFER];
  int a, b, diff;

  snapshot;
  diff = now - when;
  diff = abs(diff);

  if (diff < SECINMIN) {
    StrFormatMax(timebuf, sizeof(timebuf), "%d %s",
          diff, ((1 == diff) ? GetText(msg_second) : GetText(msg_seconds)));
  }
  else if (diff < SECINHOUR) {
    a = diff/SECINMIN;
    b = diff%SECINMIN;

    StrFormatMax(timebuf, sizeof(timebuf), "%d %s %s %d %s",
          a, ((1 == a) ? GetText(msg_minute) : GetText(msg_minutes)),
          GetText(msg_and),
          b, ((1 == b) ? GetText(msg_second): GetText(msg_seconds)));
  }
  else if (diff < SECINDAY) {
    a = diff/SECINHOUR;
    b = (diff%SECINHOUR)/SECINMIN;

    StrFormatMax(timebuf, sizeof(timebuf), "%d %s %s %d %s",
          a, ((1 == a) ? GetText(msg_hour) : GetText(msg_hours)),
          GetText(msg_and),
          b, ((1 == b) ? GetText(msg_minute) : GetText(msg_minutes)));
  }
  else if (diff < SECINMONTH) {
    a = diff/SECINDAY;
    b = (diff%SECINDAY)/SECINHOUR;

    StrFormatMax(timebuf, sizeof(timebuf), "%d %s %s %d %s",
          a, ((1 == a) ? GetText(msg_day) : GetText(msg_days)),
          GetText(msg_and),
          b, ((1 == b) ? GetText(msg_hour) : GetText(msg_hours)));
  }
  else {
    a = diff/SECINMONTH;
    b = (diff%SECINMONTH)/SECINDAY;

    StrFormatMax(timebuf, sizeof(timebuf), "%d %s %s %d %s",
          a, ((1 == a) ? GetText(msg_month) : GetText(msg_months)),
          GetText(msg_and),
          b, ((1 == b) ? GetText(msg_day) : GetText(msg_days)));
  }

  return timebuf;
}

/* --- SecToString ------------------------------------------------ */

char *SecsToString(int secs)
{
  static char buffer[25];
  int hours;
  int minutes;

  snapshot;
  hours = secs/3600;
  secs -= hours*3600;
  minutes = secs/60;
  secs -= minutes*60;

  StrFormatMax(buffer, sizeof(buffer), "%02d.%02d:%02d", hours, minutes, secs);

  return buffer;
}

/* --- ToSeconds -------------------------------------------------- */

int ToSeconds(char *string)
{
  char *next;
  int secs = 0;

  do {
    secs *= 60;
    secs += StrToLong(string, &next, 10);
    string = next + 1;
  } while (':' == *next);

  return secs;
}

/* --- GetOption -------------------------------------------------- */

itemopt option = {NULL, (char)0};

bool GetOption(char *line)
{
  if (line && ('-' == *line)) {
    line++;
    option.copt = *line++;
    while (*line && !iswhite(*line))
      line++;
    while (iswhite(*line))
      line++;
    option.newpos = line;
    return TRUE;
  }
  option.copt = (char)0;
  option.newpos = line;
  return FALSE;
}

/* --- Boolean ---------------------------------------------------- */

inline char *OnOff(bool toggle)
{
  return (toggle ? "on" : "off");
}

inline bool IsOn(char *string)
{
  return (string && StrEqual(string, "ON"));
}

/* --- Hash ------------------------------------------------------- */

inline unsigned int HashU(char *key)
{
  unsigned int k;

  for (k = 0; *key; key++)
    k = 31*k + IRC_tolower(*key);
  return (k % HASHSIZE);
}

inline unsigned int Hash(char *key)
{
  unsigned int k;

  for (k = 0; *key; key++)
    k = 31*k + *key;
  return (k % HASHSIZE);
}

/* --- HashSignatureU --------------------------------------------- */

/* Used for fast text searching. To check if a certain  word could
 * occur in a static block of text, a hashed 2-signature of the
 * text must be pre-calculated. When searching for a word, calculate
 * it's signature and compare with the signature of the text.
 *
 *   if ( (textsignature & signature) == signature )
 *     // word _could_ be in text, check further
 *   else
 *     // word is definitely not in text
 *
 * To find possible match between two string where one contains
 * wildcard the check must be
 *
 *   if ( (patternsignature & signature) == patternsignature )
 */

ulong HashSignatureU(char *name)
{
  char a, b;
  bool bvalid = FALSE; /* Does b contain a valid char */
  ulong sig = 0L;

  while (a = IRC_tolower(*name++)) {
    /* Don't hash wildcards and bigrams */
    if (('*' != a) && ('?' != a)) {
      if (bvalid && (a != b))
        sig |= 1 << (((int)a + (int)b) % ULONG_BIT);
      b = a;
      bvalid = TRUE;
    }
    else
      bvalid = FALSE;
  }
  return sig;
}

/* --- OurNextWord ------------------------------------------------ */

char *OurNextWord(char *line, char **rest)
{
  char *pointer;

  if (line) {
    while (iswhite(*line))
      line++;

    for (pointer = line; *pointer && !iswhite(*pointer); pointer++);

    if (*pointer)
      *pointer++ = (char)0;

    if (rest)
      *rest = *pointer ? pointer : NULL;
  }
  else if (rest) {
    *rest = NULL;
  }

  return ((line && *line) ? line : NULL);
}

/* --- OurNextQuoteWord ------------------------------------------- */

char *OurNextQuoteWord(char *line, char **rest)
{
  char *pointer;

  if (line) {
    while (iswhite(*line))
      line++;

    if ('\"' == *line) {
      for (pointer = ++line; *pointer && ('\"' != *pointer); pointer++);
    }
    else {
      for (pointer = line; *pointer && !iswhite(*pointer); pointer++);
    }

    if (*pointer)
      *pointer++ = (char)0;

    if (rest)
      *rest = *pointer ? pointer : NULL;
  }
  else if (rest) {
    *rest = NULL;
  }

  return ((line && *line) ? line : NULL);
}

#if 0
/* --- PatternInit() ---------------------------------------------- */

/* Setup the translation table fo the pattern mach (case insensitive) */

/* How many characters in the character set.  */
#define CHAR_SET_SIZE 256

static char p_translate[CHAR_SET_SIZE];

void PatternInit(void)
{
  unsigned i;
  /* Map uppercase characters to corresponding lowercase ones.  */
  for (i=0; i < CHAR_SET_SIZE; i++)
    p_translate[i] = isupper(i) ? tolower(i) : i;
}
#endif

/* --- PatternExist ----------------------------------------------- */

bool PatternExist(char *line, char *pattern)
{
  char const *error;
  int retcode;
  struct re_pattern_buffer patbuf;

  memset(&patbuf, 0, sizeof(struct re_pattern_buffer));
#if 0
  patbuf.translate = p_translate; /* case insensitive table */
#endif
  error = re_compile_pattern(pattern, StrLength(pattern), &patbuf);
  if (error)
    Debug("Pattern error: %s", error);

  retcode = re_search(&patbuf, line, StrLength(line), 0, StrLength(line), NULL);
  regfree(&patbuf);
  return ((retcode >= 0) ? TRUE : FALSE); /* return hit */
}

#if 0
inline bool SearchPattern(re_compile_pattern *p, char *line)
{
  return (re_search(p, line, StrLength(line), 0, StrLength(line), NULL) >= 0);
}

void *FreePattern(struct re_pattern_buffer *p)
{
  regfree(p);
  free(p);
}

struct re_pattern_buffer *PrecalcPattern(char *pattern)
{
  char const *errtxt;
  struct re_pattern_buffer *p;

  if ( p=(struct re_pattern_buffer *)calloc(1, sizeof(struct re_pattern_buffer)) ) {
    if ( errtxt=re_compile_pattern(pattern, StrLength(pattern), p) ) {
      Debug("Pattern error: %s", errtxt);
      FreePattern(p);
      p = NULL;
    }
  }
  return (p);
}
#endif

/* --- Match ------------------------------------------------------ */

/*  Using a slightly modified version of the iterative match() function
 *  from ircd sources (irc2.10.1/common/match.c), which is twice or
 *  sometimes even multiple times faster than the recursive Match().
 *  Most of the blame goes to toupper, but the iterative version would
 *  still be faster than the recursive one, regardless of toupper.
 *  Inverted the return values, removed the possibility to escape the
 *  wildcards in the pattern with a '\' and some other minor changes.
 */

#define MAX_ITERATIONS 512

bool Match(char *string, char *pattern)
{
  register u_char *s = (u_char *)string;
  register u_char *p = (u_char *)pattern;
  char *str = string;
  char *pat = pattern;
  bool wild = FALSE;
  int calls = 0;

  if (string && pattern) {
    while (1) {
#ifdef	MAX_ITERATIONS
      if (calls++ > MAX_ITERATIONS)
        break;
#endif

      if ('*' == *p) {
        while ('*' == *p)
          p++;
        wild = TRUE;
        pat = (char *)p;
        str = (char *)s;
      }

      if ((char)0 == *p) {
        if ((char)0 == *s)
          return TRUE;
        if (p > (u_char *)pattern)
          p--;
        while ((p > (u_char *)pattern) && ('?' == *p))
          p--;
        if ('*' == *p)
          return TRUE;
        if (!wild)
          return FALSE;
        p = (u_char *)pat;
        s = (u_char *)++str;
      }
      else if ((char)0 == *s)
        return FALSE;

      if ((IRC_tolower(*p) != IRC_tolower(*s)) && ('?' != *p)) {
        if (!wild)
          return FALSE;
        p = (u_char *)pat;
        s = (u_char *)++str;
      }
      else {
        if (*p)
          p++;
        if (*s)
          s++;
      }
    }
  }
  return FALSE;
}

/* --- MatchEsc --------------------------------------------------- */

/* Allows you to escape the wildcards in pattern using '\' */

bool MatchEsc(char *string, char *pattern)
{
  register u_char *s = (u_char *)string;
  register u_char *p = (u_char *)pattern;
  char *str = string;
  char *pat = pattern;
  bool wild = FALSE, q;
  int calls = 0;

  if (string && pattern) {
    while (1) {
#ifdef	MAX_ITERATIONS
      if (calls++ > MAX_ITERATIONS)
        break;
#endif

      if ('*' == *p) {
        while ('*' == *p)
          p++;
        wild = TRUE;
        pat = (char *)p;
        str = (char *)s;
      }

      if ((char)0 == *p) {
        if ((char)0 == *s)
          return TRUE;
        if (p > (u_char *)pattern)
          p--;
        while ((p > (u_char *)pattern) && ('?' == *p))
          p--;
        if (('*' == *p) && ((p <= (u_char *)pattern) || ('\\' != p[-1])))
          return TRUE;
        if (!wild)
          return FALSE;
        p = (u_char *)pat;
        s = (u_char *)++str;
      }
      else if ((char)0 == *s)
        return FALSE;

      if (('\\' == *p) && (('*' == p[1]) || ('?' == p[1]))) {
        p++;
        q = TRUE;
      }
      else
        q = FALSE;

      if ((IRC_tolower(*p) != IRC_tolower(*s)) && (('?' != *p) || q)) {
        if (!wild)
          return FALSE;
        p = (u_char *)pat;
        s = (u_char *)++str;
      }
      else {
        if (*p)
          p++;
        if (*s)
          s++;
      }
    }
  }
  return FALSE;
}

/* --- MatchMatch ------------------------------------------------- */

bool MatchMatch(char *pattern1, char *pattern2)
{
  return (Match(pattern1, pattern2) || Match(pattern2, pattern1));
}

/* --- IRCEqual --------------------------------------------------- */

bool IRCEqual(const char *first, const char *second)
{
  register const u_char *tt = tolowertab,
                        *s1 = (const u_char *)first,
                        *s2 = (const u_char *)second;

  if (first && second) {
    while (tt[*s1] == tt[*s2++]) {
      if ((char)0 == *s1++)
        return TRUE;
    }
  }
  return FALSE;
}

/* --- Userdomain ------------------------------------------------- */

/*
 *  Transforms host name to a masked domain name.
 *
 *  This function allocates a string, which you must free()
 *  yourself. {a=Userhost(b); ...use it...; free(a);}
 *
 *  Machine names may start with a digit (if they've got a really
 *  stupid sysadm) but top level domains always start with an
 *  alphabetic char. Use top level domain to distinguish between
 *  IP names and IP numeric adresses.
 *
 *  Both '@' and at least one '.' must be present in 'host'.
 *
 *  Furthermore, some people use '@' in their (faked) user name,
 *  so search for the last '@'.
 *
 *  For example:
 *       "user@machine.host.domain" becomes "user@*.host.domain"
 *  and  "user@127.0.0.1"           becomes "user@127.0.0.*"
 *
 *  From 3rd of January, 1997 (v4.1.3) also:
 *
 * Check if the host is an ISP listed as hostisp: in .config and then make the
 * pattern like:
 *
 *       "user@machine.host.domain" becomes "*@machine.host.domain"
 *
 *  From 25th of February, 1999:
 *
 *       "user@machine.top-domain" style hosts are returned unchanged.
 */

char *Userdomain(char *host)
{
  char *userdomain, *m, *t;
  bool smallone;

  snapshot;
  userdomain = StrDuplicate(host);
  if (userdomain) {
    m = StrIndexLast(userdomain, '@');  /* Userhost requires a '@' */
    if (m) {
      t = StrIndexLast(m, '.');
      if (t) {

        /* Now check for a @host.topdomain style name */
        smallone = (StrIndex(m, '.') == t) ? TRUE : FALSE;

        t++; /* Points to top level domain name */

        if (isdigit(*t)) {
          *t++ = '*';
          *t = (char)0;
        }
        else if (HostISP(userdomain)) {
          if (m > userdomain) {
            t = userdomain;

            *t++ = '*';
            while (*m)
              *t++ = *m++;
            *t = (char)0;
          }
        }
        else if (!smallone) {
          m++; /* Points to machine name */
          t = StrIndex(m, '.');
          if (t && (t > m)) {
            *m++ = '*';
            while (*t)
              *m++ = *t++;
            *m = (char)0;
          }
        }
      }
    }
  }

  return userdomain;
}

/* --- NewNick ---------------------------------------------------- */

void NewNick(void)
{
  static int num = 0;
  char *from = nickstring, *to = nickname;
  char c;
  bool bracket = FALSE;
  int sep = 0;

  snapshot;
  while (c = *from++) {
    switch (c) {
      case '[':
        bracket = TRUE;
        sep = 0;
        break;
      case '|':
        sep++;
        break;
      case ']':
        bracket = FALSE;
        break;
      default:
        if ((to - nickname) < NICKLEN) { /* Avoid overflow */
          if (!bracket)
            *to++ = c;
          else if (sep == num)
            *to++ = c;
        }
        break;
    }
  }
  *to = (char)0;

  if (sep <= num++)
    num = 0;
}

/* --- Random ----------------------------------------------------- */

inline ulong Random(void)
{
  rnd1 = (rnd1 - 3) & RNDMASK;
  rnd2 = (rnd2 - 1) & RNDMASK;
  return (rndValues[rnd2] += rndValues[rnd1]);
}

/* --- RandomInit ------------------------------------------------- */

void RandomInit(int s)
{
  int i;

  for (i=0; i < RNDSIZE; i++) {
    rndValues[i] = (ulong)s;
    /* --- randqd1() from Numerical Recipes --- */
    s = 0x0019660dL * s + 0x3c6ef35fL;
  }
  rnd1 = 0;
  rnd2 = RNDSIZE/2 - 3;

  /* --- Remove initial errors of bad seeding (warn-up) --- */
  for (i=0; i < RNDSIZE; i++) {
    Random();
  }
}

/* --- Rnd -------------------------------------------------------- */

float Rnd(void)
{
  float a;

  a = (float)Random();
  a /= (float)RNDMAX;
  return a;
}

/* --- Top -------------------------------------------------------- */

struct Top {
  char name[40];
  long value;
  long flags;
};

#define NUM_TOP 7

struct Top realtop[NUM_TOP];
struct Top *newtop[NUM_TOP];

int compar(struct Top **t1, struct Top **t2)
{
  return ((*t2)->value - (*t1)->value);
}

void AddToTop(char *name, long value) 
{
  snapshot;
  if (value > newtop[NUM_TOP-1]->value) {
    StrCopy(newtop[NUM_TOP-1]->name, name);
    newtop[NUM_TOP-1]->value = value;
    qsort((void *)newtop, NUM_TOP, sizeof(struct Top *), (int (*)(const void *, const void *))compar);
  }
}

void InitTop(void)
{
  int n;

  snapshot;
  memset(realtop, 0, sizeof(realtop)); /* reset all */
  for (n=0; n < NUM_TOP; n++) {
    newtop[n] = &realtop[n];
  }
}

char *PresentTop(void)
{
  static char buffer[256];
  int len = 0;
  int n;

  snapshot;
  for (n=0; (n < NUM_TOP) && newtop[n]->name[0]; n++) {
    StrFormatMax(&buffer[len], sizeof(buffer) - len, "%s (%d) ",
                 newtop[n]->name, (int)newtop[n]->value);
    len += StrLength(&buffer[len]);
  }

  if (0 == n) {
    StrCopyMax(buffer, sizeof(buffer), GetText(msg_no_info_for_toplist));
  }

  return buffer;
}

/* --- fcopy ------------------------------------------------------ */

#if 0
/* fcopy() was only used by seenbackup, we don't need it now? */

/* MAX_COPY_SIZE determines how big of a buffer to use */
#define MAX_COPY_BLOCK 1024

void fcopy(char *src, char *dest)
{
  char buffer[MAX_COPY_BLOCK];
  size_t bytes;
  FILE *fsrc, *fdest;

  fsrc = fopen(src, "rb");
  if (fsrc) {
    fdest = fopen(dest, "wb");
    if (fdest) {
      /* copy in 1K chunks by default */
      while (MAX_COPY_BLOCK == (bytes = fread(buffer, 1, MAX_COPY_BLOCK, fsrc)))
        fwrite(buffer, MAX_COPY_BLOCK, 1, fdest);
      fwrite(buffer, bytes, 1, fdest);
      fclose(fdest);
    }
    fclose(fsrc);
  }
}
#endif

/* --- FileExist -------------------------------------------------- */

bool FileExist(char *filename)
{
  FILE *f;

  if (filename && filename[0]) {
    f = fopen(filename, "r");
    if (f) {
      fclose(f);
      return TRUE;
    }
  }
  return FALSE;
}

/* --- StrSplitMax ------------------------------------------------ */

/*
 * A silly function used by SendMulti() to split the allocated string in
 * shorter strings. Got any better solution?
 */

char *StrSplitMax(char *source, size_t size)
{
  static char *next_string = NULL;
  static char buffer_char;
  char *target = NULL;

  if (source) {
    next_string = source;
    buffer_char = (char)0;
  }

  if (next_string && (size > 1)) {
    if (buffer_char) {
      *next_string = buffer_char;
      buffer_char = (char)0;
    }

    /* Skip any whitespace at the start */
    while (iswhite(*next_string))
      next_string++;

    if (*next_string) {
      target = next_string;

      if ((StrLength(next_string) + 1) > size) {
        char *pointer;

        pointer = next_string + size - 1;

        while ((pointer > next_string) && !iswhite(pointer[0]))
          pointer--;
        while ((pointer > next_string) && iswhite(pointer[-1]))
          pointer--;

        if (pointer == next_string) {
          pointer = next_string + size - 1;
          buffer_char = *pointer;
          *pointer = (char)0;
        }
        else {
          *pointer++ = (char)0;
        }

        next_string = pointer;
      }
      else {
        next_string = NULL;
      }
    }
  }

  return target;
}


syntax highlighted by Code2HTML, v. 0.9.1