context: using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Data;
  using System.Drawing;
  using System.Text;
  using System.Threading;
  using System.Windows.Forms;
  using System.Text.RegularExpressions;
  using System.Linq;
  using OpenDentBusiness;
  using OpenDentBusiness.Mobile;
  
  
  namespace OpenDental {
  	///<summary>THIS FORM HAS BEEN DEPRECATED!!! All functionality that previously existed in this form has been moved to FormPatientPortalSetup.</summary>
  	public partial class FormMobile:Form {
  		private static MobileWeb.Mobile mb=new MobileWeb.Mobile();
  		private static int BatchSize=100;
  		///<summary>All statements of a patient are not uploaded. The limit is defined by the recent [statementLimitPerPatient] records</summary>
  		private static int statementLimitPerPatient=5;
  		///<summary>This variable prevents the synching methods from being called when a previous synch is in progress.</summary>
  		private static bool IsSynching;
  		///<summary>This variable prevents multiple error message boxes from popping up if mobile synch server is not available.</summary>
  		private static bool IsServerAvail=true;
  		///<summary>True if a pref was saved and the other workstations need to have their cache refreshed when this form closes.</summary>
  		private bool changed;
  		///<summary>If this variable is true then records are uploaded one at a time so that an error in uploading can be traced down to a single record</summary>
  		private static bool IsTroubleshootMode=false;
  		private static FormProgress FormP;
  
  		private enum SynchEntity {
  			patient,
  			appointment,
  			prescription,
  			provider,
  			pharmacy,
  			labpanel,
  			labresult,
  			medication,
  			medicationpat,
  			allergy,
  			allergydef,
  			disease,
  			diseasedef,
  			icd9,
  			statement,
  			document,
  			recall,
  			deletedobject,
  			patientdel
  		}
  
  		public FormMobile() {
  			InitializeComponent();
  			Lan.F(this);
  		}
  
  		private void FormMobileSetup_Load(object sender,EventArgs e) {
  			textMobileSyncServerURL.Text=PrefC.GetString(PrefName.MobileSyncServerURL);
  			textSynchMinutes.Text=PrefC.GetInt(PrefName.MobileSyncIntervalMinutes).ToString();
  			textDateBefore.Text=PrefC.GetDate(PrefName.MobileExcludeApptsBeforeDate).ToShortDateString();
  			textMobileSynchWorkStation.Text=PrefC.GetString(PrefName.MobileSyncWorkstationName);
  			textMobileUserName.Text=PrefC.GetString(PrefName.MobileUserName);
  			textMobilePassword.Text="";//not stored locally, and not pulled from web server
  			DateTime lastRun=PrefC.GetDateT(PrefName.MobileSyncDateTimeLastRun);
  			if(lastRun.Year>1880) {
  				textDateTimeLastRun.Text=lastRun.ToShortDateString()+" "+lastRun.ToShortTimeString();
  			}
  			//Web server is not contacted when loading this form.  That would be too slow.
  			//CreateAppointments(5);
  		}
  
  		private void butCurrentWorkstation_Click(object sender,EventArgs e) {
  			textMobileSynchWorkStation.Text=System.Environment.MachineName.ToUpper();
  		}
  
  		private void butSave_Click(object sender,EventArgs e) {
  			Cursor=Cursors.WaitCursor;
  			if(!SavePrefs()) {
  				Cursor=Cursors.Default;
  				return;
  			}
  			Cursor=Cursors.Default;
  			MsgBox.Show(this,"Done");
  		}
  
  		///<summary>Returns false if validation failed.  This also makes sure the web service exists, the customer is paid, and the registration key is correct.</summary>
  		private bool SavePrefs(){
  			//validation
  			if( textSynchMinutes.errorProvider1.GetError(textSynchMinutes)!=""
  				|| textDateBefore.errorProvider1.GetError(textDateBefore)!="")
  			{
  				MsgBox.Show(this,"Please fix data entry errors first.");
  				return false;
  			}
  			//yes, workstation is allowed to be blank.  That's one way for user to turn off auto synch.
  			//if(textMobileSynchWorkStation.Text=="") {
  			//	MsgBox.Show(this,"WorkStation cannot be empty");
  			//	return false;
  			//}
  			// the text field is read because the keyed in values have not been saved yet
  			//if(textMobileSyncServerURL.Text.Contains("192.168.0.196") || textMobileSyncServerURL.Text.Contains("localhost")) {
  			if(textMobileSyncServerURL.Text.Contains("10.10.1.196") || textMobileSyncServerURL.Text.Contains("localhost")) {
  				IgnoreCertificateErrors();// done so that TestWebServiceExists() does not thow an error.
  			}
  			// if this is not done then an old non-functional url prevents any new url from being saved.
  			Prefs.UpdateString(PrefName.MobileSyncServerURL,textMobileSyncServerURL.Text);
  			if(!TestWebServiceExists()) {
  				MsgBox.Show(this,"Web service not found.");
  				return false;
  			}
  			if(mb.GetCustomerNum(PrefC.GetString(PrefName.RegistrationKey))==0) {
  				MsgBox.Show(this,"Registration key is incorrect.");
  				return false;
  			}
  			if(!VerifyPaidCustomer()) {
  				return false;
  			}
  			//Minimum 10 char.  Must contain uppercase, lowercase, numbers, and symbols. Valid symbols are: !@#$%^&+= 
  			//The set of symbols checked was far too small, not even including periods, commas, and parentheses.
  			//So I rewrote it all.  New error messages say exactly what's wrong with it.
  			if(textMobileUserName.Text!="") {//allowed to be blank
  				if(textMobileUserName.Text.Length<10) {
  					MsgBox.Show(this,"User Name must be at least 10 characters long.");
  					return false;
  				}
  				if(!Regex.IsMatch(textMobileUserName.Text,"[A-Z]+")) {
  					MsgBox.Show(this,"User Name must contain an uppercase letter.");
  					return false;
  				}
  				if(!Regex.IsMatch(textMobileUserName.Text,"[a-z]+")) {
  					MsgBox.Show(this,"User Name must contain an lowercase letter.");
  					return false;
  				}
  				if(!Regex.IsMatch(textMobileUserName.Text,"[0-9]+")) {
  					MsgBox.Show(this,"User Name must contain a number.");
  					return false;
  				}
  				if(!Regex.IsMatch(textMobileUserName.Text,"[^0-9a-zA-Z]+")) {//absolutely anything except number, lower or upper.
  					MsgBox.Show(this,"User Name must contain punctuation or symbols.");
  					return false;
  				}
  			}
  			if(textDateBefore.Text==""){//default to one year if empty
  				textDateBefore.Text=DateTime.Today.AddYears(-1).ToShortDateString();
  				//not going to bother informing user.  They can see it.
  			}
  			//save to db------------------------------------------------------------------------------------
  			if(Prefs.UpdateString(PrefName.MobileSyncServerURL,textMobileSyncServerURL.Text)
  				| Prefs.UpdateInt(PrefName.MobileSyncIntervalMinutes,PIn.Int(textSynchMinutes.Text))//blank entry allowed
  				| Prefs.UpdateString(PrefName.MobileExcludeApptsBeforeDate,POut.Date(PIn.Date(textDateBefore.Text),false))//blank 
  				| Prefs.UpdateString(PrefName.MobileSyncWorkstationName,textMobileSynchWorkStation.Text)
  				| Prefs.UpdateString(PrefName.MobileUserName,textMobileUserName.Text)
  			){
  				changed=true;
  				Prefs.RefreshCache();
  			}
  			//Username and password-----------------------------------------------------------------------------
  			mb.SetMobileWebUserPassword(PrefC.GetString(PrefName.RegistrationKey),textMobileUserName.Text.Trim(),textMobilePassword.Text.Trim());
  			return true;
  		}
  
  		///<summary>Uploads Preferences to the Patient Portal /Mobile Web.</summary>
  		public static void UploadPreference(PrefName prefname) {
  			if(PrefC.GetString(PrefName.RegistrationKey)=="") {
  				return;//Prevents a bug when using the trial version with no registration key.  Practice edit, OK, was giving error.
  			}
  			try {
  				if(TestWebServiceExists()) {
  					Prefm prefm = Prefms.GetPrefm(prefname.ToString());
  					mb.SetPreference(PrefC.GetString(PrefName.RegistrationKey),prefm);
  				}
  			}
  			catch(Exception ex) {
  				MessageBox.Show(ex.Message);//may not show if called from a thread but that does not matter - the failing of this method should not stop the  the code from proceeding.
  			}
  		}
  
  		private void butDelete_Click(object sender,EventArgs e) {
  			if(!SavePrefs()) {
  				return;
  			}
  			if(!MsgBox.Show(this,MsgBoxButtons.OKCancel,"Delete all your data from our server?  This happens automatically before a full synch.")) {
  				return;
  			}
  			mb.DeleteAllRecords(PrefC.GetString(PrefName.RegistrationKey));
  			MsgBox.Show(this,"Done");
  		}
  		
  		private void butFullSync_Click(object sender,EventArgs e) {
  			if(!SavePrefs()) {
  				return;
  			}
  			if(IsSynching) {
  				MsgBox.Show(this,"A Synch is in progress at the moment. Please try again later.");
  				return;
  			}
  			if(!MsgBox.Show(this,MsgBoxButtons.OKCancel,"This will be time consuming. Continue anyway?")) {
  				return;
  			}
  			//for full synch, delete all records then repopulate.
  			mb.DeleteAllRecords(PrefC.GetString(PrefName.RegistrationKey));
  			ShowProgressForm(DateTime.MinValue);
  		}
  
  		private void butSync_Click(object sender,EventArgs e) {
  			if(!SavePrefs()) {
  				return;
  			}
  			if(IsSynching) {
  				MsgBox.Show(this,"A Synch is in progress at the moment. Please try again later.");
  				return;
  			}
  			if(PrefC.GetDate(PrefName.MobileExcludeApptsBeforeDate).Year<1880) {
  				MsgBox.Show(this,"Full synch has never been run before.");
  				return;
  			}
  			DateTime changedSince=PrefC.GetDateT(PrefName.MobileSyncDateTimeLastRun);
  			ShowProgressForm(changedSince);
  		}
  		
  		private void ShowProgressForm(DateTime changedSince){
  			if(checkTroubleshooting.Checked) {
  				IsTroubleshootMode=true;
  			}
  			else {
  				IsTroubleshootMode=false;
  			}
  			DateTime timeSynchStarted=MiscData.GetNowDateTime();
  			FormP=new FormProgress();
  			FormP.MaxVal=100;//to keep the form from closing until the real MaxVal is set.
  			FormP.NumberMultiplication=1;
  			FormP.DisplayText="Preparing records for upload.";
  			FormP.NumberFormat="F0";
  			//start the thread that will perform the upload
  			ThreadStart uploadDelegate= delegate { UploadWorker(changedSince,timeSynchStarted); };
  			Thread workerThread=new Thread(uploadDelegate);
  			workerThread.Start();
  			//display the progress dialog to the user:
  			FormP.ShowDialog();
  			if(FormP.DialogResult==DialogResult.Cancel) {
  				workerThread.Abort();
  			}
  			changed=true;
  			textDateTimeLastRun.Text=PrefC.GetDateT(PrefName.MobileSyncDateTimeLastRun).ToShortDateString()+" "+PrefC.GetDateT(PrefName.MobileSyncDateTimeLastRun).ToShortTimeString();
  		}
  
  
  		///<summary>This is the function that the worker thread uses to actually perform the upload.  Can also call this method in the ordinary way if the data to be transferred is small.  The timeSynchStarted must be passed in to ensure that no records are skipped due to small time differences.</summary>
  		private static void UploadWorker(DateTime changedSince,DateTime timeSynchStarted) {
  			int totalCount=100;
  			try {//Dennis: try catch may not work: Does not work in threads, not sure about this. Note that all methods inside this try catch block are without exception handling. This is done on purpose so that when an exception does occur it does not update PrefName.MobileSyncDateTimeLastRun
  				//The handling of PrefName.MobileSynchNewTables79 should never be removed in future versions
  				DateTime changedProv=changedSince;
  				DateTime changedDeleted=changedSince;
  				DateTime changedPat=changedSince;
  				DateTime changedStatement=changedSince;
  				DateTime changedDocument=changedSince;
  				DateTime changedRecall=changedSince;
  				if(!PrefC.GetBoolSilent(PrefName.MobileSynchNewTables79Done,false)) {
  					changedProv=DateTime.MinValue;
  					changedDeleted=DateTime.MinValue;
  				}
  				if(!PrefC.GetBoolSilent(PrefName.MobileSynchNewTables112Done,false)) {
  				    changedPat=DateTime.MinValue;
  					changedStatement=DateTime.MinValue;
  					changedDocument=DateTime.MinValue;
  				}
  				if(!PrefC.GetBoolSilent(PrefName.MobileSynchNewTables121Done,false)) {
  					changedRecall=DateTime.MinValue;
  					UploadPreference(PrefName.PracticeTitle); //done again because the previous upload did not include the prefnum
  				}
  				bool synchDelPat=true;
  				if(PrefC.GetDateT(PrefName.MobileSyncDateTimeLastRun).Hour==timeSynchStarted.Hour) {
  					synchDelPat=false;// synching delPatNumList is timeconsuming (15 seconds) for a dental office with around 5000 patients and it's mostly the same records that have to be deleted every time a synch happens. So it's done only once hourly.
  				}
  				//MobileWeb
  				List<long> patNumList=Patientms.GetChangedSincePatNums(changedPat);
  				List<long> aptNumList=Appointmentms.GetChangedSinceAptNums(changedSince,PrefC.GetDate(PrefName.MobileExcludeApptsBeforeDate));
  				List<long> rxNumList=RxPatms.GetChangedSinceRxNums(changedSince);
  				List<long> provNumList=Providerms.GetChangedSinceProvNums(changedProv);
  				List<long> pharNumList=Pharmacyms.GetChangedSincePharmacyNums(changedSince);
  				List<long> allergyDefNumList=AllergyDefms.GetChangedSinceAllergyDefNums(changedSince);
  				List<long> allergyNumList=Allergyms.GetChangedSinceAllergyNums(changedSince);
  				//exclusively Patient Portal
  				/*
  				List<long> eligibleForUploadPatNumList=Patientms.GetPatNumsEligibleForSynch();
  				List<long> labPanelNumList=LabPanelms.GetChangedSinceLabPanelNums(changedSince,eligibleForUploadPatNumList);
  				List<long> labResultNumList=LabResultms.GetChangedSinceLabResultNums(changedSince);
  				List<long> medicationNumList=Medicationms.GetChangedSinceMedicationNums(changedSince);
  				List<long> medicationPatNumList=MedicationPatms.GetChangedSinceMedicationPatNums(changedSince,eligibleForUploadPatNumList);
  				List<long> diseaseDefNumList=DiseaseDefms.GetChangedSinceDiseaseDefNums(changedSince);
  				List<long> diseaseNumList=Diseasems.GetChangedSinceDiseaseNums(changedSince,eligibleForUploadPatNumList);
  				List<long> icd9NumList=ICD9ms.GetChangedSinceICD9Nums(changedSince);
  				List<long> statementNumList=Statementms.GetChangedSinceStatementNums(changedStatement,eligibleForUploadPatNumList,statementLimitPerPatient);
  				List<long> documentNumList=Documentms.GetChangedSinceDocumentNums(changedDocument,statementNumList);
  				List<long> recallNumList=Recallms.GetChangedSinceRecallNums(changedRecall);*/
  				List<long> delPatNumList=Patientms.GetPatNumsForDeletion();
  				//List<DeletedObject> dO=DeletedObjects.GetDeletedSince(changedDeleted);dennis: delete this line later
  				List<long> deletedObjectNumList=DeletedObjects.GetChangedSinceDeletedObjectNums(changedDeleted);//to delete appointments from mobile
  				totalCount= patNumList.Count+aptNumList.Count+rxNumList.Count+provNumList.Count+pharNumList.Count
  					//+labPanelNumList.Count+labResultNumList.Count+medicationNumList.Count+medicationPatNumList.Count
  					//+allergyDefNumList.Count//+allergyNumList.Count+diseaseDefNumList.Count+diseaseNumList.Count+icd9NumList.Count
  					//+statementNumList.Count+documentNumList.Count+recallNumList.Count
  					+deletedObjectNumList.Count;
  				if(synchDelPat) {
  					totalCount+=delPatNumList.Count;
  				}
  				double currentVal=0;
  				if(Application.OpenForms["FormProgress"]!=null) {// without this line the following error is thrown: "Invoke or BeginInvoke cannot be called on a control until the window handle has been created." or a null pointer exception is thrown when an automatic synch is done by the system.
  					FormP.Invoke(new PassProgressDelegate(PassProgressToDialog),
  						new object[] { currentVal,"?currentVal of ?maxVal records uploaded",totalCount,"" });
  				}
  				IsSynching=true;
  				SynchGeneric(patNumList,SynchEntity.patient,totalCount,ref currentVal);
  				SynchGeneric(aptNumList,SynchEntity.appointment,totalCount,ref currentVal);
  				SynchGeneric(rxNumList,SynchEntity.prescription,totalCount,ref currentVal);
  				SynchGeneric(provNumList,SynchEntity.provider,totalCount,ref currentVal);
  				SynchGeneric(pharNumList,SynchEntity.pharmacy,totalCount,ref currentVal);
  				//pat portal
  				/*
  				SynchGeneric(labPanelNumList,SynchEntity.labpanel,totalCount,ref currentVal);
  				SynchGeneric(labResultNumList,SynchEntity.labresult,totalCount,ref currentVal);
  				SynchGeneric(medicationNumList,SynchEntity.medication,totalCount,ref currentVal);
  				SynchGeneric(medicationPatNumList,SynchEntity.medicationpat,totalCount,ref currentVal);
  				SynchGeneric(allergyDefNumList,SynchEntity.allergydef,totalCount,ref currentVal);
  				SynchGeneric(allergyNumList,SynchEntity.allergy,totalCount,ref currentVal);
  				SynchGeneric(diseaseDefNumList,SynchEntity.diseasedef,totalCount,ref currentVal);
  				SynchGeneric(diseaseNumList,SynchEntity.disease,totalCount,ref currentVal);
  				SynchGeneric(icd9NumList,SynchEntity.icd9,totalCount,ref currentVal);
  				SynchGeneric(statementNumList,SynchEntity.statement,totalCount,ref currentVal);
  				SynchGeneric(documentNumList,SynchEntity.document,totalCount,ref currentVal);
  				SynchGeneric(recallNumList,SynchEntity.recall,totalCount,ref currentVal);*/
  				if(synchDelPat) {
  					SynchGeneric(delPatNumList,SynchEntity.patientdel,totalCount,ref currentVal);
  				}
  				//DeleteObjects(dO,totalCount,ref currentVal);// this has to be done at this end because objects may have been created and deleted between synchs. If this function is place above then the such a deleted object will not be deleted from the server.
  				SynchGeneric(deletedObjectNumList,SynchEntity.deletedobject,totalCount,ref currentVal);// this has to be done at this end because objects may have been created and deleted between synchs. If this function is place above then the such a deleted object will not be deleted from the server.
  				if(!PrefC.GetBoolSilent(PrefName.MobileSynchNewTables79Done,true)) {
  				    Prefs.UpdateBool(PrefName.MobileSynchNewTables79Done,true);
  				}
  				if(!PrefC.GetBoolSilent(PrefName.MobileSynchNewTables112Done,true)) {
  				    Prefs.UpdateBool(PrefName.MobileSynchNewTables112Done,true);
  				}
  				if(!PrefC.GetBoolSilent(PrefName.MobileSynchNewTables121Done,true)) {
  					Prefs.UpdateBool(PrefName.MobileSynchNewTables121Done,true);
  				}
  				Prefs.UpdateDateT(PrefName.MobileSyncDateTimeLastRun,timeSynchStarted);
  				IsSynching=false;
  			}
  			catch(Exception e) {
  				IsSynching=false;// this will ensure that the synch can start again. If this variable remains true due to an exception then a synch will never take place automatically.
  				if(Application.OpenForms["FormProgress"]!=null) {// without this line the following error is thrown: "Invoke or BeginInvoke cannot be called on a control until the window handle has been created." or a null pointer exception is thrown when an automatic synch is done by the system.
  					FormP.Invoke(new PassProgressDelegate(PassProgressToDialog),
  						new object[] { 0,"?currentVal of ?maxVal records uploaded",totalCount,e.Message });
  				}
  			}
  		}
  
  		///<summary>a general function to reduce the amount of code for uploading</summary>
  		private static void SynchGeneric(List<long> PKNumList,SynchEntity entity,double totalCount,ref double currentVal) {
  			//Dennis: a try catch block here has been avoid on purpose.
  			List<long> BlockPKNumList=null;
  			int localBatchSize=BatchSize;
  			if(IsTroubleshootMode) {
  				localBatchSize=1;
  			}
  			string AtoZpath=ImageStore.GetPreferredAtoZpath();
  
gt: 			for(int start=0;start<PKNumList.Count;start+=localBatchSize) {