/*___INFO__MARK_BEGIN__*/
/*************************************************************************
 *
 *  The Contents of this file are made available subject to the terms of
 *  the Sun Industry Standards Source License Version 1.2
 *
 *  Sun Microsystems Inc., March, 2006
 *
 *
 *  Sun Industry Standards Source License Version 1.2
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.2 (the "License"); You may not use this file
 *  except in compliance with the License. You may obtain a copy of the
 *  License at http://gridengine.sunsource.net/Gridengine_SISSL_license.html
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *   Copyright: 2006 by Sun Microsystems, Inc.
 *
 *   All Rights Reserved.
 *
 ************************************************************************/
/*___INFO__MARK_END__*/

#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <fnmatch.h>
#include <string.h>
#include <ctype.h>

#include "rmon/sgermon.h"

#include "uti/sge_string.h"
#include "uti/sge_log.h"
#include "uti/sge_hostname.h"

#include "sge_answer.h"
#include "sge_eval_expression.h"

#include "msg_common.h"
#include "msg_sgeobjlib.h"

/* Local variables and definitions  */
typedef struct _s_token {
   u_long32 type;       /* resource type, how to be compared */
   const char *value;   /* Pointer to tested value */
   const char *expr;    /* Pointer to tested expression */
   const char *s;       /* Index pointer to expression */
         char *pattern; /* regular expression subpattern */
   bool has_patterns;   /* pattern has patterns switch, to help be more effective */
   int  tt;             /* Type of token */
   int  et;             /* Expected Token type */
   lList **answer_list; /* Anwser list, or null */
} s_token;


/* Private function deffinitions */
static int OrExpression(s_token *, bool);
static int AndExpression(s_token *, bool);
static int SimpleExpression(s_token *, bool);
static void NextToken(s_token *, bool);
static int Error(s_token *, int);
static int ParseTerminal(s_token *, bool);
static void ParseNonTerminal(s_token *, bool);
static int indexOfTerminal(const char );
static bool is_pattern(const char );
static int MatchPattern(s_token *, bool);
static void uncaseValue(s_token *,char *);

/* arrays and enums.  */
enum { T_NOT = 0, T_OR, T_AND, T_BRACEOPEN, T_BRACECLOSE, T_END, T_EXP, T_ERROR };

/* ATTENTION! The order of TERMINALS and enumTypes have to match */
const int eTypes[] = {T_NOT, T_OR, T_AND, T_BRACEOPEN, T_BRACECLOSE, T_END};
char *tTypes[] = { "!<pattern>", "|<pattern>", "&<pattern>", "(", ")", "<end>",
"<pattern>", "<error>" };


/****** sgeobj/sge_eval_expression/sge_eval_expression() **********************
*  NAME
*     sge_eval_expression() -- boolean expression extension 
*
*  SYNOPSIS
*     int sge_eval_expression(u_long32 type, 
*                             const char *expr, 
*                             const char *value)
*
*  FUNCTION
*     boolean expression extension of regular expression evaluation function 
*     fnmatch()
*
*  INPUTS
*     u_long32 type - type of resource
*     const char *expr - expression string to be evaluated
*     const char *value - value string to be compared for
*     lList **answer_list - answer list to pass back error messages
*
*  RESULT
*     int - result if expression is true or
*         0 - strings are the same or both NULL
*         1 - if is false
*        -1 - for empty expression, null, or other error
*
*  SEE ALSO
*     uti/hostname/sge_hostcmp()
*     fnmatch()
*
*  NOTES:
*     MT-NOTE: sge_eval_expression() is MT safe
*****************************************************************************/
int 
sge_eval_expression(u_long32 type, const char *expr, const char *value, lList **answer_list) 
{
   int match;
   char pattern_buf[MAX_STRING_SIZE], value_buf[MAX_STRING_SIZE];
   
   DENTER(BASIS_LAYER, "sge_eval_expression");

   /* Null values are supported in str_cmp_null way */
   if (value==NULL || expr==NULL)
   {
     if (expr == NULL && value == NULL) { 
        DRETURN(0);
     }

     if (value==NULL) {  /* then expr has to be non-NULL */
        DRETURN(1);
     }
     else {
        DRETURN(-1);
     }
   }

   /* To long arguments */
   if (strlen(value) >= MAX_STRING_SIZE) {
      answer_list_add_sprintf(answer_list, STATUS_ESYNTAX, ANSWER_QUALITY_ERROR,
                              MSG_EVAL_EXPRESSION_LONG_VALUE, MAX_STRING_SIZE);
      ERROR((SGE_EVENT, MSG_EVAL_EXPRESSION_LONG_VALUE, MAX_STRING_SIZE));
      DRETURN(-1);
   }
   if (strlen(expr) >= MAX_STRING_SIZE) {
      answer_list_add_sprintf(answer_list, STATUS_ESYNTAX, ANSWER_QUALITY_ERROR,
                              MSG_EVAL_EXPRESSION_LONG_EXPRESSION, MAX_STRING_SIZE);
      ERROR((SGE_EVENT, MSG_EVAL_EXPRESSION_LONG_EXPRESSION, MAX_STRING_SIZE));
      DRETURN(-1);
   }   
  
   { 
	   s_token token;             /* The main token structure */
	   token.value=value;         /* the copybuffer will be on demand */
	   token.expr=expr;           /* copy per partes to pattern_buf */
	   token.pattern=pattern_buf;
	   token.s=token.expr;        /* Set the index. */
	   token.tt=T_END;            /* Type of token */
	   token.et=T_EXP;            /* Type of expected token */
	   token.type=type;
	   token.answer_list=answer_list;
	   token.has_patterns = sge_is_expression(token.expr); /*  pattern is detected latter */

	   if(token.has_patterns){
	      uncaseValue(&token,value_buf);   /* Check the value needs to be lowered */
	      match = OrExpression(&token, false);
	      /*
	       * after the expression has been evaluated
	       * the input stream must be empty
	       * and the token must be T_END
	       */
	      if (token.tt != T_END) {
                match=Error(&token, T_END);
	      } else if (token.s[0] != '\0') { /*Something is missing? */
                match=Error(&token, token.et);
	      }
	   } else {
               token.pattern=(char *) token.expr;
               match = MatchPattern(&token, false);
	   }
   }

   DRETURN(match);
}

/*-----------------------------------------------------------
 * NextTok[0]en
 *    set "s" to next token
 *    set "token" to T_NOT, T_AND, T_OR, T_BRACEOPEN, T_BRACECLOSE, T_EXP
 *    set "s" to NULL and "token" to T_END of string is completely read
 *-----------------------------------------------------------*/
static void NextToken(s_token *token_p, bool skip)
{
   token_p->et=token_p->tt;
   
   while (token_p->s[0] == ' ') token_p->s++; /*skip white chars */
   
   if (token_p->s == NULL ) return;    /* Should not happen */
   if (token_p->tt==T_ERROR) return;   /*Previous error */
   
   if (token_p->s[0] == '\0' ) {       /* At the end of expression */
      token_p->tt = T_END;
      return;
   }
   if (!ParseTerminal(token_p, skip)){
      ParseNonTerminal(token_p, skip);
   }
}


/*-----------------------------------------------------------
 * ParseTerminal
 * return 1 .. isTeminal, 0..isNonTerminal
 *-----------------------------------------------------------*/
static int ParseTerminal(s_token *token_p, bool skip)
{
   int index;
   if ((index=indexOfTerminal(token_p->s[0]))!=-1){
      token_p->tt=eTypes[index]; /*The order is same as enum */
      token_p->s++; /*Move pointer after terminal */
      return 1;
   }
   return 0;
}
/*-----------------------------------------------------------
 * ParseNonTerminal
 * Cat the pattern on next terminal and
 * move the s pointer together and stop on next terminal
 *-----------------------------------------------------------*/
static void ParseNonTerminal(s_token *token_p, bool skip)
{
   char *index;
   token_p->tt = T_EXP;
   if (skip==false) { /* expression is not in skip mode */
      token_p->has_patterns=false; /* the pattern detected */
      index = token_p->pattern; /* skip first test, allready tested */
      while (index==token_p->pattern || indexOfTerminal(token_p->s[0])==-1) {
         if (token_p->has_patterns==false && is_pattern(token_p->s[0])) {
            token_p->has_patterns=true; /* the pattern detected */
         }
         switch (token_p->type){
            case TYPE_CSTR:
            case TYPE_HOST:
               index[0]=tolower(token_p->s[0]);
               break;
            default:
               index[0]=token_p->s[0];
         }
         
         index++;
         token_p->s++;
      }
      index[0]='\0'; /*terminate pattern copy */
   } else {
      while (indexOfTerminal(token_p->s[0])==-1) {
         token_p->s++;
      }
   }
}

/*-----------------------------------------------------------
 * indexOfTerminal
 *    return index of terminal, -1 if it is not terminal
 *-----------------------------------------------------------*/
static int indexOfTerminal(const char c)
{
   switch (c) {
      case '!':  return T_NOT;
      case '|':  return T_OR;
      case '&':  return T_AND;
      case '(':  return T_BRACEOPEN;
      case ')':  return T_BRACECLOSE;
      case ' ':  return T_END;
      case '\0': return T_END;
   }
   return -1;
}

/*-----------------------------------------------------------
 * is_pattern
 *    return bool is there is an pattern sign
 *-----------------------------------------------------------*/
static bool is_pattern(const char c)
{
   switch (c) {
      case '*':
      case '?':
      case '[':
      case ']':
         return true;
   }
   return false;
}
/*-----------------------------------------------------------
 * Error
 *    print simple error message
 *-----------------------------------------------------------*/
static int Error(s_token *token_p, int expected)
{
   DENTER(GUI_LAYER, "sge_eval_expression:Error");
   if (token_p->tt!=T_ERROR){
      answer_list_add_sprintf(token_p->answer_list, STATUS_ESYNTAX,
                              ANSWER_QUALITY_ERROR, MSG_EVAL_EXPRESSION_PARSE_ERROR,
                              token_p->s - token_p->expr , token_p->expr);
      ERROR((SGE_EVENT, MSG_EVAL_EXPRESSION_PARSE_ERROR, (int)(token_p->s - token_p->expr) , token_p->expr));
      token_p->et=expected;
      token_p->tt=T_ERROR;
   }
   DRETURN((-1)); /*negative return value is error index */
}

/*----------------------------------------------------------
 * OrExpression
 * Evaluate an OR expression
 * same input/output parameters as for Expression()
 *----------------------------------------------------------*/
static int OrExpression(s_token *token_p, bool skip)
{
   int match;
   
   NextToken(token_p, skip);
   match = AndExpression(token_p, skip);
   while (token_p->tt == T_OR) {
      NextToken(token_p, skip); /* Negative logic 0=true,  or is and */
      if (match==0) {
         AndExpression(token_p, true); /* skip other for true */
      } else {
         match = AndExpression(token_p, skip);
      }
   }
   return match;
}

/*----------------------------------------------------------
 * AndExpression
 * Evaluate an AND expression
 *----------------------------------------------------------*/
static int AndExpression(s_token *token_p, bool skip)
{
   int match;
   
   match = SimpleExpression(token_p, skip);
   while (token_p->tt == T_AND) {
      NextToken(token_p, skip); /* Negative logic 0=true,  or is and */
      if (match!=0) {
         SimpleExpression(token_p, true); /* skip other for false */
      } else {
         match = SimpleExpression(token_p, skip);
      }
   }
   return match;
}

/*----------------------------------------------------------
 * SimpleExpression
 *    Evalute a simple expression
 *    this can be a "expression"
 *         1) we can match with fnmatch()
 *         2) !expression
 *         3) (exp)
 *----------------------------------------------------------*/
static int SimpleExpression(s_token *token_p, bool skip)
{
   int match;
   if (token_p->tt==T_ERROR) {
      match = -1;
   } else if (token_p->tt == T_BRACEOPEN) {
      match = OrExpression(token_p, skip);
      if (token_p->tt != T_BRACECLOSE) {
         match = Error(token_p, T_BRACECLOSE); /*Indicate what I need? */
         return match;
      }
      NextToken(token_p, skip);
   } else if (token_p->tt == T_EXP)  {
      match=MatchPattern(token_p, skip);
      NextToken(token_p, skip);
   } else if (token_p->tt == T_NOT) {
      NextToken(token_p, skip);
      match = SimpleExpression(token_p, skip);
      match = !match;
   } else {
      match = Error(token_p, token_p->et);
   }
   return match;
}

/*----------------------------------------------------------
 * MatchPattern
 *    Evalute a pattern expression
 *    RETURNS match depend on type of resource
 *----------------------------------------------------------*/
static int MatchPattern(s_token *token_p, bool skip)
{
   int match;
   /*printf("Match pattern %i: '%s'=='%s'\n", skip,token_p->pattern, token_p->value); */
   if (skip==true){
      return -1;
   }
   if (token_p->pattern==NULL){
      return -1;
   }
   if (token_p->has_patterns ){
      switch(token_p->type){
         case TYPE_STR:
         case TYPE_CSTR:
         case TYPE_RESTR: match = fnmatch(token_p->pattern, token_p->value, 0);
         break;
         case TYPE_HOST:  match = sge_hostmatch(token_p->pattern, token_p->value);
         break;
         default: match = -1;
      }
   } else { /* optimized for non pattern stuff */
      switch(token_p->type){
         case TYPE_STR:
         case TYPE_RESTR:
            match = strcmp(token_p->pattern, token_p->value);
            break;
         case TYPE_CSTR:
            match = strcasecmp(token_p->pattern, token_p->value);
            break;
         case TYPE_HOST:
            match = sge_hostcmp(token_p->pattern, token_p->value);
            break;
         default: match = -1;
      }
   }
   return ((match==0) ? 0 : 1);
}

/*----------------------------------------------------------
 * uncaseValue
 *    Change token_p->value to case lowered value stored in buffer
 *----------------------------------------------------------*/
static void uncaseValue(s_token *token_p, char *value_buf)
{
   int i;
   switch (token_p->type){
      case TYPE_CSTR:
      case TYPE_HOST:
         for (i = 0; token_p->value[i] != '\0' && i < MAX_STRING_SIZE; i++) {
            value_buf[i]=tolower(token_p->value[i]);
         }
         value_buf[i]='\0'; /*Terminate string */
         token_p->value=value_buf; /* Set up the buffer */
         break;
   }
}
