#include "ace/Monitor_Control/Constraint_Visitor.h"

#if defined (ACE_HAS_MONITOR_FRAMEWORK) && (ACE_HAS_MONITOR_FRAMEWORK == 1)

#include "ace/ETCL/ETCL_y.h"
#include "ace/ETCL/ETCL_Constraint.h"

#include "ace/OS_NS_string.h"

ACE_BEGIN_VERSIONED_NAMESPACE_DECL

namespace ACE
{
  namespace Monitor_Control
  {
    Constraint_Visitor::Constraint_Visitor (
      const Monitor_Control_Types::Data& data)
      : data_ (data)
    {}

    Constraint_Visitor::~Constraint_Visitor (void)
    {}

    ACE_CDR::Boolean
    Constraint_Visitor::evaluate_constraint (ETCL_Constraint *root)
    {
      ACE_CDR::Boolean result = false;
      this->queue_.reset ();

      // Evaluate the constraint in root_;
      if (root != 0)
      {
        if (root->accept (this) == 0 && !this->queue_.is_empty ())
        {
          ETCL_Literal_Constraint top;
          this->queue_.dequeue_head (top);
          result = (ACE_CDR::Boolean) top;
        }
      }

      // If a property couldn't be evaluated we must return FALSE.
      return result;
    }

    int
    Constraint_Visitor::visit_literal (ETCL_Literal_Constraint *constraint)
    {
      this->queue_.enqueue_head (*constraint);
      return 0;
    }

    int
    Constraint_Visitor::visit_identifier (ETCL_Identifier *ident)
    {
      /// TODO - check for strings related to other identifiers
      /// relevant in MonitorControl, for example the data timestamp.
      if (ACE_OS::strcmp (ident->value (), "value") == 0)
        {
          this->queue_.enqueue_head (
            ETCL_Literal_Constraint (this->data_.value_));

          return 0;
        }

      return -1;
    }

    int
    Constraint_Visitor::visit_unary_expr (ETCL_Unary_Expr *unary_expr)
    {
      ETCL_Constraint *subexpression = unary_expr->subexpr ();

      /// Call to accept() puts the subexpression's evaluation on our queue.
      if (subexpression->accept (this) == 0)
        {
          ETCL_Literal_Constraint subexpr_result;
          ACE_CDR::Boolean result = 0;
          int op_type = unary_expr->type ();

          switch (op_type)
          {
            case ETCL_NOT:
              this->queue_.dequeue_head (subexpr_result);
              result = ! (ACE_CDR::Boolean) subexpr_result;
              this->queue_.enqueue_head (ETCL_Literal_Constraint (result));
              return 0;
            case ETCL_MINUS:
              /// The leading '-' was parsed separately, so we have to pull
              /// the literal constraint off the queue, apply the class' own
              /// unary minus operator, and put it back.
              this->queue_.dequeue_head (subexpr_result);
              this->queue_.enqueue_head (-subexpr_result);
              return 0;
            case ETCL_PLUS:
              /// Leave the literal constraint on the queue. The leading
              /// '+' was just syntactic sugar - no action is necessary.
              return 0;
            default:
              /// The parser should never construct a ETCL_Unary_Constraint
              /// behind any operators except the above three.
              return -1;
          }
        }

      return -1;
    }

    int
    Constraint_Visitor::visit_binary_expr (ETCL_Binary_Expr *binary_expr)
    {
      int bin_op_type = binary_expr->type ();

      switch (bin_op_type)
        {
          case ETCL_OR:
            return this->visit_or (binary_expr);
          case ETCL_AND:
            return this->visit_and (binary_expr);
          case ETCL_LT:
          case ETCL_LE:
          case ETCL_GT:
          case ETCL_GE:
          case ETCL_EQ:
          case ETCL_NE:
          case ETCL_PLUS:
          case ETCL_MINUS:
          case ETCL_MULT:
          case ETCL_DIV:
            return this->visit_binary_op (binary_expr, bin_op_type);
          /// These last two are not supported in non_CORBA ETCL.
          case ETCL_TWIDDLE:
          case ETCL_IN:
          default:
            return -1;
        }
    }

    int
    Constraint_Visitor::visit_or (ETCL_Binary_Expr *binary)
    {
      int return_value = -1;
      ACE_CDR::Boolean result = false;
      ETCL_Constraint *lhs = binary->lhs ();

      /// Call to accept() puts the lhs (or its evaluation) on our queue.
      if (lhs->accept (this) == 0)
        {
          ETCL_Literal_Constraint lhs_result;
          this->queue_.dequeue_head (lhs_result);
          result = (ACE_CDR::Boolean) lhs_result;

          /// Short-circuiting OR.
          if (!result)
            {
              ETCL_Constraint *rhs = binary->rhs ();

              if (rhs->accept (this) == 0)
              {
                ETCL_Literal_Constraint rhs_result;
                this->queue_.dequeue_head (rhs_result);
                result = (ACE_CDR::Boolean) rhs_result;
                return_value = 0;
              }
            }
          else
            {
              return_value = 0;
            }
        }

      if (return_value == 0)
        {
          this->queue_.enqueue_head (ETCL_Literal_Constraint (result));
        }

      return return_value;
    }

    int
    Constraint_Visitor::visit_and (ETCL_Binary_Expr *binary)
    {
      int return_value = -1;
      ACE_CDR::Boolean result = false;
      ETCL_Constraint *lhs = binary->lhs ();

      /// Call to accept() puts the lhs (or its evaluation) on our queue.
      if (lhs->accept (this) == 0)
        {
          ETCL_Literal_Constraint lhs_result;
          this->queue_.dequeue_head (lhs_result);
          result = (ACE_CDR::Boolean) lhs_result;

          /// Short-circuiting AND.
          if (result == true)
            {
              ETCL_Constraint *rhs = binary->rhs ();

              if (rhs->accept (this) == 0)
              {
                ETCL_Literal_Constraint rhs_result;
                this->queue_.dequeue_head (rhs_result);
                result = (ACE_CDR::Boolean) rhs_result;
                return_value = 0;
              }
            }
          else
            {
              return_value = 0;
            }
        }

      if (return_value == 0)
        {
          this->queue_.enqueue_head (ETCL_Literal_Constraint (result));
        }

      return return_value;
    }

    int
    Constraint_Visitor::visit_binary_op (ETCL_Binary_Expr *binary,
                                         int op_type)
    {
      int return_value = -1;
      ETCL_Constraint *lhs = binary->lhs ();
      ACE_CDR::Boolean result = false;

      /// Perform an operation on the results of evaluating the left and
      /// right branches of this subtree. The evaluations go on our queue.
      if (lhs->accept (this) == 0)
        {
          ETCL_Literal_Constraint left_operand;
          this->queue_.dequeue_head (left_operand);
          ETCL_Constraint *rhs = binary->rhs ();

          if (rhs->accept (this) == 0)
            {
              ETCL_Literal_Constraint right_operand;
              this->queue_.dequeue_head (right_operand);
              return_value = 0;

              switch (op_type)
                {
                  case ETCL_LT:
                    result = left_operand < right_operand;
                    this->queue_.enqueue_head (
                      ETCL_Literal_Constraint (result));
                    break;
                  case ETCL_LE:
                    result = left_operand <= right_operand;
                    this->queue_.enqueue_head (
                      ETCL_Literal_Constraint (result));
                    break;
                  case ETCL_GT:
                    result = left_operand > right_operand;
                    this->queue_.enqueue_head (
                      ETCL_Literal_Constraint (result));
                    break;
                  case ETCL_GE:
                    result = left_operand >= right_operand;
                    this->queue_.enqueue_head (
                      ETCL_Literal_Constraint (result));
                    break;
                  case ETCL_EQ:
                    result = left_operand == right_operand;
                    this->queue_.enqueue_head (
                      ETCL_Literal_Constraint (result));
                    break;
                  case ETCL_NE:
                    result = left_operand != right_operand;
                    this->queue_.enqueue_head (
                      ETCL_Literal_Constraint (result));
                    break;
                  case ETCL_PLUS:
                    this->queue_.enqueue_head (
                      left_operand + right_operand);
                    break;
                  case ETCL_MINUS:
                    this->queue_.enqueue_head (
                      left_operand - right_operand);
                    break;
                  case ETCL_MULT:
                    this->queue_.enqueue_head (
                      left_operand * right_operand);
                    break;
                  case ETCL_DIV:
                    this->queue_.enqueue_head (
                      left_operand / right_operand);
                    break;
                  default:
                    return_value = -1;
                    break;
                }
            }
        }

      return return_value;
    }
  }
}

ACE_END_VERSIONED_NAMESPACE_DECL

#endif /* ACE_HAS_MONITOR_FRAMEWORK==1 */
