protected void btnPrint_Click(object sender, EventArgs e)
    {
        try
        {
            decimal smsBalance = SMSCreditDataDB.GetTotal() - SMSHistoryDataDB.GetTotal();
            decimal smsCost    = Convert.ToDecimal(SystemVariableDB.GetByDescr("SMSPrice").Value);

            int maxSMSCountCanAfford = smsCost == 0 ? 1000000 : (int)(smsBalance / smsCost);
            int smsCountSending      = 0;


            //
            // Start Validation
            //

            txtEmailSubject.Text     = txtEmailSubject.Text.Trim();
            txtEmailForPrinting.Text = txtEmailForPrinting.Text.Trim();
            txtSMSText.Text          = txtSMSText.Text.Trim();


            bool printSelected = (ddlBothMobileAndEmail.SelectedValue == "1" || ddlEmailNoMobile.SelectedValue == "1" ||
                                  ddlMobileNoEmail.SelectedValue == "1" || ddlNeitherMobileOrEmail.SelectedValue == "1");
            bool emailSelected = (ddlBothMobileAndEmail.SelectedValue == "2" || ddlEmailNoMobile.SelectedValue == "2" ||
                                  ddlMobileNoEmail.SelectedValue == "2" || ddlNeitherMobileOrEmail.SelectedValue == "2");
            bool smsSelected = (ddlBothMobileAndEmail.SelectedValue == "3" || ddlEmailNoMobile.SelectedValue == "3" ||
                                ddlMobileNoEmail.SelectedValue == "3" || ddlNeitherMobileOrEmail.SelectedValue == "3");


            string validationErrors = string.Empty;

            if (printSelected)
            {
                if (txtEmailForPrinting.Text.Length == 0)
                {
                    validationErrors += "<li>Printed Batch Letters Email Address To Send To can not be empty.</li>";
                }
                else if (!Utilities.IsValidEmailAddress(txtEmailForPrinting.Text))
                {
                    validationErrors += "<li>Printed Batch Letters Email Address To Send To must look like a valid email address.</li>";
                }
            }
            if (emailSelected)
            {
                if (txtEmailSubject.Text.Length == 0)
                {
                    validationErrors += "<li>Email Subject can not be empty.</li>";
                }
                if (FreeTextBox1.Text.Length == 0)
                {
                    validationErrors += "<li>Email Text can not be empty.</li>";
                }
            }
            if (smsSelected)
            {
                if (smsCost > 0 && smsBalance == 0)
                {
                    validationErrors += "<li>Can not send SMS's - your SMS balance is empty. Please topup or unselect sending by SMS.</li>";
                }
                else if (txtSMSText.Text.Length == 0)
                {
                    validationErrors += "<li>SMS Text can not be empty.</li>";
                }
            }

            if (validationErrors.Length > 0)
            {
                throw new CustomMessageException("<ul>" + validationErrors + "</ul>");
            }

            //
            // End Validation
            //



            //
            // get hashtables of those with mobiles and emails
            //

            ArrayList regRefIDsArr = new ArrayList();


            foreach (ListItem referrerItem in lstReferrers.Items)  // regrefid
            {
                if (referrerItem.Selected)
                {
                    regRefIDsArr.Add(Convert.ToInt32(referrerItem.Value));
                }
            }


            int[]     regRefIDs    = (int[])regRefIDsArr.ToArray(typeof(int));
            int[]     entityIDs    = RegisterReferrerDB.GetOrgEntityIDs(regRefIDs);
            Hashtable entityIDHash = RegisterReferrerDB.GetOrgEntityIDsHash(regRefIDs);
            Hashtable regRefIDHash = RegisterReferrerDB.GetByIDsInHashtable(regRefIDs);

            Hashtable emailHash  = PatientsContactCacheDB.GetBullkEmail(entityIDs, -1);
            Hashtable mobileHash = PatientsContactCacheDB.GetBullkPhoneNumbers(entityIDs, -1, "30");

            string email_from_address = ((SystemVariables)System.Web.HttpContext.Current.Session["SystemVariables"])["Email_FromEmail"].Value;
            string email_from_name    = ((SystemVariables)System.Web.HttpContext.Current.Session["SystemVariables"])["Email_FromName"].Value;

            //bool StoreLettersHistoryInDB       = Convert.ToBoolean(System.Configuration.ConfigurationManager.AppSettings["StoreLettersHistoryInDB"]);
            //bool StoreLettersHistoryInFlatFile = Convert.ToBoolean(System.Configuration.ConfigurationManager.AppSettings["StoreLettersHistoryInFlatFile"]);
            bool StoreLettersHistoryInDB       = false; // don't store bulk marketing letters
            bool StoreLettersHistoryInFlatFile = false; // don't store bulk marketing letters



            //
            // ok start the sending process
            //


            int bulkLetterSendingQueueBatchID = UseBulkLetterSender ? BulkLetterSendingQueueBatchDB.Insert(txtEmailForPrinting.Text, false) : -1;


            // TODO: Send Letter By Email
            int letterPrintHistorySendMethodID = 1; // send by mail


            // make sure at least one referrer selected
            if (lstReferrers.GetSelectedIndices().Length == 0)
            {
                throw new CustomMessageException("Please select at least one referrer.");
            }

            // make sure at least one letter selected
            if (lstLetters.GetSelectedIndices().Length == 0)
            {
                throw new CustomMessageException("Please select a letter.");
            }


            // get letter and make sure it exists
            Letter letter             = LetterDB.GetByID(Convert.ToInt32(lstLetters.SelectedValue));
            string sourchTemplatePath = letter.GetFullPath(Convert.ToInt32(Session["SiteID"]));
            if (!File.Exists(sourchTemplatePath))
            {
                throw new CustomMessageException("File doesn't exist.");
            }

            // get temp directory
            string tmpLettersDirectory = Letter.GetTempLettersDirectory();
            if (!Directory.Exists(tmpLettersDirectory))
            {
                throw new CustomMessageException("Temp letters directory doesn't exist");
            }

            // delete old tmp files
            FileHelper.DeleteOldFiles(tmpLettersDirectory, new TimeSpan(1, 0, 0));



            // create individual merged docs and put into list of docsToMerge - only if there is an org-patient relationship
            ArrayList docsToMerge = new ArrayList();

            Site site    = SiteDB.GetByID(Convert.ToInt32(Session["SiteID"]));
            int  StaffID = Convert.ToInt32(Session["StaffID"]);
            foreach (ListItem referrerItem in lstReferrers.Items)
            {
                if (!referrerItem.Selected)
                {
                    continue;
                }

                if (UseBulkLetterSender)
                {
                    int        refEntityID    = (int)entityIDHash[Convert.ToInt32(referrerItem.Value)];
                    string     refPhoneNumber = GetPhoneNbr(mobileHash, refEntityID, true);
                    string     refEmail       = GetEmail(emailHash, refEntityID);
                    SendMethod sendMethod     = GetSendMethod(refEmail != null, refPhoneNumber != null);

                    RegisterReferrer regRef = RegisterReferrerDB.GetByID(Convert.ToInt32(referrerItem.Value));

                    if (sendMethod != SendMethod.None)
                    {
                        string text = string.Empty;
                        if (sendMethod == SendMethod.SMS)
                        {
                            text = txtSMSText.Text;
                        }
                        if (sendMethod == SendMethod.Email)
                        {
                            text = FreeTextBox1.Text;
                        }

                        text = ReplaceMergeFields(text, regRefIDHash, Convert.ToInt32(referrerItem.Value));

                        bool generateLetter = false;
                        if (sendMethod == SendMethod.SMS)
                        {
                            generateLetter = false;
                        }
                        if (sendMethod == SendMethod.Email)
                        {
                            generateLetter = lstLetters.GetSelectedIndices().Length != 0;
                        }
                        if (sendMethod == SendMethod.Print)
                        {
                            generateLetter = true;
                        }


                        if (sendMethod == SendMethod.SMS)  // copy to other methods!!
                        {
                            smsCountSending++;
                        }


                        BulkLetterSendingQueueDB.Insert
                        (
                            bulkLetterSendingQueueBatchID,
                            (int)sendMethod,                                              // bulk_letter_sending_queue_method_id
                            StaffID,                                                      // added_by
                            -1,                                                           // patient_id
                            regRef.Referrer.ReferrerID,                                   // referrer_id
                            -1,                                                           // booking_id
                            (sendMethod == SendMethod.SMS)   ? refPhoneNumber       : "", // phone_number
                            (sendMethod == SendMethod.Email) ? refEmail             : "", // email_to_address
                            "",                                                           // email_to_name
                            (sendMethod == SendMethod.Email) ? email_from_address   : "", // email_from_address
                            (sendMethod == SendMethod.Email) ? email_from_name      : "", // email_from_name
                            text,                                                         // text
                            (sendMethod == SendMethod.Email) ? txtEmailSubject.Text : "", // email_subject
                            "",                                                           // email_attachment_location
                            false,                                                        // email_attachment_delete_after_sending
                            false,                                                        // email_attachment_folder_delete_after_sending

                            !generateLetter ? -1    : letter.LetterID,
                            !generateLetter ? false : chkKeepInHistory.Checked && StoreLettersHistoryInDB,
                            !generateLetter ? false : chkKeepInHistory.Checked && StoreLettersHistoryInFlatFile,
                            !generateLetter ? -1    : letterPrintHistorySendMethodID,
                            !generateLetter ? ""    : Letter.GetLettersHistoryDirectory(0),
                            !generateLetter ? ""    : letter.Docname.Replace(".dot", ".doc"),
                            !generateLetter ? -1    : site.SiteID,
                            0,                                                             // organisation_id
                            -1,                                                            // booking id
                            -1,                                                            // patient_id

                            !generateLetter ? -1    : Convert.ToInt32(referrerItem.Value), // register_referrer_id_to_use_instead_of_patients_reg_ref
                            !generateLetter ? -1    : StaffID,
                            -1,                                                            //healthcardactionid
                            !generateLetter ? ""    : sourchTemplatePath,
                            !generateLetter ? ""    : tmpLettersDirectory + letter.Docname.Replace(".dot", ".doc"),
                            !generateLetter ? false : true,

                            "",    // email_letter_extra_pages
                            "",    // email_letter_item_seperator
                            "",    // sql_to_run_on_completion
                            ""     // sql_to_run_on_failure
                        );
                    }
                }
                else
                {
                    // create doc
                    string tmpSingleFileName = Letter.CreateMergedDocument(
                        letter.LetterID,
                        chkKeepInHistory.Checked && StoreLettersHistoryInDB,
                        chkKeepInHistory.Checked && StoreLettersHistoryInFlatFile,
                        letterPrintHistorySendMethodID,
                        Letter.GetLettersHistoryDirectory(0),
                        letter.Docname.Replace(".dot", ".doc"),
                        site,
                        0,  // org id
                        -1, // booking id
                        -1, // patient id
                        Convert.ToInt32(referrerItem.Value),
                        StaffID,
                        -1, //healthcardactionid
                        sourchTemplatePath,
                        tmpLettersDirectory + letter.Docname.Replace(".dot", ".doc"),
                        true);

                    // record name of merged doc
                    docsToMerge.Add(tmpSingleFileName);
                }
            }


            if (UseBulkLetterSender)
            {
                if ((smsCountSending * smsCost) > smsBalance)
                {
                    BulkLetterSendingQueueDB.DeleteByBatchID(bulkLetterSendingQueueBatchID);
                    BulkLetterSendingQueueBatchDB.Delete(bulkLetterSendingQueueBatchID);

                    SetErrorMessage("Not Enough Credit To Send SMS's. Please Top Up You SMS Credit or Choose Methods Other Than SMS.");
                    return;
                }

                BulkLetterSendingQueueBatchDB.UpdateReadyToProcess(bulkLetterSendingQueueBatchID, true);
                SetErrorMessage("Items Added To Sending Queue. View Details <a href='/Letters_PrintBatch_StatusV2.aspx?batch_id=" + bulkLetterSendingQueueBatchID + "'>Here</a>");
            }
            else
            {
                // merge all tmp files
                string tmpFinalFileName = Letter.MergeMultipleDocuments(
                    ((string[])docsToMerge.ToArray(typeof(string))),
                    tmpLettersDirectory + letter.Docname.Replace(".dot", ".doc"));

                // delete all single tmp files
                foreach (string file in docsToMerge)
                {
                    File.Delete(file);
                }

                // download the document
                byte[] fileContents = File.ReadAllBytes(tmpFinalFileName);
                System.IO.File.Delete(tmpFinalFileName);

                // Nothing gets past the "DownloadDocument" method because it outputs the file
                // which is writing a response to the client browser and calls Response.End()
                // So make sure any other code that functions goes before this
                Letter.DownloadDocument(Response, fileContents, letter.Docname.Replace(".dot", ".doc"));
            }
        }
        catch (CustomMessageException cmEx)
        {
            SetErrorMessage(cmEx.Message);
            return;
        }
    }
    protected void Run(bool viewListOnly)
    {
        bool debugMode = true;

        int bulkLetterSendingQueueBatchID = !viewListOnly && UseBulkLetterSender?BulkLetterSendingQueueBatchDB.Insert(DebugEmail, false) : -1;

        //
        //  We can not send email all their patients in one email - will be too big with attachments and rejected by their mail provider
        //  So if via email - need to send one at a time
        //  Then if cuts out or times out, it has processed some so don't need to re-process those when it's run again
        //
        //  just remember to process the emails first ... so if any interruptions/errors ... at least some will have been processed
        //



        try
        {
            bool AutoSendFaxesAsEmailsIfNoEmailExistsToGPs = Convert.ToInt32(SystemVariableDB.GetByDescr("AutoSendFaxesAsEmailsIfNoEmailExistsToGPs").Value) == 1;

            string sendMethod = rdioSendType.SelectedValue;
            if (!viewListOnly && SelectedSendMethod == SendMethod.None)
            {
                throw new CustomMessageException("Send method not selected");
            }

            string tmpLettersDirectory = Letter.GetTempLettersDirectory();
            if (!Directory.Exists(tmpLettersDirectory))
            {
                throw new CustomMessageException("Temp letters directory doesn't exist");
            }


            string debugOutput = string.Empty;
            int    startTime   = Environment.TickCount;



            // NB.
            // start/emd date time __must__ refer to the treatment date because
            // as a letter can be generated any time (days or weeks) after a treatment, there is no way
            // to be sure which invoice it is attached to, only which booking
            if (txtStartDate.Text.Length > 0 && !Utilities.IsValidDate(txtStartDate.Text, "dd-mm-yyyy"))
            {
                throw new CustomMessageException("Start date must be empty or valid and of the format dd-mm-yyyy");
            }
            if (txtEndDate.Text.Length > 0 && !Utilities.IsValidDate(txtEndDate.Text, "dd-mm-yyyy"))
            {
                throw new CustomMessageException("End date must be empty or valid and of the format dd-mm-yyyy");
            }
            DateTime startDate = txtStartDate.Text.Length == 0 ? DateTime.MinValue : Utilities.GetDate(txtStartDate.Text, "dd-mm-yyyy");
            DateTime endDate   = txtEndDate.Text.Length == 0 ? DateTime.MinValue : Utilities.GetDate(txtEndDate.Text, "dd-mm-yyyy");


            DataTable bookingsWithSentLetters = BookingDB.GetBookingsWithEPCLetters(startDate, endDate, Convert.ToInt32(registerReferrerID.Value), Convert.ToInt32(patientID.Value), true, false);

            if (!viewListOnly && bookingsWithSentLetters.Rows.Count > MaxSending)
            {
                throw new CustomMessageException("Can not generate more than " + MaxSending + " letters at a time. Please narrow your date range.");
            }

            double queryExecutionTime = (double)(Environment.TickCount - startTime) / 1000.0;
            startTime = Environment.TickCount;


            ArrayList filesToPrint = new ArrayList();

            int       currentRegReferrerID       = -1;
            ArrayList bookingsForCurrentReferrer = new ArrayList();
            foreach (DataRow row in bookingsWithSentLetters.Rows)
            {
                Tuple <Booking, PatientReferrer, bool, bool, HealthCard> rowData = LoadRow(row);
                Booking         booking     = rowData.Item1;
                PatientReferrer pr          = rowData.Item2;
                bool            refHasEmail = rowData.Item3;
                bool            refHasFax   = rowData.Item4;
                HealthCard      hc          = rowData.Item5;


                if (pr.RegisterReferrer.RegisterReferrerID != currentRegReferrerID)
                {
                    filesToPrint.AddRange(ProcessReferrersLetters(viewListOnly, bookingsForCurrentReferrer, AutoSendFaxesAsEmailsIfNoEmailExistsToGPs, ref debugOutput, bulkLetterSendingQueueBatchID));
                    currentRegReferrerID       = pr.RegisterReferrer.RegisterReferrerID;
                    bookingsForCurrentReferrer = new ArrayList();
                }

                bookingsForCurrentReferrer.Add(rowData);
            }

            // process last group
            filesToPrint.AddRange(ProcessReferrersLetters(viewListOnly, bookingsForCurrentReferrer, AutoSendFaxesAsEmailsIfNoEmailExistsToGPs, ref debugOutput, bulkLetterSendingQueueBatchID));



            bool zipSeperately = true;

            if (zipSeperately && filesToPrint.Count > 0)
            {
                // seperate into doc types because can only merge docs with docs of same template (ie docname)
                Hashtable filesToPrintHash = new Hashtable();
                for (int i = 0; i < filesToPrint.Count; i++)
                {
                    Letter.FileContents curFileContents = (Letter.FileContents)filesToPrint[i];
                    if (filesToPrintHash[curFileContents.DocName] == null)
                    {
                        filesToPrintHash[curFileContents.DocName] = new ArrayList();
                    }
                    ((ArrayList)filesToPrintHash[curFileContents.DocName]).Add(curFileContents);
                }

                // merge and put merged files into temp dir
                string baseTmpDir = FileHelper.GetTempDirectoryName(tmpLettersDirectory);
                string tmpDir     = baseTmpDir + "EPC Letters" + @"\";
                Directory.CreateDirectory(tmpDir);
                string[] tmpFiles = new string[filesToPrintHash.Keys.Count];
                IDictionaryEnumerator enumerator = filesToPrintHash.GetEnumerator();
                for (int i = 0; enumerator.MoveNext(); i++)
                {
                    ArrayList files   = (ArrayList)enumerator.Value;
                    string    docName = (string)enumerator.Key;


                    // last file is screwing up, so just re-add the last file again for a temp fix
                    files.Add(files[files.Count - 1]);


                    Letter.FileContents fileContents = Letter.FileContents.Merge((Letter.FileContents[])files.ToArray(typeof(Letter.FileContents)), docName); // .pdf

                    string tmpFileName = tmpDir + fileContents.DocName;
                    System.IO.File.WriteAllBytes(tmpFileName, fileContents.Contents);
                    tmpFiles[i] = tmpFileName;
                }

                // zip em
                string zipFileName = "EPC Letters.zip";
                string zipFilePath = baseTmpDir + zipFileName;
                ICSharpCode.SharpZipLib.Zip.FastZip zip = new ICSharpCode.SharpZipLib.Zip.FastZip();
                zip.CreateEmptyDirectories = true;
                zip.CreateZip(zipFilePath, tmpDir, true, "");

                // get filecontents of zip here
                Letter.FileContents zipFileContents = new Letter.FileContents(zipFilePath, zipFileName);
                Session["downloadFile_Contents"] = zipFileContents.Contents;
                Session["downloadFile_DocName"]  = zipFileContents.DocName;

                // delete files
                for (int i = 0; i < tmpFiles.Length; i++)
                {
                    System.IO.File.SetAttributes(tmpFiles[i], FileAttributes.Normal);
                    System.IO.File.Delete(tmpFiles[i]);
                }
                System.IO.File.SetAttributes(zipFilePath, FileAttributes.Normal);
                System.IO.File.Delete(zipFilePath);
                System.IO.Directory.Delete(tmpDir, false);
                System.IO.Directory.Delete(baseTmpDir, false);

                // put in session variables so when it reloads to this page, we can popup the download window
                Page.ClientScript.RegisterStartupScript(this.GetType(), "download", "<script language=javascript>window.open('DownloadFile.aspx','_blank','status=1,toolbar=0,menubar=0,location=1,scrollbars=1,resizable=1,width=30,height=30');</script>");
            }

            if (!zipSeperately && filesToPrint.Count > 0)
            {
                Letter.FileContents fileContents = Letter.FileContents.Merge((Letter.FileContents[])filesToPrint.ToArray(typeof(Letter.FileContents)), "Referral Letters.doc");  // .pdf
                Session["downloadFile_Contents"] = fileContents.Contents;
                Session["downloadFile_DocName"]  = fileContents.DocName;

                // put in session variables so when it reloads to this page, we can popup the download window
                Page.ClientScript.RegisterStartupScript(this.GetType(), "download", "<script language=javascript>window.open('DownloadFile.aspx','_blank','status=1,toolbar=0,menubar=0,location=1,scrollbars=1,resizable=1,width=30,height=30');</script>");
            }


            double restExecutionTime = (double)(Environment.TickCount - startTime) / 1000.0;

            if (debugMode)
            {
                if (!viewListOnly)
                {
                    lblInfo.Text = @"<table cellpadding=""0"">
                                    <tr><td><b>Send Method</b></td><td style=""width:10px;""></td><td>" + SelectedSendMethod.ToString() + @"</td><td style=""width:25px;""></td><td><b>Query Time</b></td><td style=""width:10px;""></td><td>" + queryExecutionTime + @" seconds</td></tr>
                                    <tr><td><b>Count</b></td><td style=""width:10px;""></td><td>" + bookingsWithSentLetters.Rows.Count + @"</td><td style=""width:25px;""></td><td><b>Runing Time</b></td><td style=""width:10px;""></td><td>" + restExecutionTime + @" seconds</td></tr>
                                 </table>";
                }

                string countShowing = bookingsWithSentLetters.Rows.Count > MaxSending ? bookingsWithSentLetters.Rows.Count + " showing to re-generate. <br /><font color=red>* You can not generate more than 175 at a time. Please narrow your search before printing.</font>" : bookingsWithSentLetters.Rows.Count.ToString();
                if (viewListOnly)
                {
                    lblInfo.Text = @"<table cellpadding=""0"">
                                    <tr><td valign=""top""><b>Count</b></td><td style=""width:10px;""></td><td>" + countShowing + @"</td></tr>
                                 </table>";
                }


                if (viewListOnly)
                {
                    lblList.Text = @"<table class=""table table-bordered table-striped table-grid table-grid-top-bottum-padding-thick auto_width block_center"" cellpadding=""4"" border=""1"">
                                        <tr>
                                            <th style=""white-space:nowrap;"">Send By</th>
                                            <th>Booking</th>
                                            <th>Generate</th>
                                            <th>Referrer</th>
                                            <th>Email</th>
                                            <th>Fax</th>
                                            <th>Patient</th>
                                        </tr>" +
                                   (debugOutput.Length == 0 ? "<tr><td colspan=\"7\">No Rows</td></tr>" : debugOutput) +
                                   "</table>";
                }
            }
        }
        catch (CustomMessageException cmEx)
        {
            SetErrorMessage(cmEx.Message);
        }
        catch (Exception ex)
        {
            SetErrorMessage("", ex.ToString());
        }
    }