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


#include <ProToolkit.h>
#include <ProMdlChk.h>
#include <ProParameter.h>
#include <ProSolid.h>
#include <ProDrawing.h>
#include <ProFamtable.h>
#include <ProSelection.h>
#include <ProScope.h>

static wchar_t** s_results_table = NULL;
static wchar_t*  s_results_url = NULL;

#define MISSING_PARAM (int)999
#define INVALID_PARAM_TYPE (int)9999

/*=====================================================================================================*\
FUNCTION: UserCheckMdlParamNameCompare
PURPOSE:  Compare the model name to the value of the given param.
RETURNS:  MISSING_PARAM, INVALID_PARAM_TYPE, or the integer results of the string comparison.
\*======================================================================================================*/
static int UserCheckMdlParamNameCompare (ProMdl mdl, ProName param_name)
{
	ProError status = PRO_TK_NO_ERROR;
	ProMdlName mdl_name;
	ProModelitem modelitem;
	ProParamvalue param_value;
	ProParameter param;
	int compare_result;

	status = ProMdlMdlnameGet (mdl, mdl_name);
  
	status = ProMdlToModelitem(mdl, &modelitem);

	status = ProParameterInit(&modelitem, param_name, &param);

/*------------------------------------------------------------------------------------------------------*\
	Parameter is missing
\*------------------------------------------------------------------------------------------------------*/
	if (status != PRO_TK_NO_ERROR)
	{
		compare_result = MISSING_PARAM;
	}
	else
	{
		ProUnititem units;

		status = ProParameterValueWithUnitsGet(&param,&param_value, &units);

/*------------------------------------------------------------------------------------------------------*\
	Parameter is not a string parameter
\*------------------------------------------------------------------------------------------------------*/
		if (status != PRO_TK_NO_ERROR || param_value.type != PRO_PARAM_STRING)
		{
			compare_result = INVALID_PARAM_TYPE;
		}
		else
		{
/*------------------------------------------------------------------------------------------------------*\
	Compare the model name to the parameter value.
\*------------------------------------------------------------------------------------------------------*/
			ProWstringCompare (param_value.value.s_val, mdl_name, PRO_VALUE_UNUSED, &compare_result);
		}
	}

	return (compare_result);
}

/*======================================================================================================*\
FUNCTION: UserCustCheckMdlParamName
PURPOSE:  Check function for the ModelCheck check.  Outputs details of the comparison as a ModelCheck error.
\*======================================================================================================*/
static ProError UserCustCheckMdlParamName (ProCharName name, ProMdl mdl, ProAppData appdata, 
					int* results_count, wchar_t** results_url, wchar_t*** results_table)
{
	ProFileName message_file;
	ProError status = PRO_TK_NO_ERROR;
	ProName* param_name_ptr = (ProName*)appdata;
	ProMdlName mdl_name;
	int compare_result = UserCheckMdlParamNameCompare (mdl, *param_name_ptr);
	ProLine error_msg;

/*------------------------------------------------------------------------------------------------------*\
	Parameter is set correctly.
\*------------------------------------------------------------------------------------------------------*/
    if (compare_result == 0)
	{
		*results_count = 0;
		*results_url = NULL;
		*results_table = NULL;
		return (PRO_TK_NO_ERROR);
	}
/*------------------------------------------------------------------------------------------------------*\
	Parameter is not set correctly.  Show an appropriate error message.
\*------------------------------------------------------------------------------------------------------*/
	else
	{
		ProStringToWstring (message_file, "pt_ug_modelcheck.txt");

		status = ProMdlMdlnameGet (mdl, mdl_name);

		if (compare_result == MISSING_PARAM)
		{
			status = ProMessageToBuffer (error_msg, message_file, "UG CustomCheck: MDL PARAM NOT FOUND", 
											*param_name_ptr, mdl_name);
		}
		else if (compare_result == INVALID_PARAM_TYPE)
		{
			status = ProMessageToBuffer (error_msg, message_file, "UG CustomCheck: MDL PARAM INV TYPE", 
											*param_name_ptr, mdl_name);
		}
		else
		{
			status = ProMessageToBuffer (error_msg, message_file, "UG CustomCheck: MDL PARAM INCORRECT", 
											*param_name_ptr, mdl_name);
		}

	    status = ProArrayAlloc(1, sizeof(ProWstring), 1, (ProArray*)&s_results_table);
	 
		s_results_table [0] = (wchar_t*) calloc (PRO_LINE_SIZE, sizeof (wchar_t));
		ProWstringCopy (error_msg, s_results_table[0], PRO_VALUE_UNUSED);

/*------------------------------------------------------------------------------------------------------*\
	This URL will be used for the "Check Details" link in the ModelCheck report.
\*------------------------------------------------------------------------------------------------------*/
		s_results_url = (wchar_t*) calloc (PRO_PATH_SIZE, sizeof (wchar_t));
		ProStringToWstring (s_results_url, "http://www.ptc.com/");

	    *results_table = s_results_table;
	    *results_count = 1;
	    *results_url = s_results_url;

		return (PRO_TK_NO_ERROR);
	 }
}

/*======================================================================================================*\
FUNCTION: UserCustCheckMdlAccType
PURPOSE:  Check function for the ModelCheck check for accuracy type.  Outputs the accuracy type.
\*======================================================================================================*/
static ProError UserCustCheckMdlAccType (ProCharName name, ProMdl mdl, ProAppData appdata, 
					int* results_count, wchar_t** results_url, wchar_t*** results_table)
{
	ProFileName message_file;
	ProError status = PRO_TK_NO_ERROR;
	ProAccuracyType acc_type;
	double acc_value;
	ProLine info_msg;
	ProCharLine message_key;

	ProStringToWstring (message_file, "pt_ug_modelcheck.txt");

/*------------------------------------------------------------------------------------------------------*\
	Output the model accuracy type as an info check.
\*------------------------------------------------------------------------------------------------------*/
	status = ProSolidAccuracyGet (mdl, &acc_type, &acc_value);

	switch (acc_type)
	{
	case PRO_ACCURACY_ABSOLUTE:
		strcpy (message_key, "UG CustomCheck: MDL ACC ABS");
		break;
	case PRO_ACCURACY_RELATIVE:
		strcpy (message_key, "UG CustomCheck: MDL ACC REL");
		break;
	default:
		return (PRO_TK_UNSUPPORTED);
	}

	status = ProMessageToBuffer (info_msg, message_file, message_key);
	
	status = ProArrayAlloc(1, sizeof(ProWstring), 1, (ProArray*)&s_results_table);
	 
	s_results_table [0] = (wchar_t*) calloc (PRO_LINE_SIZE, sizeof (wchar_t));
	ProWstringCopy (info_msg, s_results_table[0], PRO_VALUE_UNUSED);

	*results_table = s_results_table;
	*results_count = 1;
	*results_url = NULL;

	return (PRO_TK_NO_ERROR);
}

/*======================================================================================================*\
FUNCTION: UserCustCheckRefScope
PURPOSE:  Check function for the ModelCheck check for reference scope.  Outputs the scope permitted for 
             external refs.
\*======================================================================================================*/
static ProError UserCustCheckRefScope (ProCharName name, ProMdl mdl, ProAppData appdata, 
					int* results_count, wchar_t** results_url, wchar_t*** results_table)
{
	ProFileName message_file;
	ProError status = PRO_TK_NO_ERROR;
	ProExtRefScope scope;
	ProInvalidRefBehavior behavior;
	ProLine info_msg;
	ProCharLine message_key;

	ProStringToWstring (message_file, "pt_ug_modelcheck.txt");

/*------------------------------------------------------------------------------------------------------*\
	Output the model ref control level as an info check.
\*------------------------------------------------------------------------------------------------------*/
	status = ProRefCtrlSolidGet (mdl, &scope, &behavior);

	switch (scope)
	{
	case PRO_REFCTRL_ALLOW_ALL:
		strcpy (message_key, "UG CustomCheck: MDL REFC ALL");
		break;
	case PRO_REFCTRL_ALLOW_SUBASSEMBLY:
		strcpy (message_key, "UG CustomCheck: MDL REFC SUB");
		break;
	case PRO_REFCTRL_ALLOW_SKELETON:
		strcpy (message_key, "UG CustomCheck: MDL REFC SKEL");
		break;
	case PRO_REFCTRL_ALLOW_NONE:
		strcpy (message_key, "UG CustomCheck: MDL REFC NONE");
		break;

	default:
		return (PRO_TK_UNSUPPORTED);
	}

	status = ProMessageToBuffer (info_msg, message_file, message_key);
	
	status = ProArrayAlloc(1, sizeof(ProWstring), 1, (ProArray*)&s_results_table);
	 
	s_results_table [0] = (wchar_t*) calloc (PRO_LINE_SIZE, sizeof (wchar_t));
	ProWstringCopy (info_msg, s_results_table[0], PRO_VALUE_UNUSED);

	*results_table = s_results_table;
	*results_count = 1;
	*results_url = NULL;

	return (PRO_TK_NO_ERROR);
}

/*======================================================================================================*\
FUNCTION: UserCustCheckDwgviewGeneric
PURPOSE:  Check function for the ModelCheck check for generics in drawing views.  
			Outputs a list of the view names.
\*======================================================================================================*/
static ProError UserCustCheckDwgviewGeneric (ProCharName name, ProMdl mdl, ProAppData appdata, 
					int* results_count, wchar_t** results_url, wchar_t*** results_table)
{
	ProFileName message_file;
	ProError status = PRO_TK_NO_ERROR;
	ProView* views;
	int i, size = 0;
	ProSolid solid;
	ProFamtable fam_table;
	wchar_t* table_entry;

	ProStringToWstring (message_file, "pt_ug_modelcheck.txt");

/*------------------------------------------------------------------------------------------------------*\
	Check the model shown by each view 
\*------------------------------------------------------------------------------------------------------*/
	status = ProDrawingViewsCollect ((ProDrawing)mdl, &views);

	if (status == PRO_TK_NO_ERROR)
	{
		status = ProArraySizeGet (views, &size);

		if (status == PRO_TK_NO_ERROR && size > 0)
		{
			status = ProArrayAlloc(0, sizeof(ProWstring), 1, (ProArray*)&s_results_table);

			for (i = 0; i < size; i ++)
			{
				status = ProDrawingViewSolidGet ((ProDrawing)mdl, views[i], &solid);

/*------------------------------------------------------------------------------------------------------*\
	If ProFamtableCheck() succeeds, this means that the model has a family table with at least one 
	instance or item in it.
\*------------------------------------------------------------------------------------------------------*/
				status = ProFamtableInit (solid, &fam_table);
				status = ProFamtableCheck (&fam_table);
				
				if (status == PRO_TK_NO_ERROR)
				{
					table_entry  = (wchar_t*) calloc (PRO_NAME_SIZE, sizeof (wchar_t));

					ProDrawingViewNameGet ((ProDrawing)mdl, views [i], table_entry);

					ProArrayObjectAdd ((ProArray*)&s_results_table, -1, 1, &table_entry);
				}
			}

/*------------------------------------------------------------------------------------------------------*\
	If no items were found...
\*------------------------------------------------------------------------------------------------------*/
			status = ProArraySizeGet (s_results_table, &size);
			if (size == 0)
			{
				ProArrayFree ((ProArray*)&s_results_table);
				s_results_table = NULL;
			}
		}
	}
				
	*results_table = s_results_table;
	*results_count = size;
	*results_url = NULL;

	return (PRO_TK_NO_ERROR);
}


/*======================================================================================================*\
FUNCTION: UserCustCheckCleanUtility
PURPOSE:  Cleanup function for all of the ModelCheck checks. 
\*======================================================================================================*/
static ProError UserCustCheckCleanUtility (ProCharName name, ProMdl mdl, ProAppData appdata)
{
	int size, i;
	ProError status = PRO_TK_NO_ERROR;

	if (s_results_table !=  NULL)
	{
		status = ProArraySizeGet (s_results_table, &size);

		if (status == PRO_TK_NO_ERROR)
		{
			for (i = 0; i < size; i++)
				free (s_results_table [i]);
		
			ProArrayFree ((ProArray*)&s_results_table);
		}
		s_results_table = NULL;
	}

	if (s_results_url != NULL)
	{
		free (s_results_url);
		s_results_url = NULL;
	}

	return (PRO_TK_NO_ERROR);
}

/*======================================================================================================*\
FUNCTION: UserCustUpdateMdlParamName
PURPOSE:  Update function for a ModelCheck error due to an invalid or missing model name parameter.
\*======================================================================================================*/
static ProError UserCustUpdateMdlParamName (ProCharName name, ProMdl mdl, wchar_t* selected_item, 
											ProAppData appdata)
{
	ProFileName message_file;
	ProError status = PRO_TK_NO_ERROR;
	ProName* param_name_ptr = (ProName*)appdata;
	ProMdlName mdl_name;
	int compare_result = UserCheckMdlParamNameCompare (mdl, *param_name_ptr);
	ProModelitem modelitem;
	ProParamvalue param_value;
	ProParameter param;

	if (compare_result == 0)
	{
		/* Nothing to do */
		return (PRO_TK_NO_ERROR);
	}
	else
	{
		ProStringToWstring (message_file, "pt_ug_modelcheck.txt");

		status = ProMdlMdlnameGet (mdl, mdl_name);

		param_value.type = PRO_PARAM_STRING;
		ProWstringCopy (mdl_name, param_value.value.s_val, PRO_VALUE_UNUSED);

		status = ProMdlToModelitem (mdl, &modelitem);

/*------------------------------------------------------------------------------------------------------*\
	Create the missing parameter with the correct value.
\*------------------------------------------------------------------------------------------------------*/
		if (compare_result == MISSING_PARAM)
		{
			ProParameterWithUnitsCreate(&modelitem, *param_name_ptr, &param_value, NULL, &param);
			status = ProMessageDisplay (message_file, "UG CustomCheck: MDL PARAM UPDATED", 
										*param_name_ptr);
		}
/*------------------------------------------------------------------------------------------------------*\
	Since there is no way to change a parameter's type except by deleting and recreating it, the
	check will not attempt to repair this situation.  Instead, show a message to the user.
\*------------------------------------------------------------------------------------------------------*/
		else if (compare_result == INVALID_PARAM_TYPE)
		{
			status = ProMessageDisplay (message_file, "UG CustomCheck: MDL PARAM UPDATE TYPE", 
										*param_name_ptr);
		}
/*------------------------------------------------------------------------------------------------------*\
	Change the value of the parameter appropriately.
\*------------------------------------------------------------------------------------------------------*/
		else
		{
			ProParameterInit (&modelitem, *param_name_ptr, &param);
			ProParameterValueWithUnitsSet(&param, &param_value, NULL);
			status = ProMessageDisplay (message_file, "UG CustomCheck: MDL PARAM UPDATED", 
										*param_name_ptr);
		}

		return (PRO_TK_NO_ERROR);
	 }
}

/*======================================================================================================*\
FUNCTION: UserDwgviewFilterByName
PURPOSE:  Filter function to find drawing views by matching the input name.
\*======================================================================================================*/
static ProError UserDwgviewFilterByName (ProDrawing drawing, ProView view, ProAppData app_data)
{
	ProError status = PRO_TK_NO_ERROR;
	ProName name;
	wchar_t* target_name = (wchar_t*)app_data;
	int compare_result;

	status = ProDrawingViewNameGet (drawing, view, name);

	ProWstringCompare (name, target_name, PRO_VALUE_UNUSED, &compare_result);

	if (compare_result == 0)
		return (PRO_TK_NO_ERROR);
	else
		return (PRO_TK_CONTINUE);
}

#define DELTA_X 10.0  /* Screen coordinates */
#define DELTA_Y 10.0  /* Screen coordinates */

/*======================================================================================================*\
FUNCTION: UserDwgviewHighlight
PURPOSE:  Action function to highlight the drawing view.
\*======================================================================================================*/
static ProError UserDwgviewHighlight (ProDrawing drawing, ProView view, ProError filter_status, 
										ProAppData app_data)
{
	ProError status = PRO_TK_NO_ERROR;
	int sheet;
	int win_id;
	ProPoint3d outline [2];
	ProPoint3d points [5];
	ProColor old_color, highlite_color;
	ProLinestyle old_ls;

	status = ProDrawingViewSheetGet (drawing, view, &sheet);

	status = ProDrawingCurrentSheetSet (drawing, sheet);

	status = ProWindowCurrentGet (&win_id);
	ProWindowRefresh (win_id);

/*------------------------------------------------------------------------------------------------------*\
	Draw the outline of the view to highlight it.
\*------------------------------------------------------------------------------------------------------*/
	status = ProDrawingViewOutlineGet  (drawing, view, outline);

	points[0][0] = outline[0][0] - DELTA_X;
	points[0][1] = outline[0][1] - DELTA_Y;
	points[0][2] = 0.0;
	points[1][0] = outline[0][0] - DELTA_X;
	points[1][1] = outline[1][1] + DELTA_Y;
	points[1][2] = 0.0;
	points[2][0] = outline[1][0] + DELTA_X;
	points[2][1] = outline[1][1] + DELTA_Y;
	points[2][2] = 0.0;
	points[3][0] = outline[1][0] + DELTA_X;
	points[3][1] = outline[0][1] - DELTA_Y;
	points[3][2] = 0.0;
	points[4][0] = outline[0][0] - DELTA_X;
	points[4][1] = outline[0][1] - DELTA_Y;
	points[4][2] = 0.0;

	ProLinestyleSet (PRO_LINESTYLE_PHANTOM, &old_ls);
	
	highlite_color.method = PRO_COLOR_METHOD_TYPE;
	highlite_color.value.type = PRO_COLOR_HIGHLITE;
	ProGraphicsColorModify (&highlite_color, &old_color);

	ProGraphicsPolylineDraw(points, 5); 

	ProGraphicsColorModify (&old_color, NULL);
	ProLinestyleSet (old_ls, NULL);

	return (PRO_TK_E_FOUND);
}

/*======================================================================================================*\
FUNCTION: UserCustActionDwgviewGeneric
PURPOSE:  Action function for the ModelCheck check that locates drawing views using generics.
\*======================================================================================================*/
static ProError UserCustActionDwgviewGeneric (ProCharName name, ProMdl mdl, wchar_t* selected_item, 
											ProAppData appdata)
{
	ProFileName message_file;
	ProError status = PRO_TK_NO_ERROR;

	ProStringToWstring (message_file, "pt_ug_modelcheck.txt");

	ProDrawingViewVisit ((ProDrawing)mdl, UserDwgviewHighlight, UserDwgviewFilterByName, 
						(ProAppData)selected_item);

	return (PRO_TK_NO_ERROR);
}

/*======================================================================================================*\
FUNCTION: UserCustomModelChecksDefine
PURPOSE:  Registers all of the custom Pro/TOOLKIT ModelCheck checks.
\*======================================================================================================*/
int UserCustomModelChecksDefine()
{
	ProFileName message_file;
	ProCharName model_check_name;
	ProLine check_label, action_label, update_label;
	static ProName param_name;
	ProError status;

/*--------------------------------------------------------------------*\
	Prepare the button labels
\*--------------------------------------------------------------------*/

	/* Parameter name that should contain the model owner name */
	ProStringToWstring (param_name, "MDL_NAME_PARAM");

	ProStringToWstring (message_file, "pt_ug_modelcheck.txt");

	/* The name of the check. */
    strcpy(model_check_name, "CHKTK_UG_MDLPARAM_NAME");
    
    /* The label for the check */
	status = ProMessageToBuffer (check_label, message_file, "UG CustomCheck: MDL PARAM NAME", param_name);

    /* Label for the button used to update the model for an 
       item found by the check */
	status = ProMessageToBuffer (update_label, message_file, "UG CustomCheckUpdate: MDL PARAM NAME");

/*--------------------------------------------------------------------*\
	Register the custom ModelCheck check.  This check allows update
	of the incorrect situation.
\*--------------------------------------------------------------------*/
    status = ProModelcheckCheckRegister (model_check_name, check_label, NULL, 
										UserCustCheckMdlParamName, 
										UserCustCheckCleanUtility, 
										NULL, 
										NULL,
										update_label, 
										UserCustUpdateMdlParamName, 
										(ProAppData) &param_name);

	
/*--------------------------------------------------------------------*\
	Prepare the button labels
\*--------------------------------------------------------------------*/

	/* The name of the check. */
    strcpy(model_check_name, "CHKTK_UG_MDL_ACC_TYPE");
    
    /* The label for the check */
	status = ProMessageToBuffer (check_label, message_file, "UG CustomCheck: MDL ACC TYPE");

/*--------------------------------------------------------------------*\
	Register the custom ModelCheck check (an info check, so no 
	action or update is possible).
\*--------------------------------------------------------------------*/
    status = ProModelcheckCheckRegister (model_check_name, check_label, NULL, 
										UserCustCheckMdlAccType, 
										UserCustCheckCleanUtility, 
										NULL, 
										NULL,
										NULL, 
										NULL, 
										NULL);

/*--------------------------------------------------------------------*\
	Prepare the button labels
\*--------------------------------------------------------------------*/

	/* The name of the check. */
    strcpy(model_check_name, "CHKTK_UG_MDL_REFC_SCOPE");
    
    /* The label for the check */
	status = ProMessageToBuffer (check_label, message_file, "UG CustomCheck: MDL REFC SCOPE");

/*--------------------------------------------------------------------*\
	Register the custom ModelCheck check (an info check, so no 
	action or update is possible).
\*--------------------------------------------------------------------*/
    status = ProModelcheckCheckRegister (model_check_name, check_label, NULL, 
										UserCustCheckRefScope, 
										UserCustCheckCleanUtility, 
										NULL, 
										NULL,
										NULL, 
										NULL, 
										NULL);

/*--------------------------------------------------------------------*\
	Prepare the button labels
\*--------------------------------------------------------------------*/

	/* The name of the check. */
    strcpy(model_check_name, "CHKTK_UG_DWGVIEW_GENERIC");
    
    /* The label for the check */
	status = ProMessageToBuffer (check_label, message_file, "UG CustomCheck: DWGVIEW GENERIC", param_name);

    /* Label for the button used to update the model for an 
       item found by the check */
	status = ProMessageToBuffer (action_label, message_file, "UG CustomCheckAction: DWGVIEW GENERIC");

/*--------------------------------------------------------------------*\
	Register the custom ModelCheck check; this check has a Highlight
	action that users can use.
\*--------------------------------------------------------------------*/
    status = ProModelcheckCheckRegister (model_check_name, check_label, NULL, 
										UserCustCheckDwgviewGeneric, 
										UserCustCheckCleanUtility, 
										action_label, 
										UserCustActionDwgviewGeneric,
										NULL, 
										NULL, 
										NULL);

	

	return (PRO_TK_NO_ERROR);
}