/*********************************************************************************************
 * Copyright 2005 - Volian Enterprises, Inc. All rights reserved.
 * Volian Enterprises - Proprietary Information - DO NOT COPY OR DISTRIBUTE
 * ------------------------------------------------------------------------------
 * $Workfile: ROFST.cs $     $Revision: 13 $
 * $Author: Kathy $   $Date: 8/31/05 11:32a $
 *
 * $History: ROFST.cs $
 * 
 * *****************  Version 13  *****************
 * User: Kathy        Date: 8/31/05    Time: 11:32a
 * Updated in $/LibSource/ROFST
 * B2005-032: new groups crashed when writing to ro.fst during approve
 * sel.
 * 
 * *****************  Version 12  *****************
 * User: Kathy        Date: 8/16/05    Time: 2:52p
 * Updated in $/LibSource/ROFST
 * B2005-030: crash when writing ro.fst for new ro
 * 
 * *****************  Version 11  *****************
 * User: Kathy        Date: 6/21/05    Time: 1:36p
 * Updated in $/LibSource/ROFST
 * FindModROs for approve selected, crash on xyplot with missing end ">"
 * 
 * *****************  Version 10  *****************
 * User: Jsj          Date: 6/02/05    Time: 11:31a
 * Updated in $/LibSource/ROFST
 * fix for approving with conditional ROs
 * 
 * *****************  Version 9  *****************
 * User: Jsj          Date: 5/20/05    Time: 9:21a
 * Updated in $/LibSource/ROFST
 * fixed getro for cases when using unit specific info to process the RO
 * return value.
 * 
 * *****************  Version 8  *****************
 * User: Jsj          Date: 5/17/05    Time: 11:48a
 * Updated in $/LibSource/ROFST
 * fixed approve graphic bug, added default graphic extension as part of
 * ROFST class initialization, code cleanup
 * 
 * *****************  Version 7  *****************
 * User: Kathy        Date: 5/11/05    Time: 9:26a
 * Updated in $/LibSource/ROFST
 * add close method
 * 
 * *****************  Version 6  *****************
 * User: Jsj          Date: 4/07/05    Time: 11:15a
 * Updated in $/LibSource/ROFST
 * fixes
 * 
 * *****************  Version 5  *****************
 * User: Jsj          Date: 3/31/05    Time: 9:16a
 * Updated in $/LibSource/ROFST
 * 
 * *****************  Version 4  *****************
 * User: Jsj          Date: 3/28/05    Time: 12:18p
 * Updated in $/LibSource/ROFST
 * 
 * *****************  Version 3  *****************
 * User: Jsj          Date: 3/22/05    Time: 9:52a
 * Updated in $/LibSource/ROFST
 * March 22 2005 update for merge
 * 
 * *****************  Version 2  *****************
 * User: Jsj          Date: 3/14/05    Time: 1:36p
 * Updated in $/LibSource/ROFST
 * update
 * 
 * *****************  Version 1  *****************
 * User: Jsj          Date: 3/10/05    Time: 3:53p
 * Created in $/LibSource/ROFST
 * 
 * *****************  Version 1  *****************
 * User: Jsj          Date: 3/10/05    Time: 3:49p
 * Created in $/LibSource/ROFST/ROFST
 * 
  *********************************************************************************************/
using System;
using System.IO;
using System.Windows.Forms;
using System.Text;
using System.Collections;
using VlnStatus;
using Utils;

/*
 * This namespace is used in both VE-PROMS and the RO Editor programs.
 */
namespace ROFST_FILE
{
	/*
	 * Globally used functions amoung a few of the classes in this namespace
	 */
	public class ROProcessTools
	{
		// For now, have hooks to set and get the current
		// procedure number along with the Maxium Procedure
		// Number length.  The 16-bit code used globals
		// jsj - 6/2/2005

		private static string PNumber = " "; // default to blank
		private static int mxPrcLen = 30; // default to 30 chars

		static ROProcessTools()
		{
		}
		
		public static string ProcedureNumber
		{
			set { PNumber = value.ToString();}
			get { return PNumber; }
		}

		public static int MaxProcNumLen
		{
			set { mxPrcLen = (int)value;}
			get { return mxPrcLen;}
		}
	
		public static string UnitSpecific(string str, int len, int trm, int maxPrcNumLen)
		{
			// This routine retrieves the procedure number format from the
			// current directory and uses it to adjust the procedure number
			// in the specified string
			string retvalu="";
			string asis="#";
			string pnum;
			if( str == null || str.Length == 0 ) return null;
			/*
			 * Bug fix: B2005-017 3/2/2005
			 * Need to use the largest procedure number length that's in the
			 * SET.DFF file.  "LargestNumber" is the largest length after the
			 * procedure number is replaced via the SET.INI file "replace" list.
			 *
				if(len==0)len=LargestNumber;
			 */
			if(len==0)len=maxPrcNumLen; //LargestRawSetNumber
			string mstr;
			PrivateProfile proc_ini = new PrivateProfile("proc.ini");
			mstr = proc_ini.Attr("unit", "procedure number");
			if (mstr == null) mstr = asis;
			pnum=mstr;
			if(pnum.Equals(asis))
			{
				retvalu=str;
			} 
			else if (!pnum.Equals("") && pnum[0] == '!') 
			{
				if (File.Exists("set.ini"))
				{
					string p = str;
					string q, tonum;
					StringBuilder rbldr = new StringBuilder();
					int newl, dw, cnt=0, lstr;

					p = p.TrimStart(" ".ToCharArray());
					lstr = p.Length;
					if (len > lstr) len = lstr;
					mstr = p.Substring(0,len);
					mstr = mstr.TrimEnd(" \t\r\n".ToCharArray());
					/* debug
					string debugstr = "|" + mstr + "|";
					*/
		
					PrivateProfile set_ini = new PrivateProfile("set.ini");

					tonum = set_ini.Attr("replace", mstr);
					if (tonum == null || tonum.Equals("")) tonum = mstr;
					newl = tonum.Length;
					dw = newl - (lstr);

					q = str; p = tonum;
					int strptr=0;
					for (strptr=0; str[strptr] == ' '; strptr++)
						rbldr.Append(" ");//initial spaces
					int pptr=0;
					while (cnt < len) 
					{
						if (tonum!=null && pptr < tonum.Length) 
							rbldr.Append(tonum[pptr++]);
						else if (trm == 1) break;
						else if (cnt < lstr) rbldr.Append(" ");
						else break;
						cnt++;
					}
					if (trm==0) 
					{
						strptr+=len;
						while (str!=null && strptr < str.Length) 
							rbldr.Append(str[strptr++]);
					}
					retvalu = rbldr.ToString();
				}
				else
				{
					retvalu=str;
				}
			} 
			else 
			{
				StringBuilder rbldr = new StringBuilder();
				// create pointers to use to transfer info
				int pnumPtr = 0, strPtr=0, endStrPtr = str.Length-1;
				string pad = "";
				// determine if the procedure number should be padded instead
				if(str[0]==' ')pad=" ";
				// copy from the procedure number definition to the pound symbol
				if (!pnum.Equals(""))
				{
					while(pnumPtr < pnum.Length && pnum[pnumPtr] != '#')
					{
						rbldr.Append(((!pad.Equals(""))? pad : pnum[pnumPtr].ToString()));
						pnumPtr++;
					}
					// skip the pound sign
					if(pnumPtr < pnum.Length && pnum[pnumPtr]!=0)pnumPtr++;
				}
				// assuming that the procedure number occurs at the beginning of
				// str, check to see where it ends
				if(str.Length > len)endStrPtr=len-1;
				// find the end of the procedure number
				while(endStrPtr > strPtr && str[endStrPtr]==' ')endStrPtr--;
				// copy the procedure number
				while(strPtr <= endStrPtr)rbldr.Append(str[strPtr++]);
				// copy the rest of the format
				while(pnumPtr < pnum.Length)
				{
					rbldr.Append(((!pad.Equals(""))? pad : pnum[pnumPtr].ToString()));
					pnumPtr++;
				}
				// copy the rest of the given string
				while(strPtr < str.Length)rbldr.Append(str[strPtr++]);
				retvalu = rbldr.ToString();
			}
			return retvalu;
		}

		public static int matchingBrace(string str)
		{
			int strptr = 0;
			int  level=1;
			while(level > 0)
			{
				strptr++;
				switch(str[strptr])
				{
					case '{':
						level++;
						break;
					case '}':
						level--;
						break;
				}
			}
			return strptr;
		}

		public static int nextDelimiter(string delim,string str,int l)
		{
			int nxtdelim = -1;
			int strptr = 0;
			while(l-- > 0 && (nxtdelim == -1))
			{
				if (delim.IndexOf(str[strptr])>-1)
					nxtdelim = strptr;
				strptr++;
			}
			return nxtdelim;
		}

		public static string getProfile(string grp, string nam)
		{
			return getProfile(grp,nam,0);
		}

		public static string getProfile(string grp, string nam, int flag)
		{
			string buff=null;
			if( grp==null ) return null;
			PrivateProfile proc_ini = new PrivateProfile("proc.ini");
			buff = proc_ini.Attr(grp,nam);
			if (buff == null)
				buff = "";
			if(buff.Equals(""))
			{
				if (flag == 0)
					buff = "<" + grp + "-" + nam + ">";
				else
					return null;
			}
			return buff;
		}

		public static string evaluate(string str,int len)
		{
			string retval=null;
			string swhir = "PSU"; /* order inwhich to check */
			string pn;
			if(str[1]=='-')
			{
				int swptr = swhir.IndexOf((str.ToUpper())[0]);
				byte swchar = 0;
				if( swptr > -1 ) swchar = (byte)swhir[swptr];

				byte sav=(byte)str[len];
				str = str.Substring(0,len);
				while ((swptr > -1) && (swhir[swptr] != 0) && retval == null)
				{
					if (swchar == 0) swchar = (byte)str[0];
					switch((char)swchar)
					{
						case 'U':
							retval=getProfile("Unit",str.Substring(2),swhir[swptr]);
							break;
						case 'S':
							retval=getProfile("Procedure Set",str.Substring(2),swhir[swptr]);
							break;
						case 'P':
							pn = UnitSpecific(PNumber, 0, 1, mxPrcLen);
							retval=getProfile(pn,str.Substring(2),swhir[swptr]);
							break;
					}
					if (retval == null)
						swchar= (byte)swhir[++swptr];
				}
				StringBuilder sb1 = new StringBuilder(str);
				sb1.Insert(len,sav);
				str = sb1.ToString();
			} 
			else if(len==1 && (str.ToUpper())[0]=='U') 
			{
				retval=getProfile("Unit","Number");
			} 
			else if(len==2 && str.ToUpper().StartsWith("ID")) 
			{
				retval=getProfile("Unit","ID");
			}
			if(retval == null)
			{
				retval=str.Substring(0,len+2>str.Length?str.Length:len+2);
			}
			return retval;
		}

	}


	/// <summary>
	/// Summary description for ROFSTHeader.
	/// </summary>

	public class FST_FileHeader
	{
		public int hSize; // size of header structure (record)
		public short hYear;// year this header was designed (1993)
		public byte hMonth;// month this header was designed (10)
		public byte hDay;// day this header was designed (25)
		public short hcYear;// FST file creation year
		public byte hcMonth;// FST file creation month
		public byte hcDay;// FST file creation day
		public byte hcHour;// FST file creation hour
		public byte hcMin;// FST file creation minute
		public byte hcSec;// FST file creation seconds
		public byte hcHund;// FST file creation hundredth seconds
		public uint dboffset;// end of RO data, start of database list


		// Constructors
		// default constructor creates with zero'd out values
		public FST_FileHeader()
		{
			hSize = 0;
			hYear = 0;
			hMonth =  0;
			hDay = 0;
			hcYear = 0;
			hcMonth = 0;
			hcDay = 0;
			hcHour = 0;
			hcMin = 0;
			hcSec = 0;
			hcHund = 0;
			dboffset = 0;
		}


		// Constructor that initializes with pass in values
		FST_FileHeader(int Size, short Year, byte Month, byte Day, short cYear, byte cMonth,
			byte cDay, byte cHour, byte cMin, byte cSec, byte cHund, uint offset)
		{
			hSize = Size;
			hYear = Year;
			hMonth =  Month;
			hDay = Day;
			hcYear = cYear;
			hcMonth = cMonth;
			hcDay = cDay;
			hcHour = cHour;
			hcMin = cMin;
			hcSec = cSec;
			hcHund = cHund;
			dboffset = offset;
		}


		public void Read (BinaryReader br)
		{
			hSize = br.ReadInt32();
			hYear = br.ReadInt16();
			hMonth = br.ReadByte();
			hDay = br.ReadByte();
			hcYear = br.ReadInt16();
			hcMonth = br.ReadByte();
			hcDay = br.ReadByte();
			hcHour = br.ReadByte();
			hcMin = br.ReadByte();
			hcSec = br.ReadByte();
//			byte tbyte = br.ReadByte();
//			hcHund = (sbyte)tbyte;
			hcHund = br.ReadByte();
			dboffset = br.ReadUInt32();

			//			return new FST_FileHeader(fSize,fYear,fMonth,fDay,
			//				fcYear,fcMonth,fcDay,fcHour,fcMin,fcSec,fcHund,fdboffset);
		}


		public void Write (BinaryWriter bw)
		{
			bw.Write(hSize);
			bw.Write(hYear);
			bw.Write(hMonth);
			bw.Write(hDay);
			bw.Write(hcYear);
			bw.Write(hcMonth);
			bw.Write(hcDay);
			bw.Write(hcHour);
			bw.Write(hcMin);
			bw.Write(hcSec);
			bw.Write(hcHund);
			bw.Write(dboffset);
		}


		public int GetStructSize()
		{
			int rtval = 0;

			// You can only use sizeof() in unsafe compiles
			// so we'll do it by hand...
			// each number is the number of bytes

			rtval += 4; //sizeof(hSize);
			rtval += 2; //sizeof(hYear);
			rtval += 1; //sizeof(hMonth);
			rtval += 1; //sizeof(hDay);
			rtval += 2; //sizeof(hcYear);
			rtval += 1; //sizeof(hcMonth);
			rtval += 1; //sizeof(hcDay);
			rtval += 1; //sizeof(hcHour);
			rtval += 1; //sizeof(hcMin);
			rtval += 1; //sizeof(hcSec);
			rtval += 1; //sizeof(hcHund);
			rtval += 4; //sizeof(dboffset);

			return rtval;
		}


		public void SetHeader(uint offset)
		{
			hSize = GetStructSize();
			hYear = 1993;
			hMonth = 10;
			hDay = 25;
			DateTime dt = DateTime.Now;
			hcYear = (short)dt.Year;
			hcMonth = (byte)dt.Month;
			hcDay = (byte)dt.Day;
			hcHour = (byte)dt.Hour;
			hcMin = (byte)dt.Minute;
			hcSec = (byte)dt.Second;
			hcHund = (byte)dt.Millisecond;
			dboffset = offset;
		}
	}


	public class ROFST_DbInfo
	{
		public ushort dbiID; // Number part of table name (RO000002 => 02)
		public ushort dbiType; // database type (Setpoint,Graphics,User defined)
		public ushort dbiAW; // length of widest AccPageID
		public uint dbiGL; // FST file position of this group
		public uint dbiIL; // FST file position of list of ROIDs
		public uint dbiAL; // FST file position of list of AccPageIDs
		public uint dbiEND; // FST file position of the end of this group
		// !!! make sure to convert strings to char arrays when writing
		public string dbiTitle; // database title
		public string dbiAP;  // AccPageID Prefix (ex. SP- )


		// Constructors
		// default constructor creates with zero'd out values
		public ROFST_DbInfo()
		{
			dbiID = 0;
			dbiType = 0;
			dbiAW = 0;
			dbiGL = 0;
			dbiIL = 0;
			dbiAL = 0;
			dbiEND = 0;
			dbiTitle = ""; 
			dbiAP = "";
		}


		// Constructor that initializes with pass in values
		ROFST_DbInfo(ushort ID, ushort Type, ushort AW, uint GL, uint IL, uint AL,	uint END, 
			string Title, string dbiAP)
		{
			dbiID = ID;
			dbiType = Type;
			dbiAW = AW;
			dbiGL = GL;
			dbiIL = IL;
			dbiAL = AL;
			dbiEND = END;
			dbiTitle = Title; 
			dbiAP = dbiAP;
		}


		public void WriteUntAndShort(BinaryWriter bw)
		{
			bw.Write(dbiID);
			bw.Write(dbiType);
			bw.Write(dbiAW);
			bw.Write(dbiGL);
			bw.Write(dbiIL);
			bw.Write(dbiAL);
			bw.Write(dbiEND);
		}


		void WriteString(BinaryWriter bw, string str)
		{
			byte nullbyte = 0;
			byte []wrBytes = new byte[str.Length + 1];
			int i;
			for(i =0; i < str.Length; i++)
			{
//				byte WrByte;
//				WrByte = (byte)str[i];
//				bw.Write(WrByte);
				wrBytes[i] = (byte)str[i];
			}
			wrBytes[i] = nullbyte;
			bw.Write(wrBytes);
//			bw.Write(nullbyte);
		}


		public void WriteStrings(BinaryWriter bw)
		{
			WriteString(bw,dbiTitle);
			WriteString(bw,dbiAP);
		}


		public uint GetStructSize()
		{
			uint rtval = 0;

			// You can only use sizeof() in unsafe compiles
			// so we'll do it by hand...
			// each number is the number of bytes

			rtval += 2; //sizeof(dbiID);
			rtval += 2; //sizeof(dbiType);
			rtval += 2; //sizeof(dbiAW);
			rtval += 4; //sizeof(dbiGL);
			rtval += 4; //sizeof(dbiIL);
			rtval += 4; //sizeof(dbiAL);
			rtval += 4; //sizeof(dbiEND);
			rtval += 4; //sizeof(uint); // to simulate the (char *) in the old code (16 datatypes)
			rtval += 4; //sizeof(uint); // to simulate the (char *) in the old code (16 datatypes)

			return rtval;
		}


		public void Load(BinaryReader br)
		{
			ReadValues(br);
		}

		//		public void ReadStrings(BinaryReader br)
		//		{
		//			dbiTitle = ROFST_File.LoadString(br);
		//			dbiAP = ROFST_File.LoadString(br);
		//		}


		public void ReadValues(BinaryReader br)
		{
			dbiID = br.ReadUInt16();
			dbiType = br.ReadUInt16();
			dbiAW = br.ReadUInt16();
			dbiGL = br.ReadUInt32();
			dbiIL = br.ReadUInt32();
			dbiAL = br.ReadUInt32();
			dbiEND = br.ReadUInt32();
			uint tmp = br.ReadUInt32();
			uint tmp1 = br.ReadUInt32();
		}
	}



	// structure to hold information to add a new group
	public struct AddGroup
	{
		public uint grp_id;
		public uint grp_parid;
		public string grp_name;
		public short grp_type;
		public short grp_stat;
		public uint grp_offset;
		public int grp_pargrpidx;


		// These functions are needed to update values when
		// referenced from the ArrayLists (allNewGrps)
//		public void OR_grp_type(short val)
//		{
//			ushort gtype = (ushort)grp_type;
//			gtype |= (ushort)val;
//			grp_type = (short)gtype;
//		}
//
//
//		public void SET_grp_parid(uint val)
//		{
//			grp_parid = val;
//		}
//	
//	
//		public void SET_grp_pargrpidx(int val)
//		{
//			grp_pargrpidx = val;
//		}
//
//
//		public void SET_grp_stat(short val)
//		{
//			grp_stat = val;
//		}
//
//
//		public void SET_grp_offset(uint val)
//		{
//			grp_offset = val;
//		}
	}


	// structure to hold information to add a new RO
	public struct AddRO
	{
		public uint ro_id;
		public uint ro_parid;
		public string ro_num;
		public string ro_desc;
		public string ro_val;
		public short ro_type;
		public short ro_stat;
		public int ro_offset;
		public int ro_pargrpidx; // index into allNewGrps


		// These functions are needed to update values when
		// referenced from the ArrayLists (allNewRos)
//		public void SET_ro_pargrpidx(int val)
//		{
//			ro_pargrpidx = val;
//		}
//	
//	
//		public void SET_ro_parid(uint val)
//		{
//			ro_parid = val;
//		}
//
//
//		public void SET_ro_stat(short val)
//		{
//			ro_stat = val;
//		}
//
//
//		public void SET_ro_offset(int val)
//		{
//			ro_offset = val;
//		}
	}


	// structure to hold RO difference information
	public struct DiffRO
	{
		public uint id;
		public short type;
		public string desc;
		public string val;
		public uint oldoff; // index into old RO.FST file (approved)
	}


	// RO update info arrays.
	// one of these structures is created from each RO database
	// (Setpoints, Graphics, User Defined, etc.)
	public struct dbinew
	{
		public ArrayList aryAddedRO;
		public ArrayList aryAddedGroup;
		public ArrayList aryAddedDiff;
	}


	// structure to store file positions in the RO.FST file
	public struct locinc
	{
		public uint maxloc;
		public int inc;
	}


	/*
	** Structure of the graphics' values.
	** LocalGrfStats ( in ROFST_File class) is used to hold the current 
	** values "locally" in memory 
	*/
	public struct GrfStats 
	{
		public string PixFileName;
		public int PixFileTime;
		public short PixFileHeight;
		public short PixFileWidth;

		public void Clear()
		{
			PixFileName="";
			PixFileTime = 0;
			PixFileHeight = 0;
			PixFileWidth = 0;
		}

		public void Load(string instr)
		{
			short DeciPerRow = 120;
			short DeciPerCol = 60;
			StringBuilder sb = new StringBuilder();
			int i=0;
			bool nl = false;
			/*
			sscanf(retval,"%s\n%08lx\n%04x\n%04x",LocalGrfStats.PixFileName,
				&LocalGrfStats.PixFileTime,&LocalGrfStats.PixFileHeight,
				&LocalGrfStats.PixFileWidth);
			LocalGrfStats.PixFileHeight *= DeciPerRow;
			LocalGrfStats.PixFileWidth *= DeciPerCol;
			*/
			while (!nl && (instr[i]!= 0))
			{
				if (instr[i]=='\n')
					nl=true;
				else
					sb.Append(instr[i]);
				i++;
			}
			if (nl)
			{
				PixFileName = sb.ToString();
			}

			nl = false;
			sb.Remove(0,sb.Length);
			while (!nl && (instr[i]!=0))
			{
				if (instr[i]=='\n')
					nl=true;
				else
					sb.Append(instr[i]);
				i++;
			}
			PixFileTime = Convert.ToInt32(sb.ToString(),16);

			nl = false;
			sb.Remove(0,sb.Length);
			while (!nl && (instr[i]!=0))
			{
				if (instr[i]=='\n')
					nl=true;
				else
					sb.Append(instr[i]);
				i++;
			}
			PixFileHeight = Convert.ToInt16(sb.ToString(),16);
			PixFileHeight *= DeciPerRow;

			sb.Remove(0,sb.Length);
			while ((i < instr.Length) && (instr[i]!=0))
			{
				sb.Append(instr[i]);
				i++;
			}
			PixFileWidth = Convert.ToInt16(sb.ToString(),16);
			PixFileWidth *= DeciPerCol;
		}
	}


	public class ROFST_File
	{
		public enum ROTypes
		{
			None=0, Text=1, Table=2, Plot=4, Figure=8
		};

		private string [] UnitVariables =
			{
				"All of the following",//
				"Number",		// 1L; - 1, 2 etc.
				"Other Number",	// 2L; - 2, 1 etc.
				"Text",			// 3L; - One, Two etc. or Unit One, Unit Two
				"Other Text",	// 4L; - Two, One etc. or Unit Two, Unit One
				"ID",			// 5L; - 1Bw, 2Bw etc.
				"Other ID",		// 6L; - 2Bw, 1Bw etc.
				"Name",			// 7L; - Braidwood Unit 1
				"Other Name"	// 8L; - Braidwood Unit 2
			};
		private uint NumUnitVariables =9;


		private ArrayList [] allNewRos; // new ROs
		private ArrayList [] allDifferences; // changed ROs
		private ArrayList [] allNewGrps; // new Groups
		private ArrayList allLocations; // positions in RO.FST


		private int curTblIdx=0; // index into allxxxxx arraylists
		private uint NewID = 0xFFFF0000;
		private uint whereisgl = 0;
		private ushort maxaw=0;
		private ushort maxtype=0;
		private int lastLocationIdx = 0;

		public BinaryReader brROFst; // file handle into RO.FST
		public ArrayList DatabasesInfo; // Holds the current RO.FST data
		public ArrayList dbiNewInfo; // will hold new RO.FST data

		public short numDatabases; // number of databases in RO.FST
		public FST_FileHeader Header;
		//		public UserRunTime usrRunTime;
		public string DirPath;

		public string DefaultGraphicFileExt = "";

		// RO Graphics information
		GrfStats LocalGrfStats;

		// constructor loads ro.fst databases
		//		public ROFST(string path, UserRunTime urt)
		public ROFST_File(string path)
		{
			try
			{
				brROFst = new BinaryReader(File.Open(path,System.IO.FileMode.Open,System.IO.FileAccess.ReadWrite,FileShare.ReadWrite));
			}
			catch (Exception e)
			{
				MessageBox.Show("Open "+path+"\n\n" + e.Message,"RO.FST Error");
				return;
			}
			DirPath = path; // path of RO.FST file

			//			usrRunTime = urt;

			// Read the RO.FST header information
			Header = new FST_FileHeader();
			Header.Read(brROFst);
			brROFst.BaseStream.Seek(Header.dboffset,SeekOrigin.Begin);
			uint howbig = brROFst.ReadUInt32();
			short howmany = brROFst.ReadInt16();

			numDatabases = howmany;
			//			dbcount = numDatabases;
			DatabasesInfo = new ArrayList();
			dbiNewInfo = new ArrayList();

			// Load the database into the arrays
			for (int i=0;i<numDatabases;i++)
			{
				dbinew dbinewItem;
				ROFST_DbInfo tmpDBinfo;
				tmpDBinfo = new ROFST_DbInfo();
				tmpDBinfo.Load(brROFst);
				DatabasesInfo.Add(tmpDBinfo);
				dbinewItem = new dbinew();
				dbiNewInfo.Add(dbinewItem);
			}
			for (int i=0;i<numDatabases;i++)
			{
				// load the titles and accessory page ids
				//				((ROFST_DbInfo)DatabasesInfo[i]).ReadStrings(brROFst);
				((ROFST_DbInfo)DatabasesInfo[i]).dbiTitle = GetAsciiString(brROFst);
				((ROFST_DbInfo)DatabasesInfo[i]).dbiAP = GetAsciiString(brROFst);
			}
			// get the default graphic file extension
			GetDefaultGraphicFileExtension();
		}


		~ ROFST_File()
		{
			if (brROFst!=null)brROFst.Close();
		}

		public void Close()
		{
			if (brROFst!=null)brROFst.Close();
			brROFst=null;
		}
		
		private void GetDefaultGraphicFileExtension()
		{
			// get the full path to the VEPROMS.INI file
			string ApplicationPath = Application.StartupPath;
			string ApplPathUp = ApplicationPath.ToUpper();
			int idx = ApplPathUp.IndexOf("VE-PROMS.NET\\BIN");
			string PromsINI = ApplicationPath.Substring(0,idx);
			PromsINI += "VE-PROMS\\VEPROMS.INI";

			PrivateProfile extINI = new PrivateProfile(PromsINI);
			string defGrExt=extINI.Attr("Graphics", "defaultext");
			if (defGrExt == null || defGrExt == "") 
			{
				// check the ROAPP.INI in the RO directory 
				string ropath = extINI.Attr("RO Defaults","ROPATH");
				if (ropath == null || ropath == "") ropath = "..\\RO";
				string src = ropath + "\\ROAPP.INI";
				if (File.Exists(src))
				{
					PrivateProfile roappINI = new PrivateProfile(src);
					defGrExt = roappINI.Attr("ROApp","Extention");
				}
				if (defGrExt == null || defGrExt == "")
					defGrExt="TIF";
			}
			DefaultGraphicFileExt = defGrExt;
		}


		// Find the location of an RO value in the RO.FST file
		private uint FindRO(uint id, uint start, uint end)
		{
			uint retval=0;
			uint topid=0,botid=0,recs=0,siz=0;
			siz=8;   // c code:sizeof(topid)*2;
			recs=(end-start)/siz;
			uint guessr,guess=0,guessid=0;
			uint botr=0,topr=recs;
			//getID(end-siz,&topoff);
			brROFst.BaseStream.Seek(end-siz,SeekOrigin.Begin);
			topid=brROFst.ReadUInt32(); 

			if(id==topid)return end-siz; // Last record

			//getID(start,&botoff);
			brROFst.BaseStream.Seek(start,SeekOrigin.Begin);
			botid=brROFst.ReadUInt32();

			if(id==botid)return start; // First record

			if(id < botid || id > topid)return 0; // Beyond range
			while (topr > botr+1)
			{
				guessr=botr+((topr-botr)*(id-botid))/(topid-botid);

				if (guessr==botr) guessr=botr+1;
				else if (guessr==topr) guessr=topr-1;

				guess=start+guessr*siz;
				brROFst.BaseStream.Seek(guess,SeekOrigin.Begin);
				//getID(guess,&retval)
				guessid=brROFst.ReadUInt32();

				if(guessid==id)
				{
					break; // found, break out of while loop
				} 
				else if(guessid < id) 
				{
					botr=guessr;
					botid=guessid;
				} 
				else 
				{
					topr=guessr;
					topid=guessid;
				}
			}
			if(guessid==id)retval=guess;  // Any other record
			return retval;
		}

		private uint _getID(uint off,ref uint nxtoff)
		{
			uint retval = 0;
			if (off > 0)
			{
				brROFst.BaseStream.Seek(off,SeekOrigin.Begin);
				retval = brROFst.ReadUInt32();
				nxtoff = brROFst.ReadUInt32();
			}
			return retval;
		}

		// Find the location of an RO value in the RO.FST file
		private uint toolsFindRO(uint id, uint start, uint end)
		{
			uint retval=0;
			uint topid=0,botid=0,recs=0,siz=0;
			siz=8;   // c code:sizeof(topid)*2;
			recs=(end-start)/siz; //c code: (end-start)>>3
			uint guessr,guess=0,guessid=0;
			uint botr=0,topr=recs;
			uint topoff =0, botoff=0;
			//getID(end-siz,&topoff);
			topid=_getID(end-siz,ref topoff);
			if(id==topid)return topoff; // Last record

			//getID(start,&botoff);
			botid=_getID(start,ref botoff);
			if(id==botid)return botoff; // First record

			if(id < botid || id > topid)return 0; // Beyond range
			while (topr > botr+1)
			{
				guessr=botr+((topr-botr)/2);

				if (guessr==botr) guessr++;
				else if (guessr==topr) guessr--;

				//getID(guess,&retval)
				guessid=_getID(start+guessr*siz,ref retval);

				if(guessid==id)
				{
					break; // found, break out of while loop
				} 
				else if(guessid < id) 
				{
					botr=guessr;
					botid=guessid;
				} 
				else 
				{
					topr=guessr;
					topid=guessid;
				}
			}
			if (guessid == id) return retval;
			return 0;
		}


		// return the type of the given RO
		private ROTypes FindROType(uint rec, uint start, uint end)
		{
			short retval=0;
			byte[] tbyte= new byte[100];
			uint fnd;
			if((fnd=FindRO(rec,start,end))>0)
			{
				uint offset=brROFst.ReadUInt32();
				brROFst.BaseStream.Seek((long)offset,SeekOrigin.Begin);
				uint guessid=brROFst.ReadUInt32();
				uint parid=brROFst.ReadUInt32();
				short count=brROFst.ReadInt16();
				retval=brROFst.ReadInt16(); // RO Type
			}
			return (ROTypes)retval;
		}


		// Get the RO Type from the ROID
		public ROTypes GetROType(string ROID)
		{
			// break up ROID into table and record
			ushort tbl = System.Convert.ToUInt16(ROID.Substring(0,4),16);
			uint rec = System.Convert.ToUInt32(ROID.Substring(4,8),16);
			ushort opt=0;
			if (ROID.Length>12) opt = System.Convert.ToUInt16(ROID.Substring(8,ROID.Length-12),16);
			if (tbl==0xFFFF) 
				return ROTypes.None;    // 0xFFFF is ro from proc.ini file
			int num=0;
			for (int i=0; i < numDatabases && ((ROFST_DbInfo)DatabasesInfo[i]).dbiID!=tbl;i++) num++;
			if (num < numDatabases)
			{
				return FindROType(rec,((ROFST_DbInfo)DatabasesInfo[num]).dbiIL,((ROFST_DbInfo)DatabasesInfo[num]).dbiAL);
			}
			return ROTypes.None;
		}

		
		private string FindROText(uint id, uint start,uint end)
		{
			string retval=null;
			uint fnd;
			if((fnd=FindRO(id,start,end))>0)
			{
				uint offset=brROFst.ReadUInt32();
				brROFst.BaseStream.Seek((long)offset,SeekOrigin.Begin);
				uint guessid=brROFst.ReadUInt32();
				uint parid=brROFst.ReadUInt32();
				short count=brROFst.ReadInt16();
				ushort typ=brROFst.ReadUInt16();
				if (count<0)retval=GetAsciiString(brROFst);
			}
			return retval;
		}


		// return the RO defined in the PROC.INI file
		// (master / slave logic)
		private string roUnitValue(uint rec)
		{
			string buff="";
			PrivateProfile proc_ini = new PrivateProfile("proc.ini");
			for(int i=0; i < NumUnitVariables; i++)
			{
				if((i+1) == rec)
				{
					buff = proc_ini.Attr("Unit",UnitVariables[i+1]);
					if (buff == null || buff.Equals(""))
						buff = "{"+ UnitVariables[i+1] + "}";
					break; // break out of for loop
				}
			}
			return buff;
		}


		//
		// this function will take the ro text value and see if
		// a macro has been used.  If so it will perform the macro
		// operation on the substring in the text value
		//
		// right now the only macro is @HSP(), where every space between
		// the "(" and ")" will be replaced with a hardspace
		//
		public string processMacros(string rostring)
		{
			StringBuilder bldstr = new StringBuilder();
			int cptr = -1;
			int stptr = 0;
			if( rostring == null ) return rostring;
			while( (cptr = rostring.IndexOf("@HSP(",stptr)) != -1 ) 
			{
				bldstr.Append(rostring.Substring(0,cptr-stptr));
				int cptr2 = rostring.IndexOf(")",cptr);
				if (cptr2 != -1)
				{
					int numspaces = cptr2 - cptr - 5;
					for (int i=0; i< numspaces; i++)
						bldstr.Append("\xFF");
					stptr = cptr2 + 1;
				}
				else
				{
					bldstr.Append(rostring.Substring(cptr,5));
					stptr += 5;
				}
			}
			if (stptr < rostring.Length)
				bldstr.Append(rostring.Substring(stptr));
			return bldstr.ToString();
		}



		private string processROString(string rostr, short menuitem)
		{
			string newstr="";
			strList2 strlst2 = new strList2(rostr);
			strList strlst = new strList(strlst2.toText());
			newstr = strlst.toText((int)menuitem);
			
			newstr = newstr.Replace("\\n","\n");
			newstr = processMacros(newstr);
			return newstr;
		}


		public string GetRO(string ROID)
		{
			return _GetRO(ROID,1);
		}

		public string GetEntireRO(string ROID)
		{
			return _GetRO(ROID,0);
		}

		public string GetRO(string ROID, string ProcNum)
		{
			ROProcessTools.ProcedureNumber = ProcNum;
			return GetRO(ROID);
		}

		public string GetEntireRO(string ROID, string ProcNum)
		{
			ROProcessTools.ProcedureNumber = ProcNum;
			return GetEntireRO(ROID);
		}

		// Get the RO value from the ROID
		private string _GetRO(string ROID_in, short procopt)
		{
			string ROID = ROID_in;
			if (ROID.Length == 12)
				ROID = ROID + "0000";
			ushort tbl = System.Convert.ToUInt16(ROID.Substring(0,4),16);
			uint rec = System.Convert.ToUInt32(ROID.Substring(4,8),16);
			ushort opt=0;
			// clear graphics info
			LocalGrfStats.Clear();
			opt = System.Convert.ToUInt16(ROID.Substring(12,ROID.Length-12),16);

			if (tbl==0xFFFF)
				return roUnitValue(rec); // RO in PROC.INI file

			int num=0;
			for(int i=0; i < numDatabases && ((ROFST_DbInfo)DatabasesInfo[i]).dbiID!=tbl;i++) num++;

			string retval = null;
			if(num < numDatabases)
			{
				retval=FindROText(rec,((ROFST_DbInfo)DatabasesInfo[num]).dbiIL,((ROFST_DbInfo)DatabasesInfo[num]).dbiAL);
			}
			if ((retval!=null&&retval!="") && (GetROType(ROID)==ROTypes.Figure))
			{
				// load graphic stats
				StringBuilder sbtmp = new StringBuilder();
				LocalGrfStats.Load(retval);
				sbtmp.Append(LocalGrfStats.PixFileName);
				sbtmp.Append("\n");
				sbtmp.Append(LocalGrfStats.PixFileTime.ToString("x8"));
				sbtmp.Append("\n");
				sbtmp.Append(LocalGrfStats.PixFileHeight.ToString("x4"));
				sbtmp.Append("\n");
				sbtmp.Append(LocalGrfStats.PixFileWidth.ToString("x4"));
				retval = sbtmp.ToString();
				
			}
			if(retval!=null && retval.Length>0 && !(GetROType(ROID)==ROTypes.Figure))
			{
				retval = retval.Replace("\r","");
				if (procopt!=0) 
				{
					string newstr = processROString(retval,(short)opt);
					if (newstr!=null && !newstr.Equals(""))
						retval = newstr;
				}
			}
			return retval;
		}


		// Get the RO's parent ID
		private uint GetParent(uint rec, uint start,uint end,ref uint offset)
		{
			uint parid=0;
			uint fnd;
			if((fnd=FindRO(rec,start,end))>0)
			{
				offset=brROFst.ReadUInt32();
				brROFst.BaseStream.Seek((long)offset,SeekOrigin.Begin);
				uint guessid=brROFst.ReadUInt32();
				parid=brROFst.ReadUInt32();
			}
			return parid;
		}
 

		// Get the RO's parent ID from the ROID
		public uint GetROParent(string ROID)
		{
			ushort tbl = Convert.ToUInt16(ROID.Substring(0,4),16);
			uint  rec = Convert.ToUInt32(ROID.Substring(4,8),16);
			uint retval=0;
			int indx=0;
			for(int i=0; i < numDatabases && ((ROFST_DbInfo)DatabasesInfo[i]).dbiID!=tbl; i++) indx++; // position to the RO database

			if(indx < numDatabases)
			{
				uint offset=0;
				retval=GetParent(rec,((ROFST_DbInfo)DatabasesInfo[indx]).dbiIL,((ROFST_DbInfo)DatabasesInfo[indx]).dbiAL,ref offset);
			}
			return retval;
		}


		// Get the group title
		private string GetDescription(ushort tbl, uint rec)
		{
			string retval=null;
			if(tbl==0xFFFF)
			{
				if(rec == 0)
				{
					retval="Unit Information";
				} 
				
				else if (rec <= NumUnitVariables)
				{
					int irec = (int)rec;
					retval=UnitVariables[irec];
				}				
			} 
			else 
			{
				int indx=0;
				for(int i=0; i < numDatabases && ((ROFST_DbInfo)DatabasesInfo[i]).dbiID!=tbl; i++) indx++;

				if(indx < numDatabases)
				{
					uint id;
					brROFst.BaseStream.Seek(((ROFST_DbInfo)DatabasesInfo[indx]).dbiGL,SeekOrigin.Begin);
					id = brROFst.ReadUInt32();

					if((rec==0) || rec==id)
					{
						retval=((ROFST_DbInfo)DatabasesInfo[indx]).dbiTitle;
					} 
					else 
					{
						uint offset=0;
						uint parid = this.GetParent(rec,((ROFST_DbInfo)DatabasesInfo[indx]).dbiIL,((ROFST_DbInfo)DatabasesInfo[indx]).dbiAL,ref offset);
						if(parid!=0)
						{
							uint t1 = FindRO(parid,((ROFST_DbInfo)DatabasesInfo[indx]).dbiIL,((ROFST_DbInfo)DatabasesInfo[indx]).dbiAL);
							t1 = brROFst.ReadUInt32();
							uint guessid;
							brROFst.BaseStream.Seek(t1,SeekOrigin.Begin);
							guessid = brROFst.ReadUInt32();
							parid = brROFst.ReadUInt32();
							short count;
							count = brROFst.ReadInt16();
							for(int i=0; i < count && retval == null; i++)
							{
								uint off;
								off=brROFst.ReadUInt32();
								ushort typ;
								typ=brROFst.ReadUInt16();
								if(off==offset)
								{
									retval = GetAsciiString(brROFst);
								} 
								else 
								{
									SkipString(brROFst);
								}
							}
						}
					}
				}
			}
			return retval;
		}


		// Get the group title by ROID
		public string GetRODescription(string ROID)
		{
			ushort tbl = Convert.ToUInt16(ROID.Substring(0,4),16);
			uint rec = Convert.ToUInt32(ROID.Substring(4,8),16);
			return (GetDescription(tbl,rec));
		}

		
		// get the Accessory Page ID
		string FindROAccID(uint id, uint start, uint end)
		{
			// Returns allocated space
			string retval=null;
			if(FindRO(id,start,end)!=0)
			{
				uint offset = brROFst.ReadUInt32();
				brROFst.BaseStream.Seek(offset,SeekOrigin.Begin);
				uint guessid = brROFst.ReadUInt32();
				uint parid = brROFst.ReadUInt32();
				short count = brROFst.ReadInt16();
				ushort ltyp = brROFst.ReadUInt16();
				SkipString(brROFst);
				retval = GetAsciiString(brROFst);
			}
			return retval;
		}


		private string roUnitAccID(uint rec)
		{
			string rotbuf="";
			for(int i=0; i < NumUnitVariables; i++)
			{
				if((i+1L)==rec)
				{
					rotbuf = "U-"+UnitVariables[i+1];
					break; // break out of for loop
				}
			}
			return rotbuf;
		}


		// Get accessory page ID via the table and record
		// also include unit specs from ini file
		private string GetAccID(ushort tbl, uint rec)
		{
			string retval=null;

			if (tbl==0xFFFF)
			{
				return roUnitAccID(rec); // PROC.INI ro
			}
			int indx=0;
			for(int i=0; i < numDatabases && ((ROFST_DbInfo)DatabasesInfo[i]).dbiID!=tbl; i++)indx++;

			if(indx < numDatabases)
			{
				retval=FindROAccID(rec,((ROFST_DbInfo)DatabasesInfo[indx]).dbiIL,((ROFST_DbInfo)DatabasesInfo[indx]).dbiAL);
				if (retval ==  null)
					retval="";	// RO not found, has ? in procedures
				else
					retval = ((ROFST_DbInfo)DatabasesInfo[indx]).dbiAP + "-" + retval;
			}
			return retval;
		}


		// Get the accessory page ID from the ROID
		// includes unit specs from ini file
		public string RoidToAccId(string ROID)
		{
			ushort tbl = Convert.ToUInt16(ROID.Substring(0,4),16);
			uint rec = Convert.ToUInt32(ROID.Substring(4,8),16);
			if(tbl != 0)
				return GetAccID(tbl,rec);
			return null;
		}


		// Get the accessory page ID
		private string GetAppID(ushort tbl)
		{
			string retval = null;
			if (tbl==0xFFFF)
			{
				return "U"; // PROC.INI ro
			}
			int indx=0;
			for(int i=0; i < numDatabases && ((ROFST_DbInfo)DatabasesInfo[i]).dbiID!=tbl; i++) indx++;

			if(indx < numDatabases) 
				return ((ROFST_DbInfo)DatabasesInfo[indx]).dbiAP;
			return retval;
		}


		// Get the accessory page ID from the ROID
		public string GetROAppID(string ROID)
		{
			ushort tbl = Convert.ToUInt16(ROID.Substring(0,4),16);
			uint rec = Convert.ToUInt32(ROID.Substring(4,8),16);
			return GetAppID(tbl);
		}


		public uint FindROFromTblRec(ushort tbl, uint id)
		{
			if(tbl==0xFFFF)
			{
				//PROC.INI ro
				if (id < NumUnitVariables) return 1;
				return 0;
			}
			int indx=0;
			for(int i=0; i < numDatabases && ((ROFST_DbInfo)DatabasesInfo[i]).dbiID!=tbl;i++) indx++;
			if (indx==numDatabases) return 0;
			return FindRO(id,((ROFST_DbInfo)DatabasesInfo[indx]).dbiIL,((ROFST_DbInfo)DatabasesInfo[indx]).dbiAL);
		}


		public bool ROExists(string ROID)
		{
			ushort tbl = Convert.ToUInt16(ROID.Substring(0,4),16);
			uint rec = Convert.ToUInt32(ROID.Substring(4,8),16);
			uint off = FindROFromTblRec(tbl,rec);
			return (off!=0);
		}


		public static void SkipString(BinaryReader br)
		{
			byte []input = {0};
			do
			{
				try
				{
					input[0] = br.ReadByte();
				}
				catch (System.IO.EndOfStreamException)
				{
					input[0] = 0;
				}
			}while (input[0] != 0);
			return;
		}


		public static string GetAsciiString(BinaryReader br, int len)
		{
			StringBuilder inpstr = new StringBuilder();
			byte input = 0;
			int cnt = 0;
			do
			{
				try
				{
					input = br.ReadByte();
					inpstr.Append(Convert.ToChar(input));
				}
				catch (System.IO.EndOfStreamException)
				{
					input = 0;
					while (cnt < len)
					{
						inpstr.Append(Convert.ToChar(input));
						cnt++;
					}
				}
				cnt++;
			}while (cnt < len);

			return (inpstr.ToString());
		}


		public static string GetAsciiString(BinaryReader br)
		{
			StringBuilder inpstr = new StringBuilder();
			byte input = 0;
			do
			{
				try
				{
					input = br.ReadByte();
					if (input > 0)
					{
						inpstr.Append(Convert.ToChar(input));
					}
				}
				catch (System.IO.EndOfStreamException)
				{
					input = 0;
				}
			}while (input != 0);
			return (inpstr.ToString());
		}


		// return the index into the the DatabasesInfo array
		private short GetTable(ushort tblid)
		{
			short found = -1;
			for(short i = 0; (i < numDatabases) && (found == -1); i++)
			{
				if (tblid == ((ROFST_DbInfo)DatabasesInfo[i]).dbiID) 
					found = i;
			}

			if (found == -1)
			{
				// add a new item to the list
				found = numDatabases;
				ROFST_DbInfo tmpDbInfo = new ROFST_DbInfo();
				tmpDbInfo.dbiID = tblid;
				DatabasesInfo.Add(tmpDbInfo);
				dbinew dbinewItem;
				dbinewItem = new dbinew();
				dbiNewInfo.Add(dbinewItem);
				numDatabases++;
			}
			return found;
		}
		

		// Add a new RO record to the arraylist of allNewRos
		private int AddNewRO(uint id,string num,string desc,string val,short type,uint grpn)
		{
			int rtval;
			AddRO tmpAddRO = new AddRO();
			tmpAddRO.ro_id = id;
			tmpAddRO.ro_num = num;
			tmpAddRO.ro_desc = desc;
			tmpAddRO.ro_val = val;
			tmpAddRO.ro_type = type;
			tmpAddRO.ro_parid = grpn;
			(allNewRos[curTblIdx]).Add(tmpAddRO);
			rtval = (allNewRos[curTblIdx]).Count -1;
			return rtval;
		}


		private int AddNewRO(string num,string desc,string val,short type,uint grpn)
		{
			return AddNewRO(NewID++, num, desc, val, type, grpn);
		}


		// Add a new Group record to the arraylist of allNewGrps
		private int AddNewGroup(uint id,string grpname,short type)
		{
			int rtval;
			AddGroup tmpAddGroup = new AddGroup();
			tmpAddGroup.grp_id = id;
			tmpAddGroup.grp_name = grpname;
			tmpAddGroup.grp_type = type;
			(allNewGrps[curTblIdx]).Add(tmpAddGroup);
			rtval = (allNewGrps[curTblIdx]).Count -1;
			return rtval;
		}


		private int AddNewGroup(string grpname,short type)
		{
			return AddNewGroup(NewID++,grpname,type);
		}


		// Return the index into the allNewGrps arraylist
		private int FindNewGroup(uint rec)
		{
			int rtval = -1;
			for (int i=0; i < (allNewGrps[curTblIdx]).Count && rtval == -1; i++)
			{
				if (((AddGroup)(allNewGrps[curTblIdx])[i]).grp_id == rec)
					rtval = i;
			}
			return rtval;
		}


		private int connectToNewGroupfRO(int lastaddROIDX,uint rec,uint parid,string desc)
		{
			int newgrpIDX;
			// position to the correct RO group
			if((newgrpIDX=FindNewGroup(rec))== -1)
			{
				// group not found, add a new one
				newgrpIDX=AddNewGroup(rec,desc,0);
			}
			//update parent info
			AddRO taddro = (AddRO)(allNewRos[curTblIdx][lastaddROIDX]);
			taddro.ro_pargrpidx = newgrpIDX;
			taddro.ro_parid = rec;
			allNewRos[curTblIdx].RemoveAt(lastaddROIDX);
			allNewRos[curTblIdx].Insert(lastaddROIDX,taddro);

			//update group info
			AddGroup taddgroup = (AddGroup)(allNewGrps[curTblIdx][newgrpIDX]);
			taddgroup.grp_type |= ((AddRO)(allNewRos[curTblIdx])[lastaddROIDX]).ro_type;
			taddgroup.grp_parid = parid;
			allNewGrps[curTblIdx].RemoveAt(newgrpIDX);
			allNewGrps[curTblIdx].Insert(newgrpIDX,taddgroup);
			return newgrpIDX;
		}


		private int connectToNewGroup(int lastaddgrpIDX,uint rec,uint parid,string desc)
		{
			// update group info
			int newgrpIDX;
			// position to the correct RO group
			if((newgrpIDX=FindNewGroup(rec))== -1)
			{
				// group not found, add a new one
				newgrpIDX=AddNewGroup(rec,desc,0);
			}
			AddGroup taddgroup = (AddGroup)(allNewGrps[curTblIdx][lastaddgrpIDX]);
			taddgroup.grp_pargrpidx = newgrpIDX;
			taddgroup.grp_parid = rec;
			allNewGrps[curTblIdx].RemoveAt(lastaddgrpIDX);
			allNewGrps[curTblIdx].Insert(lastaddgrpIDX,taddgroup);

			taddgroup = (AddGroup)(allNewGrps[curTblIdx][newgrpIDX]);
			taddgroup.grp_type |= ((AddGroup)(allNewGrps[curTblIdx])[lastaddgrpIDX]).grp_type;
			taddgroup.grp_parid = parid;
			allNewGrps[curTblIdx].RemoveAt(newgrpIDX);
			allNewGrps[curTblIdx].Insert(newgrpIDX,taddgroup);
			return newgrpIDX;
		}


		private void addDifference(uint id,string val,string desc,short type,uint offset)
		{
			DiffRO tmpDiff  = new DiffRO();
			tmpDiff.id=id;
			tmpDiff.type=type;
			tmpDiff.desc=desc;
			tmpDiff.val=val;
			tmpDiff.oldoff=offset;
			allDifferences[curTblIdx].Add(tmpDiff);
		}


		private short ReadShort(BinaryReader br)
		{
			short rtnval = -1;
			try
			{
				rtnval = br.ReadInt16();
			}
			catch (Exception)
			{
				rtnval = -1;
			}
			return rtnval;
		}

/*
 * Read in the ROFST Update file.  Put the changes in array lists
 * to that will be used to organize and merge the differences into
 * the target RO.FST file (usually in the Approved directory). For
 * array lists, use the number of databases from the working draft
 * (there may be new databases created since last approval)
 */
		public void processAppUpdFile(string sAppUpdFile, short nDB)
		{
			BinaryReader brAppUpd;
			string num, desc, val;
			short typ, inch;
			uint rec;
			ushort tbl;
			uint parid;
			int LastAddedROIDX=-1, LastAddedGrpIDX=-1;
			curTblIdx = 0; // initialize Current Table Index
			allNewRos = (ArrayList [])new ArrayList[nDB];
			allNewGrps = (ArrayList [])new ArrayList[nDB];
			allDifferences = (ArrayList [])new ArrayList[nDB];
			allLocations = new ArrayList();

			for (int i=0; i<nDB; i++)
			{
				allNewRos[i] = new ArrayList();
				allNewGrps[i] = new ArrayList();
				allDifferences[i] = new ArrayList();
			}
			// Initialize the allLocations array
			locinc tmplocinc = new locinc();
			tmplocinc.inc = 0;
			tmplocinc.maxloc = 0xFFFFFFFF;
			allLocations.Add(tmplocinc);

			// Open the AppUpd file and read in the RO.FST changes
			try
			{
				brAppUpd = new BinaryReader(File.Open(sAppUpdFile,System.IO.FileMode.Open,System.IO.FileAccess.Read,FileShare.Read));
			}
			catch (Exception e)
			{
				MessageBox.Show("Open "+sAppUpdFile+"\n\n" + e.Message,"Approval Error");
				return;
			}

			while ((inch = ReadShort(brAppUpd)) != -1)
			{
				if (inch == 0 || inch == 1)
				{
					// Read the ROID for the Table and Record Number
					tbl = brAppUpd.ReadUInt16(); // table number
					rec = brAppUpd.ReadUInt32(); // RO Record number

					// Position to the correct RO database
					// ex. Setpoint, Graphics, User Defined, etc.
					curTblIdx = GetTable(tbl);

					// Read in the RO FST record information
					parid = brAppUpd.ReadUInt32(); // Parid
					typ = brAppUpd.ReadInt16();	// type
					desc = GetAsciiString(brAppUpd); // description
					val = GetAsciiString(brAppUpd); // value
					num = GetAsciiString(brAppUpd); // acc ID
					if (inch==0) 
						// New RO FST record
						LastAddedROIDX = AddNewRO(rec,num,desc,val,typ,parid);
					else
					{
						// Find existing RO FST record in Approved RO.FST
						uint start = ((ROFST_DbInfo)DatabasesInfo[curTblIdx]).dbiIL;
						uint end = ((ROFST_DbInfo)DatabasesInfo[curTblIdx]).dbiAL;
						//NOTE: the FindRO() used in the old rofstupd.c code
						// had some differences, but we will try the following
						// version of this function since its logic appears in 
						// other areas of VE-PROMS code.
						uint OldOff = toolsFindRO(rec,start,end);
						// Save the differences
						addDifference(rec,val,desc,typ,OldOff);
					}
				} 
				else
				{
					rec = brAppUpd.ReadUInt32(); // RO Record number
					parid = brAppUpd.ReadUInt32();
					desc = GetAsciiString(brAppUpd);
					if (parid == 0)
					{
						string apid= GetAsciiString(brAppUpd);
						((ROFST_DbInfo)DatabasesInfo[curTblIdx]).dbiTitle = desc;
						((ROFST_DbInfo)DatabasesInfo[curTblIdx]).dbiAP = apid;
					}
					if (LastAddedROIDX != -1)
						// update value in allNewRos
						LastAddedGrpIDX = connectToNewGroupfRO(LastAddedROIDX,rec,parid,desc);
					else
						// update value in allNewGrps
						LastAddedGrpIDX = connectToNewGroup(LastAddedGrpIDX,rec,parid,desc);
					LastAddedROIDX = -1;
				}
			}
			brAppUpd.Close();
		}


		// format strings used in the RO.FST menus that are displayed
		// in the VE-PROMS Editor
		private void placeText(StringBuilder buff,ushort doff,ushort wid,string instr)
		{
			ushort siznum=0;
			string str = instr;
			int idx =0;
			if(doff != 0)
			{
				str = str.TrimStart(' '); // skip leading spaces
				ushort l=(ushort)str.Length;
				string numandsigns = "0123456789+-";
				while (numandsigns.IndexOf(str[idx])!=-1)
				{
					siznum++;
					idx++;
				}
				if(siznum==0)
				{// doff is calculated to keep the values positive
					doff=((ushort)(siznum+(wid-l)/2)); // if no digits - center
				} 
				else if(((doff-siznum)+l) > wid)
				{
					doff=(ushort)(wid-siznum-l);
				}
				while(wid > 0 && siznum < doff)
				{ // add spaces if necessary for alignment
					buff.Append(' ');
					siznum++;
					wid--;
				}
			}
			idx = 0;
			while(wid > 0)
			{ // output the value or spaces to the specified width
				if (idx < str.Length && str[idx] != 0 && str[idx] != '\n')
					buff.Append(str[idx]);
				else
					buff.Append(" ");
				wid--;
				idx++;
			}
		}


		private string getMenuString(short type,string val,string desc)
		{
			StringBuilder buff = new StringBuilder();
			switch(type)
			{
				case 1:
					placeText(buff,5,17,val);
					buff.Append(" ");
					break;
				case 2:
					placeText(buff,5,17,"(Table)");
					buff.Append(" ");
					break;
				case 4:
					placeText(buff,5,17,"(Graph)");
					buff.Append(" ");
					break;
			}
			buff.Append(desc);
			return buff.ToString();
		}


		public void WriteString(BinaryWriter NewFSTFile, string str,bool AddNull)
		{
			int n=str.Length;
			if (AddNull) 
				n++;
			byte [] b = new byte[n];
			for(int i =0; i < str.Length; i++)
			{
				b[i] = (byte)str[i];
			}
			NewFSTFile.Write(b,0,n);
		}


		public uint saveNewMenuEntries(BinaryWriter NewFSTFile,uint id)
		{
			long start=NewFSTFile.Seek(0,System.IO.SeekOrigin.Current);
			int numNewRos = (allNewRos[curTblIdx]).Count;
			int cntNewRos = 0;
			while (cntNewRos < numNewRos)
			{
				// Skip to the one to be saved
				while ((cntNewRos < numNewRos) &&
					(((AddRO)(allNewRos[curTblIdx])[cntNewRos]).ro_stat != 1) &&
					((AddRO)(allNewRos[curTblIdx])[cntNewRos]).ro_parid != id)
					cntNewRos++;
				// If one exists, save it and skip to the next one
				if (cntNewRos < numNewRos)
				{
					AddRO taddro = (AddRO)(allNewRos[curTblIdx][cntNewRos]);
					taddro.ro_stat = 2;
					allNewRos[curTblIdx].RemoveAt(cntNewRos);
					allNewRos[curTblIdx].Insert(cntNewRos,taddro);
					// Save the offset
					NewFSTFile.Write(((AddRO)(allNewRos[curTblIdx])[cntNewRos]).ro_offset);
					// Save the type
					NewFSTFile.Write(((AddRO)(allNewRos[curTblIdx])[cntNewRos]).ro_type);
					// Get the menu string
					string mnustr = getMenuString(((AddRO)(allNewRos[curTblIdx])[cntNewRos]).ro_type,((AddRO)(allNewRos[curTblIdx])[cntNewRos]).ro_val,((AddRO)(allNewRos[curTblIdx])[cntNewRos]).ro_desc);
					// Save menu entry
					WriteString(NewFSTFile,mnustr,true);
					cntNewRos++;
				}
			}
			int cntNewGrps = 0;
			int numNewGrps = (allNewGrps[curTblIdx]).Count;
			while (cntNewGrps < numNewGrps)
			{
				// Skip to the one to be saved
				while ((cntNewGrps < numNewGrps) &&
					(((AddGroup)(allNewGrps[curTblIdx])[cntNewGrps]).grp_stat != 1 ||
					((AddGroup)(allNewGrps[curTblIdx])[cntNewGrps]).grp_parid != id))
					cntNewGrps++;
				// If one exists, save it and skip to the next one
				if(cntNewGrps < numNewGrps)
				{
					// Change stat to menu entry saved
					AddGroup taddgroup = (AddGroup)(allNewGrps[curTblIdx][cntNewGrps]);
					taddgroup.grp_stat = 2;
					allNewGrps[curTblIdx].RemoveAt(cntNewGrps);
					allNewGrps[curTblIdx].Insert(cntNewGrps,taddgroup);
					// Save the offset
					NewFSTFile.Write(((AddGroup)(allNewGrps[curTblIdx])[cntNewGrps]).grp_offset);
					// Save the type
					NewFSTFile.Write(((AddGroup)(allNewGrps[curTblIdx])[cntNewGrps]).grp_type);
					// Save menu entry
					WriteString(NewFSTFile,((AddGroup)(allNewGrps[curTblIdx])[cntNewGrps]).grp_name,true);
					cntNewGrps++;
				}
			}
			return ((uint)((NewFSTFile.Seek(0,System.IO.SeekOrigin.Current)) - start));
		}



		public void AddLocation(uint location,int inc)
		{
			int t_inc = ((locinc)allLocations[lastLocationIdx]).inc;
			if (location == ((locinc)allLocations[lastLocationIdx]).maxloc)
			{
				// update the current record
				// - need to remove current item, then insert new item.
				locinc tmploc = (locinc)(allLocations[lastLocationIdx]);
				tmploc.inc += inc;
				allLocations.RemoveAt(lastLocationIdx);
				allLocations.Insert(lastLocationIdx,tmploc);
			}
			else
			{
				locinc tmploc = (locinc)(allLocations[lastLocationIdx]);
				locinc newloc = new locinc();
				// new item gets last item's maxloc
				newloc.maxloc = tmploc.maxloc;
				newloc.inc = tmploc.inc + inc;
				// update the current record
				// - need to remove current item, then insert new item.
				tmploc.maxloc = location; //update maxloc
				allLocations.RemoveAt(lastLocationIdx);
				allLocations.Insert(lastLocationIdx,tmploc);
				// append the new item
				allLocations.Add(newloc);
				lastLocationIdx++;
			}
		}


		public short saveAdditions(BinaryWriter NewFSTFile,uint parid,uint start)
		{
			// Initialize count of added entries
			short count = 0;
			// Initialize count of added bytes
			int bytes = 0;

			int numgroups = (allNewGrps[curTblIdx]).Count;
			int grpcnt = 0;
			while (grpcnt < numgroups)
			{
				if (((AddGroup)(allNewGrps[curTblIdx])[grpcnt]).grp_stat == 0 && ((AddGroup)(allNewGrps[curTblIdx])[grpcnt]).grp_parid == parid)
				{
					// Save new group
					// Change stat to inprogress
					AddGroup taddgroup = (AddGroup)(allNewGrps[curTblIdx][grpcnt]);
					taddgroup.grp_stat = 99;
					allNewGrps[curTblIdx].RemoveAt(grpcnt);
					allNewGrps[curTblIdx].Insert(grpcnt,taddgroup);
					count++;
					// Save any children
					short howmany = saveAdditions(NewFSTFile,((AddGroup)(allNewGrps[curTblIdx])[grpcnt]).grp_id,start);

					// Remember the offset
					taddgroup = (AddGroup)(allNewGrps[curTblIdx][grpcnt]);
					taddgroup.grp_offset = (uint)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current); // ftell(ofile)
					allNewGrps[curTblIdx].RemoveAt(grpcnt);
					allNewGrps[curTblIdx].Insert(grpcnt,taddgroup);

					// Save ID
					if (((AddGroup)(allNewGrps[curTblIdx])[grpcnt]).grp_parid == 0)
					{
						whereisgl = (uint)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current);
					}
					NewFSTFile.Write(((AddGroup)(allNewGrps[curTblIdx])[grpcnt]).grp_id);
					// Save the Parent ID
					NewFSTFile.Write(((AddGroup)(allNewGrps[curTblIdx])[grpcnt]).grp_parid);
					// save howmany
					NewFSTFile.Write(howmany);
					// save children
					saveNewMenuEntries(NewFSTFile,((AddGroup)(allNewGrps[curTblIdx])[grpcnt]).grp_id);
					taddgroup = (AddGroup)(allNewGrps[curTblIdx][grpcnt]);
					taddgroup.grp_stat = 1;
					allNewGrps[curTblIdx].RemoveAt(grpcnt);
					allNewGrps[curTblIdx].Insert(grpcnt,taddgroup);
					bytes += (int)(NewFSTFile.Seek(0,System.IO.SeekOrigin.Current) - ((AddGroup)(allNewGrps[curTblIdx])[grpcnt]).grp_offset);
				}
				grpcnt++;
			}
			// Second check new ROs
			int rocnt = 0;
			int numros = (allNewRos[curTblIdx]).Count;
			while (rocnt < numros)
			{
				// save new RO
				if (((AddRO)(allNewRos[curTblIdx])[rocnt]).ro_stat == 0 &&
					((AddRO)(allNewRos[curTblIdx])[rocnt]).ro_parid == parid)
				{
					// change the stat
					AddRO taddro = (AddRO)(allNewRos[curTblIdx][rocnt]);
					taddro.ro_stat = 1;
					// Increment count
					count++;
					taddro.ro_offset = (int)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current);
					allNewRos[curTblIdx].RemoveAt(rocnt);
					allNewRos[curTblIdx].Insert(rocnt,taddro);
					// Save the ID
					NewFSTFile.Write(((AddRO)(allNewRos[curTblIdx])[rocnt]).ro_id);
					// Save the Parent ID
					NewFSTFile.Write(((AddRO)(allNewRos[curTblIdx])[rocnt]).ro_parid);
					// Save -1
					NewFSTFile.Write((short)-1);
					// Save type
					NewFSTFile.Write(((AddRO)(allNewRos[curTblIdx])[rocnt]).ro_type);
					maxtype |= (ushort)((AddRO)(allNewRos[curTblIdx])[rocnt]).ro_type;
					// Save value
					WriteString(NewFSTFile,((AddRO)(allNewRos[curTblIdx])[rocnt]).ro_val,true);
					// Save accid
					short aw = (short)((AddRO)(allNewRos[curTblIdx])[rocnt]).ro_num.Length;
					if (aw > maxaw) maxaw = (ushort)aw;
					WriteString(NewFSTFile,((AddRO)(allNewRos[curTblIdx])[rocnt]).ro_num,true);
					// Calculate the number of extra bytes output
					bytes+=(int)(NewFSTFile.Seek(0,System.IO.SeekOrigin.Current) - ((AddRO)(allNewRos[curTblIdx])[rocnt]).ro_offset);
				}
				rocnt++;
			}
			// Adjust the location information
			if (bytes != 0 && start > 0)
				AddLocation(start - 1,bytes);
			return count;
		}


		// was a difference flagged for this RO
		// - check via saved offsets into the RO.fST file
		public int isDifferent(uint off)
		{
			int allDiffCnt = 0;
			int NumAllDiff = (allDifferences[curTblIdx]).Count;
			while (allDiffCnt < NumAllDiff)
			{
				if (((DiffRO)(allDifferences[curTblIdx])[allDiffCnt]).oldoff == off)
					return allDiffCnt;
				allDiffCnt++;
			}
			return -1;
		}



		public uint newLocation(uint oldlocation)
		{
			int idx = 0;
			while (oldlocation > ((locinc)allLocations[idx]).maxloc) idx++;
			return oldlocation + (uint)((locinc)allLocations[idx]).inc;
		}


		public void processGroupRecords(BinaryWriter NewFSTFile,uint end)
		{
			uint start;
			while ((start=(uint)brROFst.BaseStream.Seek(0,System.IO.SeekOrigin.Current)) < end)
			{
				// Read ID
				uint id = brROFst.ReadUInt32();
				// Read Parent ID
				uint parid = brROFst.ReadUInt32();
				// Read How Many
				short howmany = brROFst.ReadInt16();
				// Check of additions and write them
				short howmanyplus = howmany;
				if (howmany >= 0)
					howmanyplus += saveAdditions(NewFSTFile,id,start);
				if (parid == 0)
					whereisgl = (uint)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current); // ftell(ofile)
				// Write ID
				NewFSTFile.Write(id);
				// Write Parent ID
				NewFSTFile.Write(parid);
				// Write howmany
				NewFSTFile.Write(howmanyplus);
				// If group - read and write menu list
				int starti = (int)brROFst.BaseStream.Seek(0,System.IO.SeekOrigin.Current); // ftell(rofstfile)
				int starto = (int)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current); // ftell(ofile)
				if (howmanyplus >= 0)
				{
					// for orignal entries
					for (int i=0;i<howmany;i++)
					{
						// Read Offset
						uint offset = brROFst.ReadUInt32();
						// Read Type
						short type = brROFst.ReadInt16();
						// Read Menu
						string menustr = GetAsciiString(brROFst);
						// Look for differences
						int thisdiff = isDifferent(offset);
						if (thisdiff != -1)
						{
							type = ((DiffRO)(allDifferences[curTblIdx])[thisdiff]).type;
							menustr = getMenuString(type,((DiffRO)(allDifferences[curTblIdx])[thisdiff]).val,((DiffRO)(allDifferences[curTblIdx])[thisdiff]).desc);
						}
						// Save new offset
						NewFSTFile.Write(newLocation(offset));
						// Save type
						NewFSTFile.Write(type);
						// Save menu
						WriteString(NewFSTFile,menustr,true);
					}
					// Save new entries
					if (howmanyplus > howmany)
					{
						saveNewMenuEntries(NewFSTFile,id);
					}
				}
				else
				{
					// if RO - read RO record
					// Read type
					short type = brROFst.ReadInt16();
					// Read value
					string val = GetAsciiString(brROFst);
					// Read accid
					string num = GetAsciiString(brROFst);
					// check if changed
					int thisdiff = isDifferent(start);
					if (thisdiff != -1)
					{
						type = ((DiffRO)(allDifferences[curTblIdx])[thisdiff]).type;
						val = ((DiffRO)(allDifferences[curTblIdx])[thisdiff]).val;
					}
					// Save type
					NewFSTFile.Write(type);
					// Save value
					WriteString(NewFSTFile,val,true);
					// Save accid
					WriteString(NewFSTFile,num,true);
				}
				int originalSize = (int)brROFst.BaseStream.Seek(0,System.IO.SeekOrigin.Current) - starti;
				int copySize = (int)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current) - starto;
				if (copySize != originalSize)
					AddLocation(start,copySize - originalSize);
			}
			if (end == 0)
			{
				maxtype = 0;
				maxaw = 0;
				saveAdditions(NewFSTFile,0,0);
			}
		}


		public int getROAccLess(string minnum)
		{
			int romin = -1;
			int roptr = 0;
			int numroptr = (allNewRos[curTblIdx]).Count;
			while (roptr < numroptr)
			{
				// skip processed entries
				while ((roptr < numroptr) &&
					((AddRO)(allNewRos[curTblIdx])[roptr]).ro_stat == 4) roptr++;
				if ((roptr < numroptr))
				{
					// Look for "smallest"
					if ((minnum==null)||
						((AddRO)(allNewRos[curTblIdx])[roptr]).ro_num.CompareTo(minnum) < 0)
					{
						romin = roptr;
						minnum = ((AddRO)(allNewRos[curTblIdx])[roptr]).ro_num;
					}
					roptr++;
				}
			}
			return romin;
		}


		public void putFixed(BinaryWriter NewFstFile, string str, short len)
		{
			int n=len; //str.Length;
			int strlen = str.Length;
			byte [] b = new byte[len];
		// write a fixed length of a string.
			for (int i =0; n > 0; i++)
			{
				if (i < strlen)
					b[i] = (byte)str[i];
				else
					b[i] = (byte)0; // pad with NULLs if smaller than "len"
				n--;
			}
			NewFstFile.Write(b,0,len);
		}


		public void processAccIdRecords(BinaryWriter NewFSTFile,uint end,short len)
		{
			int extra = 0;
			int roptr = 0;
			while (brROFst.BaseStream.Seek(0,System.IO.SeekOrigin.Current) < end)
			{
				// Read ID
				uint id = brROFst.ReadUInt32();
				// Read accID
				string num = GetAsciiString(brROFst,len);
				// Check other accIDs in new records and save
				while ((roptr = getROAccLess(num)) != -1)
				{
					extra+=(len + 4); //sizeof(ulong 16bit)+len
					NewFSTFile.Write(((AddRO)(allNewRos[curTblIdx])[roptr]).ro_id);
					putFixed(NewFSTFile,((AddRO)(allNewRos[curTblIdx])[roptr]).ro_num,len);
					AddRO taddro = (AddRO)(allNewRos[curTblIdx][roptr]);
					taddro.ro_stat = 4;
					allNewRos[curTblIdx].RemoveAt(roptr);
					allNewRos[curTblIdx].Insert(roptr,taddro);
				}
				// Write ID
				NewFSTFile.Write(id);
				// Write accID
				putFixed(NewFSTFile,num,len);
			}
			// Save remaining RO info
			while ((roptr = getROAccLess(null))!=-1)
			{
				extra += (len+4); //sizeof(ulong 16bit)+len
				NewFSTFile.Write(((AddRO)(allNewRos[curTblIdx])[roptr]).ro_id);
				putFixed(NewFSTFile,((AddRO)(allNewRos[curTblIdx])[roptr]).ro_num,len);
				AddRO taddro = (AddRO)(allNewRos[curTblIdx][roptr]);
				taddro.ro_stat = 4;
				allNewRos[curTblIdx].RemoveAt(roptr);
				allNewRos[curTblIdx].Insert(roptr,taddro);
			}
			if (extra > 0) 
				AddLocation(end-1,extra);
		}


		public void nextIDandOffset(ref uint nxtid, ref uint nxtoff)
		{
			int roptr = 0, romin = -1;
			int numroptr = (allNewRos[curTblIdx]).Count;
			int grpptr = 0, grpmin = -1;
			int numgrpptr = (allNewGrps[curTblIdx]).Count;

			while (roptr < numroptr)
			{
				if (((AddRO)(allNewRos[curTblIdx])[roptr]).ro_id > nxtid &&
					(romin == -1 || ((AddRO)(allNewRos[curTblIdx])[roptr]).ro_id < ((AddRO)(allNewRos[curTblIdx])[romin]).ro_id))
					romin = roptr;
				roptr++;
			}

			while (grpptr < numgrpptr)
			{
				if (((AddGroup)(allNewGrps[curTblIdx])[grpptr]).grp_id > nxtid &&
					(grpmin == -1 || ((AddGroup)(allNewGrps[curTblIdx])[grpptr]).grp_id < ((AddGroup)(allNewGrps[curTblIdx])[grpmin]).grp_id))
					grpmin = grpptr;
				grpptr++;
			}
			if (grpmin != -1)
			{
				if ((romin != -1) && ((AddRO)(allNewRos[curTblIdx])[romin]).ro_id < ((AddGroup)(allNewGrps[curTblIdx])[grpmin]).grp_id)
				{
					AddRO taddro = (AddRO)(allNewRos[curTblIdx][romin]);
					taddro.ro_stat = 3;
					allNewRos[curTblIdx].RemoveAt(romin);
					allNewRos[curTblIdx].Insert(romin,taddro);
					nxtid = ((AddRO)(allNewRos[curTblIdx])[romin]).ro_id;
					nxtoff = (uint)((AddRO)(allNewRos[curTblIdx])[romin]).ro_offset;
				}
				else
				{
					AddGroup taddgroup = (AddGroup)(allNewGrps[curTblIdx][grpmin]);
					taddgroup.grp_stat = 3;
					allNewGrps[curTblIdx].RemoveAt(grpmin);
					allNewGrps[curTblIdx].Insert(grpmin,taddgroup);
					nxtid = ((AddGroup)(allNewGrps[curTblIdx])[grpmin]).grp_id;
					nxtoff = ((AddGroup)(allNewGrps[curTblIdx])[grpmin]).grp_offset;
				}
			}
			else
			{
				if (romin != -1)
				{
					AddRO taddro = (AddRO)(allNewRos[curTblIdx][romin]);
					taddro.ro_stat = 3;
					allNewRos[curTblIdx].RemoveAt(romin);
					allNewRos[curTblIdx].Insert(romin,taddro);
					nxtid = ((AddRO)(allNewRos[curTblIdx])[romin]).ro_id;
					nxtoff = (uint)((AddRO)(allNewRos[curTblIdx])[romin]).ro_offset;
				}
				else
				{
					nxtid = 0;
					nxtoff = 0;
				}
			}
		}


		public void processIdRecords(BinaryWriter NewFstFile,uint end)
		{
			int extra = 0;
			uint nxtid =0, nxtoff=0;
			nextIDandOffset(ref nxtid,ref nxtoff);
			while (brROFst.BaseStream.Seek(0,System.IO.SeekOrigin.Current) < end)
			{
				// Read ID
				uint id = brROFst.ReadUInt32();
				// Look for new entries that precede ID
				while (nxtid != 0 && nxtid < id)
				{
					extra += 8; // sizeof(ulong 16bit) * 2
					NewFstFile.Write(nxtid);
					NewFstFile.Write(nxtoff);
					nextIDandOffset(ref nxtid,ref nxtoff); // passed by reference
				}
				// Write ID
				NewFstFile.Write(id);
				// Read Offset
				uint off = brROFst.ReadUInt32();
				// Write new offset
				NewFstFile.Write(newLocation(off));
			}
			// Save new RO entries and Group entries
			while (nxtid != 0)
			{
				extra += 8; // sizeof(ulong 16bit)*2
				NewFstFile.Write(nxtid);
				NewFstFile.Write(nxtoff);
				nextIDandOffset(ref nxtid,ref nxtoff); // passed by reference
			}
			if (extra != 0)
				AddLocation(end-1,extra);
		}


		/*
		 * Merge the changes into the RO.FST file (usually Approved directory)
		 */
		public void CreateMergedFSTFile(string NewFStName)
		{
			BinaryWriter NewFSTFile=null;

			// Open a new FST file for writing
			try
			{
				NewFSTFile = new BinaryWriter(File.Open(NewFStName,System.IO.FileMode.Create,System.IO.FileAccess.ReadWrite,FileShare.None));
			}
			catch (Exception e)
			{
				MessageBox.Show("Error Opening "+NewFStName +"\n\n" + e.Message,"Approval Error");
			}
			
			// Write the RO.FST header
			Header.Write(NewFSTFile);

			// Position the old FST file past the header
			brROFst.BaseStream.Seek(Header.GetStructSize(),System.IO.SeekOrigin.Begin);

			// Loop through each database
			for (short i=0; i < numDatabases; i++)
			{
				// Set the current table index used to access the allNewRos,
				// allNewGrps, and allDifferences arrays
				curTblIdx = i;

				// Loop through the Group and RO records
				// Update the RO Group information
				processGroupRecords(NewFSTFile,((ROFST_DbInfo)DatabasesInfo[i]).dbiIL);
				if (((ROFST_DbInfo)DatabasesInfo[i]).dbiAW == 0)
					((ROFST_DbInfo)DatabasesInfo[i]).dbiAW = maxaw;

				if (((ROFST_DbInfo)DatabasesInfo[i]).dbiType == 0)
					((ROFST_DbInfo)DatabasesInfo[i]).dbiType = maxtype;

				((ROFST_DbInfo)DatabasesInfo[i]).dbiGL = whereisgl;
				((ROFST_DbInfo)DatabasesInfo[i]).dbiIL = (uint)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current);
				
				// Loop through the ID list
				processIdRecords(NewFSTFile,((ROFST_DbInfo)DatabasesInfo[i]).dbiAL);
				((ROFST_DbInfo)DatabasesInfo[i]).dbiAL = (uint)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current);
				// Loop through the accid list
				processAccIdRecords(NewFSTFile,((ROFST_DbInfo)DatabasesInfo[i]).dbiEND,(short)((ROFST_DbInfo)DatabasesInfo[i]).dbiAW);
				((ROFST_DbInfo)DatabasesInfo[i]).dbiEND = (uint)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current);
			}
			// Save the Database info
			uint lngbuf = 0;
			// Save the current location in the new fst file
			uint dblength = (uint)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current);
			NewFSTFile.Write(lngbuf);
			NewFSTFile.Write(numDatabases);
			uint lngval;
			ROFST_DbInfo tmpdbinfo = new ROFST_DbInfo();
			lngval = (uint)(tmpdbinfo.GetStructSize() * numDatabases);
			for (int i=0;i<numDatabases;i++)
			{
				NewFSTFile.Write(((ROFST_DbInfo)DatabasesInfo[i]).dbiID);
				NewFSTFile.Write(((ROFST_DbInfo)DatabasesInfo[i]).dbiType);
				NewFSTFile.Write(((ROFST_DbInfo)DatabasesInfo[i]).dbiAW);
				NewFSTFile.Write(((ROFST_DbInfo)DatabasesInfo[i]).dbiGL);
				NewFSTFile.Write(((ROFST_DbInfo)DatabasesInfo[i]).dbiIL);
				NewFSTFile.Write(((ROFST_DbInfo)DatabasesInfo[i]).dbiAL);
				NewFSTFile.Write(((ROFST_DbInfo)DatabasesInfo[i]).dbiEND);
				NewFSTFile.Write(lngval);
				lngval += (uint)((ROFST_DbInfo)DatabasesInfo[i]).dbiTitle.Length + 1;
				NewFSTFile.Write(lngval);
				lngval += (uint)((ROFST_DbInfo)DatabasesInfo[i]).dbiAP.Length + 1;
			}
			for (short i=0;i<numDatabases;i++)
			{
				WriteString(NewFSTFile,((ROFST_DbInfo)DatabasesInfo[i]).dbiTitle,true);
				WriteString(NewFSTFile,((ROFST_DbInfo)DatabasesInfo[i]).dbiAP,true);
			}
			// locate the end
			uint dbend = (uint)NewFSTFile.Seek(0,System.IO.SeekOrigin.Current); // ftell(ofile)
			// How long is the db info
			NewFSTFile.Seek((int)dblength,System.IO.SeekOrigin.Begin);
			lngbuf = dbend-dblength-4; //dbend-dblength-sizeof(dblength)
			NewFSTFile.Write(lngbuf);
			// Go to the beginning of the new file
			NewFSTFile.Seek(0,System.IO.SeekOrigin.Begin);
			// Save the header again
			if (Header.hcYear==0)
				Header.SetHeader(dblength); // sets the header and date/time
			else
				Header.dboffset = dblength;
			Header.Write(NewFSTFile);
			NewFSTFile.Flush();
			NewFSTFile.Close();
			uint test = (uint) Header.hcHund;
			// stamp the new FST file with the RO.FST header date/time
			DateTime dt = new DateTime((int)Header.hcYear,(int)Header.hMonth,(int)Header.hDay,(int)Header.hcHour,(int)Header.hcMin,(int)Header.hcSec,(int)Header.hcHund);
			File.SetLastWriteTime(NewFStName,dt);
		}
	}
/*************************************************************************/

	public class strEntry
	{

		public strEntry()
		{
		}

		public virtual string toText(int mi){return "";}

		public virtual int getLength(int mi){return 0;}

	}


	
	public class seText : strEntry
	{
		public string str;
		public int len;

		public seText(string instr, int inlen)
		{
			str = instr.Substring(0,inlen);
			len = inlen;
		}

		public override string toText(int mi)
		{
			return str;
		}

		public override int getLength(int mi)
		{
			return len;
		}
	}


	public class seUser : strEntry
	{
		string uvalue;
		int len;

		public seUser(string str, int l)
		{
			uvalue = ROProcessTools.evaluate(str,l);
			len = uvalue.Length;
		}

		public override string toText(int mi)
		{
			return uvalue;
		}

		public override int getLength(int mi)
		{
			return len;
		}
	}

	public class strList
	{
		public varList vList;
		public menuList mList;
		private ArrayList aryStrEntry;

		public strList(string str)
		{
			vList = new varList();
			mList = new menuList();
			aryStrEntry = new ArrayList();
			Process(str,str.Length);
		}

		public strList(string str, int l, varList vl, menuList ml)
		{
			if (vl == null)
				vList =  new varList();
			else
				vList = vl;
			if (ml == null)
				mList = new menuList();
			else
				mList = ml;
			if (aryStrEntry == null)
				aryStrEntry = new ArrayList();
			Process(str.Substring(0,l),l);
		}

		public void Add(strEntry se)
		{
			aryStrEntry.Add(se);
		}

		public void processCondition(string cnd, string opt, int lc, int lo)
		{
			// equaluate condition
			string stat = ROProcessTools.evaluate(cnd,lc);
			int ls = stat.Length;
			// look for match - remember default
			string match=null;
			int matchlen = 0;
			string def = null;
			int deflen = 0;
			while (lo > 0 && match == null)
			{
				int end = ROProcessTools.matchingBrace(opt);
				int eq = opt.IndexOf('=');
				int len = end + 1; // +1 to include the '}'
				int li = eq-1;
				int ld = len-li-3; // One for '{', '=' and '}'
				if (def == null || eq == 1)
				{
					def = opt.Substring(eq+1,ld);
					deflen =  ld;
				}
				if (ls == li && stat.StartsWith(opt.Substring(1,li)))
					{
					match = opt.Substring(eq+1,ld);
					matchlen = ld;
				}
				opt =  opt.Substring(len);
				lo -= len;
			}
			// if match process option - or process default
			if (match == null)
			{
				match = def;
				matchlen = deflen;
			}
			Process(match,matchlen);
		}

		public void processBrace(string str, int l, ref int nfnd)
		{
			int nxt = ROProcessTools.nextDelimiter("{=",str,l); // -1 means not found
			if(nxt == -1) // varUse
			{
				strList found = null;
				found = vList.lookFor(str,l);
				if (found != null)
					Add(new seVarUse(found));
				else
					nfnd = 1;
			}
			else
			{
				if (str[nxt] == '{') // conditonal or menu
				{
					if(nxt==0)
					{ // menu
						mList.Process(str,l,vList);
						Add(new seMenuUse(mList));
					} 
					else 
					{ // conditional
						processCondition(str, str.Substring(nxt), nxt, l -nxt);
					}
				}
				else 
				{ // must be variable definiton
					vList.Add(str, str.Substring(nxt+1), nxt, l - nxt - 1, mList);
				}
			}
		}

		public void Process (string str, int len)
		{
			int nxtDlm = -1; // -1 = not found
			while (len > 0)
			{
				nxtDlm = ROProcessTools.nextDelimiter("{",str,len);
				if (nxtDlm == -1)
				{
					// add entire string as is
					Add(new seText(str,len));
					//str = str.Substring(len);  don't think we need to do this
					len = 0;
				}
				else
				{
					int cnt = 0;
					if (nxtDlm > 0)
					{
						// add preceeding text
						Add(new seText(str,nxtDlm));
						str = str.Substring(nxtDlm);
						len-=nxtDlm;
					}
					// add text group by delimiter, including
					// the delimiters
					nxtDlm = ROProcessTools.matchingBrace(str)+1;
					cnt = nxtDlm;
					int nfnd = 0;
					processBrace(str.Substring(1),cnt-2,ref nfnd);
					if (nfnd > 0)
					{
						Add(new seText(str,cnt));
					}
					// position to next delimiter grouping
					len -= cnt;
					str = str.Substring(nxtDlm);
				}
			}
		}

		public string toText(int mi)
		{
			StringBuilder bldstr = new StringBuilder();
			for (int i=0; i < aryStrEntry.Count; i++)
			{
				bldstr.Append(((strEntry)aryStrEntry[i]).toText(mi));
			}
			return bldstr.ToString();
		}

		public int getLength(int mi)
		{
			int rtnval = 0;
			for (int i=0; i < aryStrEntry.Count; i++)
			{
				rtnval += (((strEntry)aryStrEntry[i]).getLength(mi));
			}
			return rtnval;
		}

		public int menuCount()
		{
			return mList.getCount();
		}

		public string getIds(ref int cnt)
		{
			// return a string of the IDs
			return mList.getIds(ref cnt);
		}
	}



	public class strList2
	{

		private ArrayList aryStrEntry;

		public strList2(string str)
		{
			int l=str.Length;
			if (aryStrEntry == null)
				aryStrEntry = new ArrayList();
			while(l > 0)
			{
				int ptr=ROProcessTools.nextDelimiter("<",str,l);
				int cptr = (ptr==-1)?-1:ROProcessTools.nextDelimiter(">",str.Substring(ptr,str.Length - ptr),str.Length-ptr);
				if(ptr == -1 || (ptr>-1 && cptr == -1))
				{
					Add(new seText(str,l));
					l=0;
				} 
				else 
				{
					int cnt = ptr; //(int)(ptr-str);
					if(cnt > 0)
					{
						Add(new seText(str,cnt));
						l-=cnt;
						str = str.Substring(ptr); // str=ptr
					}
					ptr=ROProcessTools.nextDelimiter(">",str,l)+1;
					cnt = ptr;//(int)(ptr-str);
					string tmpstr = str.Substring(1);
					Add(new seUser(tmpstr,cnt-2));
					l-=cnt;
					str = str.Substring(ptr);//str=ptr;
				}
			}
		}

		public void Add (strEntry se)
		{
			aryStrEntry.Add(se);
		}

		public string toText()
		{
			StringBuilder bldstr = new StringBuilder();
			for (int i=0; i < aryStrEntry.Count; i++)
			{
				bldstr.Append(((strEntry)aryStrEntry[i]).toText(0));
			}
			return bldstr.ToString();
		}
	}

	public class varEntry
	{
		string Name;
		int Len;
		strList Definition;

		public varEntry(string InName, string InDef, int lenInName, int lenInDef, varList vlist, menuList mlist)
		{
			Definition =  new strList(InDef,lenInDef,vlist,mlist);
			Name = InName.Substring(0,lenInName);
			Len = lenInName;
		}

		public  strList lookFor(string nam, int l)
		{
			string tname = nam.Substring(0,l);
			if (l == Len && (Name.CompareTo(tname)==0))
				return Definition; // found
			return null; // not found
		}
	}

	public class varList
	{
		ArrayList varEntryAry;

		public varList()
		{
			varEntryAry = new ArrayList();
		}

		public void Add(string InName, string InDef, int lenInName, int lenInDef, menuList mlist)
		{
			varEntry tmpEntry = new varEntry(InName,InDef,lenInName,lenInDef,null,mlist);
			varEntryAry.Add(tmpEntry);
		}

		public strList lookFor(string str, int len)
		{
			strList rtnval = null;
			for (int i = 0; (rtnval == null) && (i < varEntryAry.Count); i++)
			{
				rtnval = ((varEntry)varEntryAry[i]).lookFor(str,len);
			}
			return rtnval;
		}
	}

	public class seVarUse : strEntry
	{
		strList Def; 

		public seVarUse(strList d)
		{
			Def = d;
		}


		public override string toText(int mi)
		{
			return Def.toText(mi);
		}

		public override int getLength(int mi)
		{
			return Def.getLength(mi);
		}
	}

	public class menuEntry
	{
		byte id;
		strList definition;

		public menuEntry(string idptr,string def,int li,int ld,varList vlst)
		{
			definition = new strList(def, ld, vlst,null);
			// save the id
			if(li != 0)
				id=(byte)idptr[0];
			else 
				id=0;
		}

		public int getLength(int mi)
		{
			if(mi==(int)id || mi==-1)
				return definition.getLength(mi);
			else
				return 0;
		}

		public virtual string toText(int mi)
		{
			if (mi == id || mi==-1)
				return definition.toText(mi);
			else
				return "";
		}

		public string getID()
		{
			string rtnstr = id.ToString();
			return rtnstr;
		}

		public int getCount()
		{
			return 1;
		}
	}


	public class menuList : strEntry
	{
		ArrayList aryMenuList;

		public menuList()
		{
			aryMenuList = new ArrayList();
		}

		public void Add(menuEntry MnuEntry)
		{
			aryMenuList.Add(MnuEntry);
		}

		private int _getLength(int mi)
		{
			int lcnt = aryMenuList.Count;
			int rtnval =0;

			for (int i=0; i < lcnt; i++)
			{
				rtnval += (((menuEntry)aryMenuList[i])).getLength(mi);
			}
			return rtnval;
		}

		private int _getLenAndLookat(int mi, ref int lookat)
		{
			int len =0;
			lookat = mi;

			if (aryMenuList.Count > 0)
			{
				len=_getLength(lookat);
				if (len ==0)
				{
					lookat = 0;
					len = _getLength(lookat);
				}
				if (len == 0)
				{
					lookat = -1;
					len = _getLength(lookat);
				}
			}

			return len;
		}

		public override int getLength(int mi)
		{
			int lookat=0;
			return _getLenAndLookat(mi,ref lookat);
		}

		public override string toText(int mi)
		{
			StringBuilder rtnstr = new StringBuilder();
			int lookat=0;

			int len = _getLenAndLookat(mi,ref lookat);
			if (aryMenuList.Count > 0)
			{
				for (int i = 0; i< aryMenuList.Count; i++)
				{
					string roval = ((menuEntry)aryMenuList[i]).toText(lookat);
					if (lookat == -1)
					{
						// For Approve Individual, show all possible RO
						// values (for conditional RO's)
						// We may need to add a flag here later on when
						// this logic is used in the Procedure Editor.
						if (i > 0 && !roval.Equals(""))
							rtnstr.Append("\n");
					}
					rtnstr.Append(roval);
				}
			}
			return rtnstr.ToString();
		}

		public string getIds(ref int cnt)
		{
			StringBuilder rtnstr = new StringBuilder();
			cnt = getCount();
			int arycnt = aryMenuList.Count;

			for (int i=0; i<arycnt; i++)
			{
				rtnstr.Append(((menuEntry)aryMenuList[i]).getID());
			}
			return rtnstr.ToString();
		}

		public int getCount()
		{
			int rtncnt=0;
			int arycnt = aryMenuList.Count;
			for (int i=0; i<arycnt; i++)
			{
				rtncnt+=(((menuEntry)aryMenuList[i]).getCount());
			}
			return rtncnt;
		}

		public void Process(string str,int l, varList v)
		{
			// get each piece and add a new entry
			do 
			{
				int end= ROProcessTools.matchingBrace(str);
				int eq=str.IndexOf('=');
				int len = end + 1;	// +1 to include the '}'
				int li = eq - 1;
				int ld = len-li-3;	// One for '{', '=' and '}'
				Add(new menuEntry(str.Substring(1),str.Substring(eq+1),li,ld,v));
				// point to the next piece
				str = str.Substring(len);
				l -= len;
			} while(l > 0);
		}
	}

	public class seMenuUse : strEntry
	{
		menuList ml;

		public seMenuUse(menuList m)
		{
			ml=m;
		}

		public override string toText(int mi)
		{
			return ml.toText(mi);
		}

		public override int getLength(int mi)
		{
			return ml.getLength(mi);
		}
	}

}
