/*
	Copyright (c) 2024 PTC Inc. and/or Its Subsidiary Companies. All Rights Reserved.
*/



#include <ProToolkit.h>
#include <ProParameter.h>
#include <ProRelSet.h>

#include <PTUDFExamples.h>
#include <ProTKRunTime.h>

/*====================================================================*\
FUNCTION: PTUDFExParamValidateLimit
PURPOSE:  Check the parameter value if it meets the given limit restriction.
\*====================================================================*/
static ProError PTUDFExParamValidateLimit (ProParamvalue* value, ProParamLimit*
					   limit)
{
  double real_value, real_limit;

/*--------------------------------------------------------------------*\
Shouldn't happen, boolean and string params can't be ranged
\*--------------------------------------------------------------------*/
  if (value->type != PRO_PARAM_INTEGER && value->type != PRO_PARAM_DOUBLE)
    return PRO_TK_NOT_VALID; 

  if (limit->type == PRO_PARAM_UNLIMITED)
    return PRO_TK_NO_ERROR;

/*--------------------------------------------------------------------*\
Real value and real limit are set to double even if the parameter is integer.
\*--------------------------------------------------------------------*/
  real_value = (value->type == PRO_PARAM_INTEGER? (double)value->value.i_val : value->value.d_val);

  real_limit = (limit->value.type == PRO_PARAM_INTEGER ? (double)limit->value.value.i_val : limit->value.value.d_val);

  switch (limit->type)
    {
    case PRO_PARAM_LESS_THAN:
      return (real_value < real_limit ? PRO_TK_NO_ERROR : PRO_TK_NOT_VALID);
    case PRO_PARAM_LESS_THAN_OR_EQUAL:
      return (real_value <= real_limit ? PRO_TK_NO_ERROR : PRO_TK_NOT_VALID);
    case PRO_PARAM_GREATER_THAN:
      return (real_value > real_limit ? PRO_TK_NO_ERROR : PRO_TK_NOT_VALID);
    case PRO_PARAM_GREATER_THAN_OR_EQUAL:
      return (real_value >= real_limit ? PRO_TK_NO_ERROR : PRO_TK_NOT_VALID);
    }

  return (PRO_TK_NO_ERROR);
}

/*====================================================================*\
FUNCTION: PTUDFExParamValidateConstraint
PURPOSE:  Check the parameter value in a single constraint to see if it
          passes.
\*====================================================================*/
static ProError PTUDFExParamValidateConstraint (ProRelset* relset,
						ProName name,
						ProParamvalue* value, 
						wchar_t* constraint)
{
  ProLine line;
  char c_line [PRO_LINE_SIZE * 4], c_constraint [PRO_LINE_SIZE * 4];
  char c_name [PRO_NAME_SIZE * 4];
  char c_param_value [PRO_LINE_SIZE * 4];
  char c_buff [2];
  int param_name_len, constraint_len;
  ProParamvalue result;
  int i;
  ProUnititem units;


/*--------------------------------------------------------------------*\
Convert the new value to a string value expression.
\*--------------------------------------------------------------------*/
  switch (value->type)
    {
    case PRO_PARAM_INTEGER:
      ProTKSprintf (c_param_value, "%d", value->value.i_val);
      break;
    case PRO_PARAM_DOUBLE:
      ProTKSprintf (c_param_value, "%lf", value->value.d_val);
      break; 
    case PRO_PARAM_BOOLEAN:
      ProTKSprintf (c_param_value, "%s", value->value.l_val ? "TRUE" : "FALSE");
      break; 
    case PRO_PARAM_STRING:
      ProWstringToString (c_param_value, value->value.s_val);
      break;
    }

  ProWstringToString (c_name, name);
  param_name_len = strlen (c_name);

  ProWstringToString (c_constraint, constraint);
  constraint_len = strlen (c_constraint);

/*--------------------------------------------------------------------*\
Replace each instance of 'name' appearing in the constraint with the 
value expression.
\*--------------------------------------------------------------------*/
  strcpy (c_line, "");

  for (i = 0; i < constraint_len; i ++)
    {
      if (strncmp (&c_constraint[i], c_name, param_name_len) == 0)
	{
	  strcat (c_line, c_param_value);
	  i = i + param_name_len;
	}
      ProTKSprintf (c_buff, "%c", c_constraint [i]);
      strcat (c_line, c_buff);
    }

  ProStringToWstring (line, c_line);
  
/*--------------------------------------------------------------------*\
Pass the modified expression to Pro/E for evaluation.  It should output
a boolean param value with the result.
\*--------------------------------------------------------------------*/
  status = ProRelationEvalWithUnitsRefResolve(relset, line, &result, &units, PRO_B_FALSE);
  PT_TEST_LOG_SUCC ("ProRelationEvalWithUnitsRefResolve()");
  
  if (result.type != PRO_PARAM_BOOLEAN)
    return (PRO_TK_NOT_VALID);
  
  if (result.value.l_val != PRO_B_TRUE)
    return (PRO_TK_NOT_VALID);

  return (PRO_TK_NO_ERROR);
}


/*====================================================================*\
FUNCTION: PTUDFExParamCompareValue
PURPOSE:  Compare two param values.
\*====================================================================*/
static ProError PTUDFExParamCompareValue (ProParamvalue* value,
					       ProParamvalue* permitted_value)
{
  if (value->type != permitted_value->type)
    return (PRO_TK_NOT_VALID);

  switch (value->type)
    {
    case PRO_PARAM_INTEGER:
      return (value->value.i_val == permitted_value->value.i_val ? 
	      PRO_TK_NO_ERROR : PRO_TK_NOT_VALID);
    case PRO_PARAM_DOUBLE:
      return (value->value.d_val == permitted_value->value.d_val ? 
	      PRO_TK_NO_ERROR : PRO_TK_NOT_VALID);
    case PRO_PARAM_BOOLEAN:
       return (value->value.l_val == permitted_value->value.l_val ? 
	      PRO_TK_NO_ERROR : PRO_TK_NOT_VALID);
    case PRO_PARAM_STRING:
      {
	int result;
	ProWstringCompare (value->value.s_val, permitted_value->value.s_val,
			   PRO_VALUE_UNUSED, &result);
        return (result == 0 ?  PRO_TK_NO_ERROR : PRO_TK_NOT_VALID);
      }
    }

  return (PRO_TK_NOT_VALID);
}


/*====================================================================*\
FUNCTION: PTUDFExParamRangeFailureStringGet
PURPOSE: Create the string showing that the value failed due to
         a violated range.
\*====================================================================*/
static ProError PTUDFExParamRangeFailureStringGet (ProParameter* param,
						   ProParamLimit* minimum,
						   ProParamLimit* maximum,
						   ProLine reason)
{
  ProCharLine c_reason;

  ProTKSprintf (c_reason, "Parameter violated range assigned.");
  ProStringToWstring (reason, c_reason);
  
  return (PRO_TK_NO_ERROR);
}

/*====================================================================*\
FUNCTION: PTUDFExParamConstraintFailureStringGet
PURPOSE: Create the string showing that the value failed due to
         a violated constraint.
\*====================================================================*/
static ProError PTUDFExParamConstraintFailureStringGet (ProParameter* param,
							wchar_t* constraint, 
							ProLine reason)
{
  ProCharLine c_reason;

  ProTKSprintf (c_reason, "Parameter violated constraint assigned.");
  ProStringToWstring (reason, c_reason);
  
  return (PRO_TK_NO_ERROR);
}

/*====================================================================*\
FUNCTION: PTUDFExParamVerify
PURPOSE: Utility for deciding if a given parameter can be set to a
         given value.
\*====================================================================*/
ProError PTUDFExParamVerify (ProParameter* param, ProParamvalue* value, ProLine reason)
{
  ProError ret_status = PRO_TK_NO_ERROR;
  ProBoolean is_enumerated;
  ProParamvalue* values;
  ProBoolean has_range;
  ProParamLimit minimum;
  ProParamLimit maximum;
  ProError min_status, max_status;
  ProModelitem m_item;
  ProRelset relset;
  wchar_t** constraints;
  int i, array_size;
  ProCharLine c_reason;

/*--------------------------------------------------------------------*\
Check enumerated values for the parameter.
\*--------------------------------------------------------------------*/
  status = ProParameterIsEnumerated (param, &is_enumerated, &values);
  PT_TEST_LOG_SUCC ("ProParameterIsEnumerated");

  if (is_enumerated)
    {
      status = ProArraySizeGet (values, &array_size);
      PT_TEST_LOG_SUCC ("ProArraySizeGet");

      for (i = 0; i < array_size; i++)
	{
/*--------------------------------------------------------------------*\
If this function returns PRO_TK_NO_ERROR, we have a match and can stop
checking.
\*--------------------------------------------------------------------*/
	  status = PTUDFExParamCompareValue (value, &values[i]);
	  if (status == PRO_TK_NO_ERROR)
	    break;
	}

      if (status != PRO_TK_NO_ERROR)
	{
	  ProTKSprintf (c_reason, "Parameter value not found in list of available enumerated values.");
	  ProStringToWstring (reason, c_reason);
	  ret_status = PRO_TK_NOT_VALID;
	}

      ProArrayFree ((ProArray*)&values);
    }

  if (ret_status != PRO_TK_NO_ERROR)
    return (ret_status);

/*--------------------------------------------------------------------*\
Check numerical range for the parameter.
\*--------------------------------------------------------------------*/
  status = ProParameterRangeGet (param, &has_range, &minimum, &maximum);
  PT_TEST_LOG ("ProParameterRangeGet", status, status != PRO_TK_NO_ERROR && status != PRO_TK_E_NOT_FOUND);

  if (has_range && (minimum.type != PRO_PARAM_UNLIMITED && maximum.type != PRO_PARAM_UNLIMITED))
    {
/*--------------------------------------------------------------------*\
If either function returns PRO_TK_NOT_VALID the value is out of range.
\*--------------------------------------------------------------------*/
      min_status = PTUDFExParamValidateLimit (value, &minimum);
      max_status = PTUDFExParamValidateLimit (value, &maximum);

      if (min_status == PRO_TK_NOT_VALID || max_status == PRO_TK_NOT_VALID)
	{
	  PTUDFExParamRangeFailureStringGet (param, &minimum, &maximum, 
					     reason);
	  ret_status = PRO_TK_NOT_VALID;
	}
    }

  if (ret_status != PRO_TK_NO_ERROR)
    return (ret_status);

/*--------------------------------------------------------------------*\
Check constraints applied to the parameter via parameter tables.
\*--------------------------------------------------------------------*/
  if (param->owner.type == PRM_MODEL)
    {
      status = ProMdlToModelitem (param->owner.who.model, &m_item);
    }
  else
    {
      m_item = param->owner.who.item;
    }

  status = ProModelitemToRelset (&m_item, &relset);

  status = ProRelsetConstraintsGet (&relset, &constraints);
  PT_TEST_LOG ("ProRelsetConstraintsGet", status, status != PRO_TK_NO_ERROR && status != PRO_TK_E_NOT_FOUND);
  
  if (status == PRO_TK_NO_ERROR)
    {
      status = ProArraySizeGet (constraints, &array_size);

      for (i = 0; i < array_size; i++)
	{
/*--------------------------------------------------------------------*\
If the function returns PRO_TK_NOT_VALID the constraint is violated.
\*--------------------------------------------------------------------*/
	  status = PTUDFExParamValidateConstraint (&relset, param->id, value, 
						   constraints[i]);
	  
	  if (status == PRO_TK_NOT_VALID)
	    {
	      PTUDFExParamConstraintFailureStringGet (param, constraints[i], 
						      reason);
	      ret_status = PRO_TK_NOT_VALID;
	      
	      break;
	    }
	}
      
      ProArrayFree ((ProArray*)&constraints);
    }

  return (ret_status);
}