/**************************************************************************/
/* 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);
int 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 */
/**************************************************/

// PhasPlot sample program
// Written by Kenneth Moore June 1997
// Version 1.0


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


/* global variables used by this UDF */
int activesurf, numsurfs;
#define MAX_POINTS 100

/* 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)
{
char szModuleName[260], szOutputFile[260];
FILE *output;
double max_sdia, normrad, rad, r2, rt, deltar, maxfreq;
int  i, j, s, numterms, text, show_settings, units;
static char szTemp[500], szBuffer[5000], szSub[256], lensname[100], szAppName[] = "Binary 2 Phase";
double data[201], phasedata[MAX_POINTS+1], freqdata[MAX_POINTS+1], perdata[MAX_POINTS+1], TWOPI;

TWOPI = 6.283185307;

/* 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 lens name */
PostRequestMessage("GetName", lensname);
/* clip off the cr-lf */
if (strlen(lensname) > 2) lensname[strlen(lensname) - 2] = '\0';
else lensname[0] = '\0';

/* now get the system data; we need the number of surfaces */
PostRequestMessage("GetSystem", szBuffer);
numsurfs = atoi(GetString(szBuffer, 0, szSub));
units    = 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));

check_again:;

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

if (activesurf != 0)
	{
   /* verify the surface is still a binary 2 surface */
   sprintf(szBuffer,"GetSurface,%i", activesurf);
   PostRequestMessage(szBuffer, szBuffer);
   GetString(szBuffer, 0, szSub);
	/* if it is not a binary 2; clear the surface flag */
   if (strcmp(szSub, "BINARY_2") != 0) activesurf = 0;
   }

if (activesurf == 0)
	{
   /* maybe there is no config data, or the specified surface is no longer binary; */
   /* it is probably the first time we have run */
   /* find the first surface that is a binary 2; use that as default. */
   for (s = 1; (s <= numsurfs)&&(activesurf==0); s++)
   	{
      sprintf(szBuffer,"GetSurface,%i", s);
      PostRequestMessage(szBuffer, szBuffer);
      GetString(szBuffer, 0, szSub);
      if (strcmp(szSub, "BINARY_2") == 0) activesurf = s;
      }
   }

if (activesurf == 0)
	{
   /* none of them are binary! Issue an error message and make empty windows. */
   MessageBox (hwndClient, "At least one surface must be a Binary 2!", "No phase surfaces?", MB_ICONEXCLAMATION | MB_OK);
   sprintf(szBuffer,"%i", activesurf);
   MakeEmptyWindow(text, szAppName, szBuffer);
   return;
   }

/* is the user requesting to see the settings box? */
if (show_settings)
	{
   int oldsurf, button;
   /* we need to display the settings box! */
   oldsurf = activesurf;
   button = DialogBox(globalhInstance, "SurDlg", hwndClient, (DLGPROC) SurDlgProc);
   if (button == IDCANCEL)
   	{
      activesurf = oldsurf;
		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));

/* get the extra data editor data */
sprintf(szBuffer,"GetExtra,%i,1", activesurf);
PostRequestMessage(szBuffer, szBuffer);

/* how many terms are there? */
numterms = (int) floor(0.5+atof(GetString(szBuffer, 0, szSub)));

/*
for debug
sprintf(szTemp,"number of terms = %i", numterms);
MessageBox(hwndClient, szTemp, "Debug data", MB_ICONEXCLAMATION | MB_OK);
*/

/* the normalization radius */
sprintf(szBuffer,"GetExtra,%i,2", activesurf);
PostRequestMessage(szBuffer, szBuffer);
normrad  = atof(GetString(szBuffer, 0, szSub));

/* avoid divide by zero */
if (normrad <= 0) normrad = 1.0;

/* put all the extra data in an array */
for (i = 1; i <= numterms; i++)
	{
	sprintf(szBuffer,"GetExtra,%i,%i", activesurf, i+2);
	PostRequestMessage(szBuffer, szBuffer);
	data[i] = atof(GetString(szBuffer, 0, szSub));
	}

/*
for debugging
for (j = 1; j <= numterms; j++)
	{
	sprintf(szTemp,"Term %i = %.4E", j, data[j]);
	MessageBox(hwndClient, szTemp, "Debug data", MB_ICONEXCLAMATION | MB_OK);
	}
*/

/* okay, compute the phase from 0 to the max_sdia */
for (i = 0; i <= MAX_POINTS; i++)
	{
   rad = (float) i * (max_sdia/normrad) / (float) MAX_POINTS;
   r2 = rad*rad;
   rt = 1;
   phasedata[i] = 0.0;
   for (j = 1; j <= numterms; j++)
   	{
      rt *= r2;
      phasedata[i] += data[j]*rt/TWOPI;
      }
   }

/* now compute the line period */
deltar = max_sdia/MAX_POINTS;
if (deltar == 0.0) deltar = 1.0;

freqdata[0] = 0.0;
maxfreq = 0.0;
for (i = 1; i <= MAX_POINTS; i++)
	{
   freqdata[i] = fabs(phasedata[i] - phasedata[i-1])/deltar;
   if (freqdata[i] > maxfreq) maxfreq = freqdata[i];
   if (freqdata[i] > 1e-8)
   	{
      perdata[i] = 1.0/freqdata[i];
      }
   else perdata[i] = 0.0;
   }
Get_2_5_10(maxfreq, &maxfreq);

/* 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? */
if (text)
	{
   fputs("Listing of Binary 2 phase data\n",output);
   sprintf(szBuffer, "Lens Name: %s\n", lensname);
   fputs(szBuffer,output);

   if (units == 0) fputs("\n       Radial Ap       Phase(Rad)          Periods       Periods/mm        mm/Period\n",output);
   if (units == 1) fputs("\n       Radial Ap       Phase(Rad)          Periods       Periods/cm        cm/Period\n",output);
   if (units == 2) fputs("\n       Radial Ap       Phase(Rad)          Periods       Periods/in        in/Period\n",output);
   if (units == 3) fputs("\n       Radial Ap       Phase(Rad)          Periods        Periods/M         M/Period\n",output);
   for (i = 0; i <= MAX_POINTS; i++)
   	{
      rad = (float) i * max_sdia / (float) MAX_POINTS;
      if (perdata[i] != 0.0) sprintf(szBuffer,"%16.6f %16.6f %16.6f %16.6f %16.6f\n", rad, phasedata[i]*TWOPI, fabs(phasedata[i]), freqdata[i], perdata[i]);
      else                   sprintf(szBuffer,"%16.6f %16.6f %16.6f %16.6f ----------------\n", rad, phasedata[i]*TWOPI, phasedata[i], freqdata[i]);
      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", szOutputFile, szModuleName, szAppName, activesurf);
   PostRequestMessage(szBuffer, szBuffer);
   }
else
	{
   /* okay, make a plot instead */

   double oldx, oldy, x, y, left, right, top, bottom, xtick, ytick;
   double minphase, maxphase;

   /* make a standard ZEMAX frame */
   /* note the first graphic command must be FRAME or NOFRAME */
   fputs("FRAME\n",output);

   /* draw the main data box */
   left = .2;
   right = .8;
   bottom = .3;
   top = .95;
   xtick = .010;
   ytick = .015;

   sprintf(szBuffer,"BOX %.4f %.4f %.4f %.4f\n", left, bottom, right, top);
   fputs(szBuffer,output);

   /* give it a plot title */
   fputs("TITLE \"Binary 2 Phase and Line Frequency vs. Aperture\"\n",output);

   /* Put the lens name, etc.. on it in the usual place */
   fputs("ADDRESS\n",output);
   fputs("LENSNAME\n",output);
   fputs("DATE\n",output);
   sprintf(szBuffer,"DATA 2 \"Surface number %.i\"\n", activesurf);
   fputs(szBuffer,output);

   /* ok, what is a reasonable y range for the plot? */
   minphase = 0.0;
   maxphase = 0.0;
   for (i = 0; i <= MAX_POINTS; i++)
   	{
      minphase = min(minphase, phasedata[i]);
      maxphase = max(maxphase, phasedata[i]);
      }
   if (minphase < 0)
   	{
      /* pick a min phase smaller than lowest phase to plot; this handy function does just that */
      Get_2_5_10(-minphase, &minphase);
      minphase = -minphase;
      }
   if (maxphase > 0)
   	{
      /* pick a max phase bigger than the biggest phase to plot; this handy function does just that */
      Get_2_5_10(maxphase, &maxphase);
      }
   if (minphase == 0.0 && maxphase == 0.0)
   	{
      /* avoid divide by zero */
      minphase = -1.0;
      maxphase = +1.0;
      }

   /* make plus and minus scales the same if not all above or below zero */
   if (minphase < 0 && maxphase > 0)
   	{
      maxphase =  max(maxphase, -minphase);
      minphase = -maxphase;
      }

   /* vertical left side (phase) */
   fputs("PENSTYLE 1 0 1\n",output);
   sprintf(szBuffer,"TEXT \"%s\" %.4f %.4f 90\n", "Phase in periods", .025, .5);
   fputs(szBuffer,output);

   /* vertical right side (freq) */
   fputs("PENSTYLE 3 0 1\n",output);
   if (units == 0) sprintf(szBuffer,"TEXT \"%s\" %.4f %.4f 90\n", "periods/mm", .975, .55);
   if (units == 1) sprintf(szBuffer,"TEXT \"%s\" %.4f %.4f 90\n", "periods/cm", .975, .55);
   if (units == 2) sprintf(szBuffer,"TEXT \"%s\" %.4f %.4f 90\n", "periods/in", .975, .55);
   if (units == 3) sprintf(szBuffer,"TEXT \"%s\" %.4f %.4f 90\n", "periods/M",  .975, .55);
   fputs(szBuffer,output);


   /* okay, make tick marks */
   for (i = 0; i <= 10; i++)
   	{
      /* vertical */
      y = bottom + (top - bottom)* (float) i / 10.0;
      sprintf(szBuffer,"LINE %.4f %.4f %.4f %.4f\n", left, y, left+xtick, y);
      fputs(szBuffer,output);
      sprintf(szBuffer,"LINE %.4f %.4f %.4f %.4f\n", right, y, right-xtick, y);
      fputs(szBuffer,output);

      /* horizontal */
      x = left + (right - left)* (float) i / 10.0;
      sprintf(szBuffer,"LINE %.4f %.4f %.4f %.4f\n", x, bottom, x, bottom+ytick);
      fputs(szBuffer,output);
      sprintf(szBuffer,"LINE %.4f %.4f %.4f %.4f\n", x, top, x, top-ytick);
      fputs(szBuffer,output);

      if (i == 0 || i == 5 || i == 10)
      	{
         /* label the plot */

         /* vertical left side (phase) */
		   fputs("PENSTYLE 1 0 1\n",output);
         sprintf(szBuffer,"TEXT \"%6.2f\" %.4f %.4f\n", minphase + (float) i / 10.0 * (maxphase-minphase), .10, y);
         fputs(szBuffer,output);

         /* vertical right side (slope) */
		   fputs("PENSTYLE 3 0 1\n",output);
         sprintf(szBuffer,"TEXT \"%6.2f\" %.4f %.4f\n", (float) i / 10.0 * (maxfreq), .85, y);
         fputs(szBuffer,output);

         /* Horizontal */
		   fputs("PENSTYLE 0 0 1\n",output);
         sprintf(szBuffer,"TEXT \"%.2f\" %.4f %.4f\n", (float) i / 10.0 * (max_sdia), x, bottom-.05);
         fputs(szBuffer,output);
         }
      }

   /* Ok, plot the data in color, solid line, with a thicker line */
   fputs("PENSTYLE 1 0 2\n",output);

	oldx = 0.0;
   oldy = 0.0;
   for (i = 0; i <= MAX_POINTS; i++)
   	{
      x = left + (right-left)* (double) i / (double) MAX_POINTS;
      y = bottom + (top-bottom)* (phasedata[i]-minphase) / (maxphase-minphase);
      if (i > 0)
      	{
         sprintf(szBuffer,"LINE %.4f %.4f %.4f %.4f\n", oldx, oldy, x, y);
         fputs(szBuffer,output);
         }
      oldx = x;
      oldy = y;
      }

   /* Ok, plot the data in color, solid line, with a thicker line */
   fputs("PENSTYLE 3 0 2\n",output);

	oldx = 0.0;
   oldy = 0.0;
   for (i = 0; i <= MAX_POINTS; i++)
   	{
      x = left + (right-left)* (double) i / (double) MAX_POINTS;
      y = bottom + (top-bottom)* (freqdata[i]) / (maxfreq);
      if (i > 0)
      	{
         sprintf(szBuffer,"LINE %.4f %.4f %.4f %.4f\n", oldx, oldy, x, y);
         fputs(szBuffer,output);
         }
      oldx = x;
      oldy = y;
      }

   fclose(output);
   /* note the text flag is 1, because we can do text! */
   sprintf(szBuffer,"MakeGraphicWindow,\"%s\",\"%s\",\"%s\",1,%i", szOutputFile, szModuleName, szAppName, activesurf);
   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, "Phase Plot Settings");

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

			return TRUE;

		case WM_COMMAND:
			switch(wID)
				{
				case IDOK:
					/* get surf */
					hwndCntl = GetDlgItem(hDlg, 1000);
					activesurf = 1 + (WORD) SendMessage(hwndCntl, CB_GETCURSEL, 0, 0L);
					EndDialog(hDlg, wParam);
					return TRUE;
				case IDCANCEL:
            	if (lParam)
               	{
                  }
					EndDialog(hDlg, wParam);
					return TRUE;
				}
		}
	return FALSE;
}

