/**
 * Prefix opclass allows to efficiently index a prefix table with
 * GiST.
 *
 * More common use case is telephony prefix searching for cost or
 * routing.
 *
 * Many thanks to AndrewSN, who provided great amount of help in the
 * writting of this opclass, on the PostgreSQL internals, GiST inner
 * working and prefix search analyses.
 *
 * $Id: prefix.c,v 1.44 2009/02/07 13:54:01 dim Exp $
 */

#include <stdio.h>
#include "postgres.h"

#include "utils/elog.h"
#include "utils/palloc.h"
#include "utils/builtins.h"
#include "libpq/pqformat.h"
#include <string.h>
#include <locale.h>
#include <math.h>

#define  DEBUG

/**
 * This code has only been tested with PostgreSQL 8.3.
 */
#ifdef PG_VERSION_NUM
#define PG_MAJOR_VERSION (PG_VERSION_NUM / 100)
#else
#error "Unknown postgresql version"
#endif

#if PG_MAJOR_VERSION != 803
#error "Unsupported postgresql version"
#endif

PG_MODULE_MAGIC;

#define BTREE_FR_OPS_COLLATION "fr_FR.UTF8"

/*
 * BTree support functions
 */
Datum btree_fr_eq(PG_FUNCTION_ARGS);
Datum btree_fr_neq(PG_FUNCTION_ARGS);
Datum btree_fr_lt(PG_FUNCTION_ARGS);
Datum btree_fr_le(PG_FUNCTION_ARGS);
Datum btree_fr_gt(PG_FUNCTION_ARGS);
Datum btree_fr_ge(PG_FUNCTION_ARGS);
Datum btree_fr_cmp(PG_FUNCTION_ARGS);

/*
 * The cmp function we base all our interface on.
 *
 * Given how PostgreSQL is using setlocale(), we have to set it each time we
 * need to call our strcoll() implementation, then reset it. We can trust
 * the backend environment so will just call setlocale(LC_COLLATE, "") to
 * reset.
 *
 */
static
int btree_fr_varstr_cmp(char *p, int a, char *q, int b) {
  int cmp;

  /*
   * Set the collation in use for out strcoll call
   */
  setlocale(LC_COLLATE, BTREE_FR_OPS_COLLATION);

#ifdef DEBUG
  elog(NOTICE, 
       "btree_fr_varstr_cmp: LC_COLLATE = %s", setlocale(LC_COLLATE, NULL));
#endif

  /*
   * We want to make sure the locale is RESET, hence the PG_TRY block
   */
  PG_TRY(); {
    cmp = varstr_cmp(p, a, q, b);
  }
  PG_CATCH(); {
    /*
     * Reset the collation rules to whatever the backend env is set to.
     */
    setlocale(LC_COLLATE, "");
    PG_RE_THROW();
    FlushErrorState();
  } 
  PG_END_TRY();   
  
  /*
   * Reset the collation rules to whatever the backend env is set to.
   */
  setlocale(LC_COLLATE, "");
  return cmp;
}


PG_FUNCTION_INFO_V1(btree_fr_eq);
Datum
btree_fr_eq(PG_FUNCTION_ARGS)
{
  text *tp = PG_GETARG_TEXT_PP(0);
  text *tq = PG_GETARG_TEXT_PP(1);
  int a    = VARSIZE_ANY_EXHDR(tp);
  int b    = VARSIZE_ANY_EXHDR(tq);
  bool result;

  if (VARSIZE_ANY_EXHDR(tp) != VARSIZE_ANY_EXHDR(tq))
    result = false;

  else {

    char *p  = 
      DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tp)));
    char *q  = 
      DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tq)));

    result = (0 == btree_fr_varstr_cmp(p, a, q, b));

    PG_FREE_IF_COPY(tp, 0);
    PG_FREE_IF_COPY(tq, 1);
  }

  PG_RETURN_BOOL( result );
}

PG_FUNCTION_INFO_V1(btree_fr_neq);
Datum
btree_fr_neq(PG_FUNCTION_ARGS)
{
  text *tp = PG_GETARG_TEXT_PP(0);
  text *tq = PG_GETARG_TEXT_PP(1);
  int a    = VARSIZE_ANY_EXHDR(tp);
  int b    = VARSIZE_ANY_EXHDR(tq);
  bool result;

  if (VARSIZE_ANY_EXHDR(tp) != VARSIZE_ANY_EXHDR(tq))
    result = true;

  else {

    char *p  = 
      DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tp)));
    char *q  = 
      DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tq)));

    result = (0 != btree_fr_varstr_cmp(p, a, q, b));

    PG_FREE_IF_COPY(tp, 0);
    PG_FREE_IF_COPY(tq, 1);
  }

  PG_RETURN_BOOL( result );
}

PG_FUNCTION_INFO_V1(btree_fr_cmp);
Datum
btree_fr_cmp(PG_FUNCTION_ARGS)
{
  text *tp = PG_GETARG_TEXT_PP(0);
  text *tq = PG_GETARG_TEXT_PP(1);
  int a    = VARSIZE_ANY_EXHDR(tp);
  int b    = VARSIZE_ANY_EXHDR(tq);

  char *p  = 
    DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tp)));
  char *q  = 
    DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tq)));

  int result = btree_fr_varstr_cmp(p, a, q, b);

  PG_FREE_IF_COPY(tp, 0);
  PG_FREE_IF_COPY(tq, 1);
  PG_RETURN_INT32( result );
}

PG_FUNCTION_INFO_V1(btree_fr_le);
Datum
btree_fr_le(PG_FUNCTION_ARGS)
{
  text *tp = PG_GETARG_TEXT_PP(0);
  text *tq = PG_GETARG_TEXT_PP(1);
  int a    = VARSIZE_ANY_EXHDR(tp);
  int b    = VARSIZE_ANY_EXHDR(tq);

  char *p  = 
    DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tp)));
  char *q  = 
    DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tq)));

  int result = (0 <= btree_fr_varstr_cmp(p, a, q, b));

  PG_FREE_IF_COPY(tp, 0);
  PG_FREE_IF_COPY(tq, 1);
  PG_RETURN_INT32( result );
}

PG_FUNCTION_INFO_V1(btree_fr_lt);
Datum
btree_fr_lt(PG_FUNCTION_ARGS)
{
  text *tp = PG_GETARG_TEXT_PP(0);
  text *tq = PG_GETARG_TEXT_PP(1);
  int a    = VARSIZE_ANY_EXHDR(tp);
  int b    = VARSIZE_ANY_EXHDR(tq);

  char *p  = 
    DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tp)));
  char *q  = 
    DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tq)));

  int result = (0 < btree_fr_varstr_cmp(p, a, q, b));

  PG_FREE_IF_COPY(tp, 0);
  PG_FREE_IF_COPY(tq, 1);
  PG_RETURN_INT32( result );
}

PG_FUNCTION_INFO_V1(btree_fr_ge);
Datum
btree_fr_ge(PG_FUNCTION_ARGS)
{
  text *tp = PG_GETARG_TEXT_PP(0);
  text *tq = PG_GETARG_TEXT_PP(1);
  int a    = VARSIZE_ANY_EXHDR(tp);
  int b    = VARSIZE_ANY_EXHDR(tq);

  char *p  = 
    DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tp)));
  char *q  = 
    DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tq)));

  int result = (0 >= btree_fr_varstr_cmp(p, a, q, b));

  PG_FREE_IF_COPY(tp, 0);
  PG_FREE_IF_COPY(tq, 1);
  PG_RETURN_INT32( result );
}

PG_FUNCTION_INFO_V1(btree_fr_gt);
Datum
btree_fr_gt(PG_FUNCTION_ARGS)
{
  text *tp = PG_GETARG_TEXT_PP(0);
  text *tq = PG_GETARG_TEXT_PP(1);
  int a    = VARSIZE_ANY_EXHDR(tp);
  int b    = VARSIZE_ANY_EXHDR(tq);

  char *p  = 
    DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tp)));
  char *q  = 
    DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(tq)));

  int result = (0 > btree_fr_varstr_cmp(p, a, q, b));

  PG_FREE_IF_COPY(tp, 0);
  PG_FREE_IF_COPY(tq, 1);
  PG_RETURN_INT32( result );
}

