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


/*--------------------------------------------------------------------*\
Pro/TOOLKIT includes
\*--------------------------------------------------------------------*/
#include "ProToolkit.h"
#include "ProObjects.h"

#include "ProAxis.h"
#include "ProCsys.h"
#include "ProGraphic.h"
#include "ProDisplist.h"
#include "ProGeomitem.h"
#include "ProMenu.h"
#include "ProMessage.h"
#include "ProPoint.h"
#include "ProSelection.h"
#include "ProSolid.h"
#include "ProUtil.h"
#include "ProWindows.h"
#include "ProEdge.h"

/*--------------------------------------------------------------------*\
System includes
\*--------------------------------------------------------------------*/

/*--------------------------------------------------------------------*\
Application includes
\*--------------------------------------------------------------------*/
#include "TestError.h"
/*--------------------------------------------------------------------*\
Application macros
\*--------------------------------------------------------------------*/
#define DSPL1_ID   115
#define DSPL2_ID   (DSPL1_ID+1)

#ifdef  MSGFIL
    #undef MSGFIL
#endif

#define MSGFIL msgfil

#define COPY3D(a, b) memcpy(a, b, 3*sizeof(double))
/*--------------------------------------------------------------------*\
Application data types
\*--------------------------------------------------------------------*/
typedef struct tag_hit_list
{
    Pro3dPnt origin;
    int	    n_hits;	
    Pro3dPnt *p_pnt;
    ProBoolean *to_front; 
} Hit_list;

/*--------------------------------------------------------------------*\
Application global/external data
\*--------------------------------------------------------------------*/
static ProFileName msgfil; 

/*--------------------------------------------------------------------*\
Function prototypes
\*--------------------------------------------------------------------*/

int ProTestSolidRay(ProAppData data, int value);
int ProUtilDeleteRay(ProAppData data, int value);

static int CreateRays(ProModelitem *, ProRay *);
static int CreateRaysFromPoint(ProModelitem *, ProRay *);
static int CreateRaysFromEdgeEnd(ProModelitem *, ProRay *);
static int CreateRaysFromCsys(ProModelitem *, ProRay *);
static int CreateRaysFromAxis(ProModelitem *, ProRay *);
static int TransfRays(ProSelection, ProRay *, int);
static int CreateHitList(ProSolid, ProRay*, Hit_list *);
static int CreateDispList(Hit_list *, int *, int *);

/*=============================================================*\
  Function: 	ProTestRay
  Purpose:	To initiate the user's options
\*=============================================================*/
int ProTestRay (void *data, int dummy)
{
		/* Declare external functions */
    int m_id, action;
    ProError status;
    
    ProStringToWstring (msgfil,(char*) "gen_msg.txt");
    
    status = ProMenuFileRegister((char*)"TkRay",(char*) "tkray.mnu", &m_id);
    TEST_CALL_REPORT("ProMenuFileRegister()", "ProTestRay()", 
				    status, status !=PRO_TK_NO_ERROR);
    if (status != PRO_TK_NO_ERROR) return (-1);
    
    status = ProMenubuttonActionSet((char*)"TkRay",(char*) "-Ray Inter Demo", 
	    (ProMenubuttonAction)ProTestSolidRay, NULL, 0);
    TEST_CALL_REPORT("ProMenubuttonActionSet()", "ProTestRay()", 
		status, status !=PRO_TK_NO_ERROR);
    if (status != PRO_TK_NO_ERROR) return (-1);
    status = ProMenubuttonActionSet((char*)"TkRay",(char*) "-Ray Del", 
	(ProMenubuttonAction)ProUtilDeleteRay, NULL, 0);
    TEST_CALL_REPORT("ProMenubuttonActionSet()", "ProTestRay()", 
		status, status !=PRO_TK_NO_ERROR);
    if (status != PRO_TK_NO_ERROR) return (-1);
    status = ProMenubuttonActionSet((char*)"TkRay",(char*) "-Done", 
	(ProMenubuttonAction)ProMenuDelete, NULL, 0);
    TEST_CALL_REPORT("ProMenubuttonActionSet()", "ProTestRay()", 
		status, status !=PRO_TK_NO_ERROR);
    if (status != PRO_TK_NO_ERROR) return (-1);
    status = ProMenubuttonActionSet((char*)"TkRay",(char*) "TkRay", 
	(ProMenubuttonAction)ProMenuDelete, NULL, 0);
    TEST_CALL_REPORT("ProMenubuttonActionSet()", "ProTestRay()", 
		status, status !=PRO_TK_NO_ERROR);
    if (status != PRO_TK_NO_ERROR) return (-1);
    status = ProMenuCreate(PROMENUTYPE_MAIN,(char*) "TkRay", NULL);
    status = ProMenuProcess((char*)"TkRay", &action);

    return(0);
}

/*=============================================================*\
  Function:	ProTestSolidRay    
  Purpose:	main test ProSolidRayIntersectionCompute() function
\*=============================================================*/
int ProTestSolidRay(ProAppData data, int value) 
{
    ProSelection *p_sel;
    ProModelitem modelitem;    
    ProRay	 rays[3];
    ProSolid model;
    int n_ray, n_sel, i, mode;
    ProError status;
    Hit_list h_list[3];
    ProAsmcomppath comp_path;

    ProMessageDisplay(MSGFIL, (char*)"TEST %0s", "Select Csys, Axis or Datum Point");
    
    status = ProSelect((char*)"point,axis,csys,edge_end", 1, NULL, NULL, NULL, NULL, 
	 &p_sel, &n_sel);
    if (status != PRO_TK_NO_ERROR)
	return (0);
    
    status = ProSelectionModelitemGet(p_sel[0], &modelitem);
    TEST_CALL_REPORT("ProSelectionModelitemGet()", "ProTestSolidRay()",
	status, status != PRO_TK_NO_ERROR);

    status = ProSelectionModelitemGet(p_sel[0], &modelitem);
    TEST_CALL_REPORT("ProSelectionModelitemGet()", "ProTestSolidRay()",
	status, status != PRO_TK_NO_ERROR);

    status = ProSelectionAsmcomppathGet(p_sel[0], &comp_path);
    TEST_CALL_REPORT("ProSelectionAsmcomppathGet()", "ProTestSolidRay()",
	status, status != PRO_TK_NO_ERROR);

    model=(comp_path.owner)!=NULL?comp_path.owner:(ProSolid)modelitem.owner;

    n_ray = CreateRays(&modelitem, rays);
    if (n_ray == -1) return (-1);
    
    TransfRays(p_sel[0], rays, n_ray);

    for (i=0; i<n_ray; i++)
    {
	CreateHitList(model, rays+i, h_list+i);
    }

    ProUtilDeleteRay(NULL, 0);

    mode=0;
    status = ProDisplist3dCreate( DSPL1_ID,  (ProDisplistCallback)CreateDispList, (void *)h_list, 
	(void *)&n_ray, (void *)&mode, NULL, NULL, NULL );
    TEST_CALL_REPORT("ProDisplist3dCreate()", "ProTestSolidRay()",
	status, status != PRO_TK_NO_ERROR);
    
    mode=1;	
    status = ProDisplist3dCreate( DSPL2_ID,  (ProDisplistCallback)CreateDispList, (void *)h_list, 
	(void *)&n_ray, (void *)&mode, NULL, NULL, NULL);
    TEST_CALL_REPORT("ProDisplist3dCreate()", "ProTestSolidRay()",
	status, status != PRO_TK_NO_ERROR);

    for (i=0; i<n_ray; i++)
    {
	free(h_list[i].p_pnt);	
	free(h_list[i].to_front);
    }
    
    status = ProWindowRepaint(PRO_VALUE_UNUSED);
    TEST_CALL_REPORT("ProWindowRepaint()", "ProTestSolidRay()",
	status, status != PRO_TK_NO_ERROR);
    return (0);

}

/*=============================================================*\
  Function:	ProUtilDeleteRay
  Purpose:	to delete application's display list
\*=============================================================*/
int ProUtilDeleteRay(ProAppData data, int value)
{
    ProError status;

    status = ProDisplist3dDelete( DSPL1_ID );
    TEST_CALL_REPORT("ProDisplist3dDelete()", "ProUtilDeleteRay()",
	status, status != PRO_TK_NO_ERROR && status != PRO_TK_E_NOT_FOUND);
    status = ProDisplist3dDelete( DSPL2_ID );
    TEST_CALL_REPORT("ProDisplist3dDelete()", "ProUtilDeleteRay()",
	status, status != PRO_TK_NO_ERROR && status != PRO_TK_E_NOT_FOUND);
    status = ProWindowRepaint(PRO_VALUE_UNUSED);
    TEST_CALL_REPORT("ProWindowRepaint()", "ProUtilDeleteRay()",
	status, status != PRO_TK_NO_ERROR && status != PRO_TK_E_NOT_FOUND);

    return (0);
}


/*=============================================================*\
  Function:	CreateRays
  Purpose:	to fill ProRays structure
  Return :	# of rays, -1 if unsuccessfull
\*=============================================================*/
static int CreateRays(
    ProModelitem *mod_item,  /* In : user's selection */
    ProRay *rays)	    /* Out: the filled ray structure */
{
    int ret;

    switch (mod_item->type)
    {
	case PRO_EDGE_END:
	case PRO_EDGE_START:
	    ret = CreateRaysFromEdgeEnd (mod_item, rays) == 0 ? 3 : -1;
	    break;
	case PRO_POINT:
	    ret = CreateRaysFromPoint(mod_item, rays) == 0 ? 3 : -1;
	    break;

	case PRO_CSYS:
	    ret = CreateRaysFromCsys(mod_item, rays) == 0 ? 3 : -1;
	    break;
	    
	case PRO_AXIS:
	    ret = CreateRaysFromAxis(mod_item, rays) == 0 ? 1 : -1;
	    break;

	default :
	    ret =-1;	       
    }
    return (ret);
}

/*=============================================================*\
  Function:	CreateRaysFromEdgeEnd
  Purpose:	to fill ProRays structure
  Return :	0 if successfull, -1 if unsuccessfull
\*=============================================================*/
static int CreateRaysFromEdgeEnd(
    ProModelitem *mod_item,  /* In : user's selection */
    ProRay *rays)	     /* Out: the filled ray structure */
{
    ProEdge edge;
    ProError status;
    double edge_param;
    ProVector xyz, der1, der2, dir;
   
    rays[0].dir_vector[0] = rays[1].dir_vector[1] = 1.0;
    rays[2].dir_vector[2] = 1.0;
    rays[0].dir_vector[1] = rays[0].dir_vector[2] = 0.0;
    rays[1].dir_vector[0] = rays[1].dir_vector[2] = 0.0;
    rays[2].dir_vector[0] = rays[2].dir_vector[1] = 0.0;
	    
    status = ProEdgeInit((ProSolid)mod_item->owner, mod_item->id, &edge);
    TEST_CALL_REPORT("ProEdgeInit()", "CreateRaysFromEdgeEnd()", status, 
	status != PRO_TK_NO_ERROR);
    if (status!=PRO_TK_NO_ERROR) 
	return (-1);

    edge_param = 1.0 * (mod_item->type == PRO_EDGE_END);
    
    ProEdgeXyzdataEval (edge, edge_param, xyz, der1, der2, dir);
    COPY3D (rays[0].start_point, xyz);
	    
    COPY3D(rays[1].start_point, rays[0].start_point);
    COPY3D(rays[2].start_point, rays[0].start_point);
    return (0);
}

/*=============================================================*\
  Function:	CreateRaysFromPoint
  Purpose:	to fill ProRays structure
  Return :	0 if successfull, -1 if unsuccessfull
\*=============================================================*/
static int CreateRaysFromPoint(
    ProModelitem *mod_item,  /* In : user's selection */
    ProRay *rays)	     /* Out: the filled ray structure */
{
    ProPoint pnt;
    ProError status;
   
    rays[0].dir_vector[0] = rays[1].dir_vector[1] = 1.0;
    rays[2].dir_vector[2] = 1.0;
    rays[0].dir_vector[1] = rays[0].dir_vector[2] = 0.0;
    rays[1].dir_vector[0] = rays[1].dir_vector[2] = 0.0;
    rays[2].dir_vector[0] = rays[2].dir_vector[1] = 0.0;
	    
    status = ProPointInit((ProSolid)mod_item->owner, mod_item->id, &pnt);
    TEST_CALL_REPORT("ProPointInit()", "CreateRaysFromPoint()", status, 
	status != PRO_TK_NO_ERROR);
    if (status!=PRO_TK_NO_ERROR) 
	return (-1);

    status = ProPointCoordGet(pnt, rays[0].start_point);
    TEST_CALL_REPORT("ProPointCoordGet()", "CreateRaysFromPoint()", status, 
	status != PRO_TK_NO_ERROR);
    if (status!=PRO_TK_NO_ERROR) 
	return (-1);
	    
    COPY3D(rays[1].start_point, rays[0].start_point);
    COPY3D(rays[2].start_point, rays[0].start_point);
    return (0);
}

/*=============================================================*\
  Function:	CreateRaysFromCsys
  Purpose:	to fill ProRays structure
  Return :	0 if successfull, -1 if unsuccessfull
\*=============================================================*/
static int CreateRaysFromCsys(
    ProModelitem *mod_item,  /* In : user's selection */
    ProRay *rays)	    /* Out: the filled ray structure */
{
    ProError status;
    ProCsys  csys;
    ProGeomitemdata *geomdata;

    status = ProCsysInit((ProSolid)mod_item->owner, mod_item->id, &csys);
    TEST_CALL_REPORT("ProCsysInit()", "CreateRaysFromCsys()", status, 
	status != PRO_TK_NO_ERROR);
    if (status!=PRO_TK_NO_ERROR) 
	return (-1);
	    
    status = ProCsysDataGet(csys, &geomdata);
    TEST_CALL_REPORT("ProCsysDataGet()", "CreateRaysFromCsys()", status, 
	status != PRO_TK_NO_ERROR);
    if (status!=PRO_TK_NO_ERROR) 
	return (-1);

    COPY3D(rays[0].start_point, geomdata->data.p_csys_data->origin);
    COPY3D(rays[1].start_point, rays[0].start_point);
    COPY3D(rays[2].start_point, rays[0].start_point);
    COPY3D(rays[0].dir_vector, geomdata->data.p_csys_data->x_vector);
    COPY3D(rays[1].dir_vector, geomdata->data.p_csys_data->y_vector);
    COPY3D(rays[2].dir_vector, geomdata->data.p_csys_data->z_vector);
	    
    status = ProGeomitemdataFree(&geomdata);
    TEST_CALL_REPORT("ProGeomitemdataFree()", "CreateRaysFromCsys()", status, 
	status != PRO_TK_NO_ERROR);

    return (0);
}

/*=============================================================*\
  Function:	CreateRaysFromAsys
  Purpose:	to fill ProRays structure
  Return :	0 if successfull, -1 if unsuccessfull
\*=============================================================*/
static int CreateRaysFromAxis(
    ProModelitem *mod_item,  /* In : user's selection */
    ProRay *rays)	    /* Out: the filled ray structure */
{
    ProError status;
    ProAxis  axis;
    ProGeomitemdata *geomdata;

    status = ProAxisInit((ProSolid)mod_item->owner, mod_item->id, &axis);
    TEST_CALL_REPORT("ProAxisInit()", "CreateRaysFromAxis()", status, 
	status != PRO_TK_NO_ERROR);
    if (status!=PRO_TK_NO_ERROR) 
	return (-1);

    status = ProAxisDataGet(axis, &geomdata);
    TEST_CALL_REPORT("ProAxisDataGet()", "CreateRaysFromAxis()", status, 
	status != PRO_TK_NO_ERROR);
    if (status!=PRO_TK_NO_ERROR) 
	return (-1);

    COPY3D(rays[0].start_point, geomdata->data.p_curve_data->line.end1);
    COPY3D(rays[0].dir_vector, geomdata->data.p_curve_data->line.end2);
    rays[0].dir_vector[0] -= rays[0].start_point[0]; 
    rays[0].dir_vector[1] -= rays[0].start_point[1]; 
    rays[0].dir_vector[2] -= rays[0].start_point[2]; 

    return (0);
}


/*=============================================================*\
  Function:	TransfRays
  Purpose:	to transform ray's origin and direction to top
		level assembly csys
\*=============================================================*/
static int TransfRays(
    ProSelection sel,   /* In : the user's selection structure */
    ProRay *rays,	/* In, Out : the array of ProRay structures */
    int	n_ray)		/* In : # of rays*/
{
    ProMatrix trf;
    ProError status;
    ProAsmcomppath	comp_path;
    ProVector pnt;
    int i;

    status = ProSelectionAsmcomppathGet(sel, &comp_path);
    TEST_CALL_REPORT("ProSelectionAsmcomppathGet()", "TransfRays()",
	status, status != PRO_TK_NO_ERROR);
    if (comp_path.owner==NULL) return (0);     
    
    status = ProAsmcomppathTrfGet(&comp_path, PRO_B_TRUE, trf);
    TEST_CALL_REPORT("ProAsmcomppathTrfGet()", "TransfRays()",
        status, status != PRO_TK_NO_ERROR);
    for (i=0; i<n_ray; i++)
    {
	status = ProPntTrfEval(rays[i].start_point, trf, pnt);    
	TEST_CALL_REPORT("ProPntTrfEval()", "TransfRays()",
	    status, status != PRO_TK_NO_ERROR);
	COPY3D(rays[i].start_point, pnt);

	status = ProVectorTrfEval(rays[i].dir_vector, trf, rays[i].dir_vector);
	TEST_CALL_REPORT("ProVectorTrfEval()", "TransfRays()",
	    status, status != PRO_TK_NO_ERROR);
    }

    return (0);
}

/*=============================================================*\
  Function:	CreateHitList
  Purpose:	generate hit list for one ray
\*=============================================================*/
static int CreateHitList(
    ProSolid model,	    /* In : The  model */
    ProRay* ray,	    /* In : ray */
    Hit_list *h_list)       /* In : The hit list, user's memory */
{
    
    ProError status;
    int	n_hits, i;
    ProSelection *p_sel;
    ProAsmcomppath	comp_path;
    double depth;
    ProPoint3d pnt;
    ProMode mode;
    ProMatrix trf = { {1.0, 0.0, 0.0, 0.0},
                      {0.0, 1.0, 0.0, 0.0},
                      {0.0, 0.0, 1.0, 0.0},
                      {0.0, 0.0, 0.0, 1.0} };
    
    h_list->p_pnt = NULL;
    h_list->to_front = NULL;
    h_list->n_hits =0;

    status = ProSolidRayIntersectionCompute(model, -1, ray, &p_sel, &n_hits);
    TEST_CALL_REPORT("ProSolidRayIntersectionCompute()", "CreateHitList()",
	status, status != PRO_TK_NO_ERROR && status != PRO_TK_E_NOT_FOUND);

    if (status != PRO_TK_NO_ERROR) 
	return (-1);

    COPY3D(h_list->origin, ray->start_point);   
    h_list->n_hits = n_hits;     
    h_list->p_pnt = (Pro3dPnt *)calloc(n_hits, sizeof(Pro3dPnt));
    h_list->to_front = (ProBoolean *)calloc(n_hits, sizeof(ProBoolean));
    for (i=0; i<n_hits; i++)
    {
	status = ProSelectionDepthGet(p_sel[i], &depth);
	TEST_CALL_REPORT("ProSelectionDepthGet()", "CreateHitList()",
	    status, status != PRO_TK_NO_ERROR);

	h_list->to_front[i] = (ProBoolean)(depth>=0);
	status = ProSelectionAsmcomppathGet(p_sel[i], &comp_path);
	TEST_CALL_REPORT("ProSelectionAsmcomppathGet()", "CreateHitList()",
            status, status != PRO_TK_NO_ERROR);
        status = ProModeCurrentGet(&mode);
	TEST_CALL_REPORT("ProModeCurrentGet()", "CreateHitList()",
            status, status != PRO_TK_NO_ERROR);
        if(mode == PRO_MODE_ASSEMBLY)
        {
            status = ProAsmcomppathTrfGet(&comp_path, PRO_B_TRUE, trf);
            TEST_CALL_REPORT("ProAsmcomppathTrfGet()", "CreateHitList()",
	        status, status != PRO_TK_NO_ERROR);
        }
	status = ProSelectionPoint3dGet(p_sel[i], pnt);
	TEST_CALL_REPORT("ProSelectionPoint3dGet()", "CreateHitList()",
	    status, status != PRO_TK_NO_ERROR);

	status = ProPntTrfEval(pnt, trf, h_list->p_pnt[i]);    
	TEST_CALL_REPORT("ProPntTrfEval()", "CreateHitList()",
	    status, status != PRO_TK_NO_ERROR);
    }

    return (0);
}     

/*=============================================================*\
  Function:	CreateDispList
  Purpose:	generate display list
\*=============================================================*/
static int CreateDispList(
    Hit_list *p_list,	    /*In : the hit list array */
    int *n_list,	    /*In :  n_list[0] contains number of lists in p_list */
    int *mode)		    /*In :  mode[0]==0 behind origin 
				    mode[0]==1 front origin */ 
{
    int i, l;
    ProError status;
    ProColor keep_color, letter_color, highlite_color;
    double radius;
    ProMdl model;
    Pro3dPnt points[2];

    TEST_CALL_REPORT("ProDisplistCallback()", "CreateDispList()", (ProError)0, 0);   

    status = ProMdlCurrentGet (&model);
    TEST_CALL_REPORT("ProMdlCurrentGet()", "CreateDispList()", status, 
	status != PRO_TK_NO_ERROR);
    status = ProSolidOutlineGet ((ProSolid)model, points);
    TEST_CALL_REPORT("ProSolidOutlineGet()", "CreateDispList()", status, 
	status != PRO_TK_NO_ERROR);
	
    radius = 0.3*(points[1][0] - points[0][0])/100.0;
	
	highlite_color.method = PRO_COLOR_METHOD_TYPE;
	highlite_color.value.type = PRO_COLOR_HIGHLITE;
	
	letter_color.method = PRO_COLOR_METHOD_TYPE;
	letter_color.value.type = PRO_COLOR_LETTER;
	    
    status = ProGraphicsColorModify(mode[0] ? &letter_color : 
		&highlite_color, &keep_color); 
    TEST_CALL_REPORT("ProGraphicsColorModify()", "CreateDispList()", status, 
	status != PRO_TK_NO_ERROR);

    for (l=0; l<n_list[0]; l++)
    {   
	for (i=0; i<p_list[l].n_hits; i++)
	{
	    if (mode[0] ^ (p_list[l].to_front[i]==PRO_B_TRUE)) continue;
	    ProGraphicsPenPosition(p_list[l].origin);
    	    ProGraphicsLineDraw(p_list[l].p_pnt[i]);
	    ProGraphicsCircleDraw(p_list[l].p_pnt[i], radius);
	}
    }
    
    status = ProGraphicsColorModify(&keep_color, NULL); 
    TEST_CALL_REPORT("ProGraphicsColorModify()", "CreateDispList()", status, 
	status != PRO_TK_NO_ERROR);
    return (0);
}