/**************************************************************************/
/* The next few lines are standard "boiler plate" code all UDF's have     */
/**************************************************************************/

#include <windows.h>
#include <dde.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>

/* boiler plate declarations for functions in zclient we will call */
void WaitForData(HWND hwnd);
char *GetString(char *szBuffer, int n, char *szSubString);
void remove_quotes(char *s);
void PostRequestMessage(char *szItem, char *szDataOut);
void CenterWindow(HWND hwnd);
void UserFunction(char *szCommandLine);
void MakeEmptyWindow(int text, char *szAppName, char *szOptions);
void Get_2_5_10(double cmax, double *cscale);

/* boiler plate global variables used by the client code */
extern HINSTANCE globalhInstance;
extern HWND hwndClient;


/**************************************************/
/* Below is the code specific to this UDF program */
/**************************************************/

// Sag program
// Written by James Sutter Aug 1997
// Version 1.0


/* declare the dialog box function for the settings box */
LRESULT CALLBACK SurDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);


/* other global variables */
int activesurf, numsurfs, scandirection;
int steps, nShowxy;
double max_sdia, radial_d, xoffset, yoffset;
#define MAX_POINTS 1000
double DTOR = M_PI/180;

/* okay, now the actual code that creates the UDF data for display in a ZEMAX window */
/* this function MUST be called UserFunction and take the command line argument      */

void UserFunction(char *szCommandLine)
{

/* local variables  */
FILE *output;
char szModuleName[260], szOutputFile[260];
static char szAppName[] = "Sag Calculation";	/* Window title */
static char szBuffer[5000], szSub[256], lensname[100], lensfile[256], plotdate[100];
int  text, show_settings, button;

int  i;  /* loop counter */
int unitflag;
char surftype[50];
double sagdata, sagref;
double xc, yc; /* surface coordinates */
double r, stepsize, angle, costheta, sintheta; /* used to compute surface coordinates */


/* extract the command line arguments */
text          = atoi(GetString(szCommandLine, 0, szSub));
show_settings = atoi(GetString(szCommandLine, 1, szSub));
GetString(szCommandLine, 2, szOutputFile);
remove_quotes(szOutputFile);


/* first, update the lens so we have the latest data; and then test to make sure the system is valid */
PostRequestMessage("GetUpdate", szBuffer);

if (atoi(GetString(szBuffer, 0, szSub)))
	{
   /* the current lens cannot be traced! */
   /* some features may be able to create data without tracing rays; some can't */
   /* If data cannot be created return "empty" data displays */
   sprintf(szBuffer,"%i", activesurf);
   MakeEmptyWindow(text, szAppName, szBuffer);
   return;
   }

/* now get the file path */
PostRequestMessage("GetFile", lensfile);
lensfile[strlen(lensfile)-2] = '\0';

/* now get the lens name */
PostRequestMessage("GetName", lensname);
lensname[strlen(lensname)-2] = '\0';

/* now get the date */
PostRequestMessage("GetDate", plotdate);
plotdate[strlen(plotdate)-2] = '\0';

/* now get the system data; we need the number of surfaces */
PostRequestMessage("GetSystem", szBuffer);
numsurfs = atoi(GetString(szBuffer, 0, szSub));
unitflag = atoi(GetString(szBuffer, 1, szSub));

/* okay, which surface do we do? */

/* we may have been passed this as a command line argument; if we are being updated or were in graphics mode and 'text' was selected */
activesurf = atoi(GetString(szCommandLine, 3, szSub));

/* get the number of steps from command line*/
steps = atoi(GetString(szCommandLine, 4, szSub));
if (steps > MAX_POINTS) steps = MAX_POINTS;
if (steps <= 0) steps = 25;

/* get the radial distance from command line*/
radial_d = atof(GetString(szCommandLine, 5, szSub));
if (radial_d < 0) radial_d = 0;

/* get the scandirection from command line*/
scandirection = atoi(GetString(szCommandLine, 6, szSub));

/* get the x offset from command line*/
xoffset = atof(GetString(szCommandLine, 7, szSub));

/* get the y offset from command line*/
yoffset = atof(GetString(szCommandLine, 8, szSub));

/* get check box setting from command line*/
nShowxy = atoi(GetString(szCommandLine, 9, szSub));

check_again:;

/* the surface may be invalid, better to check */
if (activesurf > numsurfs) activesurf = 0;

/* if the object distance is infinity, surf 0 is invalid */
if (activesurf == 0)
	{
	/* get the surface data; we need the object thickness */
	sprintf(szBuffer,"GetSurface,%i", activesurf);
	PostRequestMessage(szBuffer, szBuffer);
	if (fabs(atof(GetString(szBuffer, 2, szSub))) >= 1.0E+10) activesurf = 1;
   }

/* convert scandirection +y,-y,+x,-x to angle in degrees */
if (scandirection == 0) angle = 0;
if (scandirection == 1) angle = 180;
if (scandirection == 2) angle = 90;
if (scandirection == 3) angle = -90;

 /* is the user requesting to see the settings box? */
if (show_settings)
	{
   /* we need to display the settings box! */
	button = DialogBox(globalhInstance, "SurDlg", hwndClient, (DLGPROC) SurDlgProc);

  	if (button == IDCANCEL)
   	{
		sprintf(szBuffer,"ReleaseWindow,%s", szOutputFile);
		PostRequestMessage(szBuffer, szBuffer);
      return;
      }


	/* check it one more time! */
   show_settings = 0;
   goto check_again;
   }

/* finally, we have the selected surface. Compute the data and write it out. */

/* get the surface data; we need the semi-diameter of the surface */
sprintf(szBuffer,"GetSurface,%i", activesurf);
PostRequestMessage(szBuffer, szBuffer);
max_sdia = atof(GetString(szBuffer, 4, szSub));
GetString(szBuffer, 0, surftype);
surftype[strlen(lensfile)-2] = '\0';

/* if radial distance = 0 then use max_sdia by default */
   if (radial_d > 0) max_sdia = radial_d;

/* get sag at offset point */
sprintf(szBuffer,"GetSag,%i,%f,%f", activesurf, xoffset, yoffset);
PostRequestMessage(szBuffer, szBuffer);
sagref = atof(GetString(szBuffer, 0, szSub));

/* open a file for output */
if ((output = fopen(szOutputFile, "wt")) == NULL)
	{
   /* can't open the file!! */
   return;
   }

/* this windows function gives us the name of our own executable; we pass this back to ZEMAX */
GetModuleFileName(globalhInstance, szModuleName, 255);

/* ok, do you want a text listing or a plot? */
/* we only want to do a text listing because a plot could be confusing */

   fputs("Listing of surface sag at an arbitrary offset point\n\n",output);
   sprintf(szBuffer, "File : %s\n", lensfile);
   fputs(szBuffer,output);
   sprintf(szBuffer, "Title: %s\n", lensname);
   fputs(szBuffer,output);
   sprintf(szBuffer, "Date : %s\n\n", plotdate);
   fputs(szBuffer,output);
   sprintf(szBuffer, "Surface: %i\n", activesurf);
   fputs(szBuffer,output);
   sprintf(szBuffer, "Surface type: %s\n", surftype);
   fputs(szBuffer,output);
   sprintf(szBuffer, "Scan length: %.4f\n", max_sdia);
   fputs(szBuffer,output);
   sprintf(szBuffer, "Scan direction: ");
   fputs(szBuffer,output);
	if (scandirection == 0) sprintf(szBuffer, "+y\n");
	if (scandirection == 1) sprintf(szBuffer, "-y\n");
	if (scandirection == 2) sprintf(szBuffer, "+x\n");
	if (scandirection == 3) sprintf(szBuffer, "-x\n");
   fputs(szBuffer,output);
   sprintf(szBuffer, "Offset from vertex\n", xoffset);
   fputs(szBuffer,output);
   sprintf(szBuffer, "      X offset: %.4f\n", xoffset);
   fputs(szBuffer,output);
   sprintf(szBuffer, "      Y offset: %.4f\n", yoffset);
   fputs(szBuffer,output);
   sprintf(szBuffer, "\nSag at offset point: %.4f\n",sagref);
   fputs(szBuffer,output);

   sprintf(szBuffer, "\nSag in ");
   fputs(szBuffer,output);
   /* add units */
   if (unitflag == 0) fputs("mm relative to offset\n",output);
	if (unitflag == 1) fputs("cm relative to offset\n",output);
   if (unitflag == 2) fputs("in relative to offset\n",output);
   if (unitflag == 3) fputs("meters relative to offset\n",output);


	if (nShowxy == 0)
   	{
	   fputs("\nScan length    Sag\n",output);
		}
   else
   	{
	   fputs("\nScan length    X              Y              Sag\n",output);
   	}

	/* Since don't generate a plot, calculate data in print loop */
   stepsize = (max_sdia / steps);
	sintheta = sin(angle*DTOR);
	costheta = cos(angle*DTOR);
   /* try to reduce round off error */
   if (fabs(costheta) < 1e-15) costheta = 0;
   if (fabs(sintheta) < 1e-15) sintheta = 0;

   for (i = 0; i <= steps; i++)
   	{
		/* compute the radial coordinate to use */
	   r = (i * stepsize);
		yc = r * costheta + yoffset;
		xc = r * sintheta + xoffset;

		sprintf(szBuffer,"GetSag,%i,%f,%f", activesurf, xc, yc);
		PostRequestMessage(szBuffer, szBuffer);
		sagdata = atof(GetString(szBuffer, 0, szSub)) - sagref;

		if (nShowxy == 0)
      	{
	      sprintf(szBuffer,"%12.6E   %12.6E\n", r, sagdata);
	      fputs(szBuffer,output);
   		}
      else
      	{
	      sprintf(szBuffer,"%12.6E   %12.6E   %12.6E   %12.6E\n", r, xc, yc, sagdata);
	      fputs(szBuffer,output);
			}
      }

	/* close the file! */
   fclose(output);

	/* create a text window. Note we pass back the filename, module name, and activesurf as a single setting parameter. */
	sprintf(szBuffer,"MakeTextWindow,\"%s\",\"%s\",\"%s\",%i,%i,%f,%i,%f,%f,%i", szOutputFile, szModuleName, szAppName, activesurf, steps, radial_d, scandirection, xoffset, yoffset, nShowxy);
   PostRequestMessage(szBuffer, szBuffer);

}


LRESULT CALLBACK SurDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
	{
	char szDisplay[260];
	HWND hwndCntl;
	int i;
	WORD wID;

	wID = LOWORD(wParam);
	switch(message)
		{
		case WM_INITDIALOG:
			CenterWindow(hDlg);
			SetWindowText(hDlg, "Sag Settings");

			/* fill the combo box dialog with surfs */
			hwndCntl = GetDlgItem(hDlg, 1000);
			for (i=0; i <= numsurfs; i++)
				{
				sprintf(szDisplay,"%i",i);
				SendMessage(hwndCntl, CB_ADDSTRING, i, (LONG) (LPSTR) szDisplay);
				}
			SendMessage(hwndCntl, CB_SETCURSEL, activesurf, 0L);

         /* initialize editbox with number of steps */
  			hwndCntl = GetDlgItem(hDlg, 1001);
         sprintf(szDisplay, "%i", steps);
         SetWindowText(hwndCntl, szDisplay);

         /* initialize editbox with radial distance */
  			hwndCntl = GetDlgItem(hDlg, 1002);
         sprintf(szDisplay, "%.4f", radial_d);
         SetWindowText(hwndCntl, szDisplay);

			/* fill the combo box dialog with scan directions */
			hwndCntl = GetDlgItem(hDlg, 1003);
			sprintf(szDisplay,"+y");
			SendMessage(hwndCntl, CB_ADDSTRING, 0, (LONG) (LPSTR) szDisplay);
			sprintf(szDisplay,"-y");
			SendMessage(hwndCntl, CB_ADDSTRING, 1, (LONG) (LPSTR) szDisplay);
			sprintf(szDisplay,"+x");
			SendMessage(hwndCntl, CB_ADDSTRING, 2, (LONG) (LPSTR) szDisplay);
			sprintf(szDisplay,"-x");
			SendMessage(hwndCntl, CB_ADDSTRING, 3, (LONG) (LPSTR) szDisplay);
			SendMessage(hwndCntl, CB_SETCURSEL, scandirection, 0L);

         /* initialize editbox with x offset*/
  			hwndCntl = GetDlgItem(hDlg, 1004);
         sprintf(szDisplay, "%.4f", xoffset);
         SetWindowText(hwndCntl, szDisplay);

         /* initialize editbox with y offset*/
  			hwndCntl = GetDlgItem(hDlg, 1005);
         sprintf(szDisplay, "%.4f", yoffset);
         SetWindowText(hwndCntl, szDisplay);

         /* initialize show x,y checkbox */
  			hwndCntl = GetDlgItem(hDlg, 1006);
  			SendMessage(hwndCntl, BM_SETCHECK, nShowxy, 0L);

			return TRUE;

		case WM_COMMAND:
			switch(wID)
				{
				case IDOK:
					/* get surf */
					hwndCntl = GetDlgItem(hDlg, 1000);
					activesurf = (WORD) SendMessage(hwndCntl, CB_GETCURSEL, 0, 0L);

					/* get number of steps */
					hwndCntl = GetDlgItem(hDlg, 1001);
               GetWindowText(hwndCntl, szDisplay, 24);
					steps = atoi(szDisplay);
               if (steps > MAX_POINTS) steps = MAX_POINTS;
					if (steps <= 0) steps = 100;

               /* get the radial distance */
					hwndCntl = GetDlgItem(hDlg, 1002);
               GetWindowText(hwndCntl, szDisplay, 24);
					radial_d = atof(szDisplay);
               if (radial_d < 0) radial_d = 0;

					/* get the scan direction */
					hwndCntl = GetDlgItem(hDlg, 1003);
					scandirection = (WORD) SendMessage(hwndCntl, CB_GETCURSEL, 0, 0L);

               /* get the x-offset */
					hwndCntl = GetDlgItem(hDlg, 1004);
               GetWindowText(hwndCntl, szDisplay, 24);
					xoffset = atof(szDisplay);

               /* get the y-offset */
					hwndCntl = GetDlgItem(hDlg, 1005);
               GetWindowText(hwndCntl, szDisplay, 24);
					yoffset = atof(szDisplay);

               /* get check box */
					hwndCntl = GetDlgItem(hDlg, 1006);
               nShowxy = (WORD) SendMessage (hwndCntl, BM_GETCHECK, 0, 0L);

					EndDialog(hDlg, wParam);
					return TRUE;

            case 1006:
               /* toggle check mark */
					hwndCntl = GetDlgItem(hDlg, 1006);
					SendMessage (hwndCntl, BM_SETCHECK, (WORD)
               	!SendMessage (hwndCntl, BM_GETCHECK, 0, 0L), 0L);
					return TRUE;

				case IDCANCEL:
            	if (lParam)
               	{
                  }
					EndDialog(hDlg, wParam);
					return TRUE;
				}
		}
	return FALSE;
	}

