using System;
using System.Windows.Forms;
using TotalRecorderLib;

namespace RecPlaySave
{
	/// <summary>
	/// Supports syncronized data for async operation
	/// </summary>
	public delegate void InvokeDelegate();

	[System.Runtime.Remoting.Contexts.Synchronization]
	public partial class MainForm: Form
	{
		#region Consts
		private const double SECONDS_TO_RECORD = 10.0;
		private enum Stages {
			stNothing, stRecording, stAfterRecording, stPlaying, stAfterPlaying };
		#endregion

		#region Private members
		private TotalRecorderApplication m_TRApp = null;
		private TotalRecorderDocument    m_TRDoc = null;

		private _ITotalRecorderApplicationEvents_OnMajorEventEventHandler
			m_TRMajorEvent = null;
		private _ITotalRecorderApplicationEvents_OnMinorEventEventHandler
			m_TRMinorEvent = null;

		private bool m_TRSavedMessageMode = false;
		private TotalRecorderRecordingParameters m_TRSavedRCP = null;
		private Stages m_Stage = Stages.stNothing;

		private double m_SyncPosition = 0;
		private double m_SyncDuration = 0;
		private string m_SyncStatus = "";
		private string m_SyncError = "";
		#endregion

		private void ShowClientErr(string ErrMess)
		{
			MessageBox.Show(ErrMess, this.Text, MessageBoxButtons.OK,
				MessageBoxIcon.Error);
		}

		#region MainForm constructor
		public MainForm()
		{
			InitializeComponent();
		}
		#endregion

		#region MainForm async operation
		private void SetSyncData(double Position, double Duration, string Status)
		{
			lock (this)
			{
				m_SyncPosition = Position;
				m_SyncDuration = Duration;
				m_SyncStatus   = Status;
			}
			BeginInvoke(new InvokeDelegate(InvokeData));
		}

		private void InvokeData()
		{
			lock (this)
			{
				lbCurPos.Text   = m_SyncPosition.ToString("N1");
				lbDuration.Text = m_SyncDuration.ToString("N1");
				lbStatus.Text   = m_SyncStatus;
			}
		}

		private void AddServerError(string Error)
		{
			lock (this)
			{
				m_SyncError += Error + "\n\n";
			}
			BeginInvoke(new InvokeDelegate(InvokeError));
		}

		private void InvokeError()
		{
			lock (this)
			{
				tbErrors.Text = m_SyncError.Replace("\n", "\r\n");
			}
		}
		#endregion

		#region Total Recorder event handlers
		private void ProcessEvent()
		{
			TotalRecorderApplication TRApp = null;
			TotalRecorderDocument    TRDoc = null;

			lock (this)
			{
				TRApp = m_TRApp;
				TRDoc = m_TRDoc;
			}

			try
			{
				if (TRApp == null || TRDoc == null)
					return;
				if (m_Stage == Stages.stNothing)
					return;

				string LastError = TRApp.LastErrorMessage;
				while (LastError != null)
				{
					AddServerError(LastError);
					LastError = TRApp.LastErrorMessage;
				}

				SetSyncData(TRApp.Position, TRDoc.Duration,
					TRApp.get_StatusMessage(0));

				switch (m_Stage)
				{
					case Stages.stRecording:
						if (! TRApp.Recording) // was error
						{
							m_Stage = Stages.stNothing;
							BeginInvoke(new InvokeDelegate(ReleaseServer));
							return;
						}
						if (TRDoc.Duration >= SECONDS_TO_RECORD)
						{
							m_Stage = Stages.stAfterRecording;  // will be recursive calls
							TRApp.Recording = false;
							TRApp.Position  = 0.0;
							TRApp.Playing   = true;
							if (! TRApp.Playing)
							{
								m_Stage = Stages.stNothing;
								ShowClientErr("Failed to start playing.");
								BeginInvoke(new InvokeDelegate(ReleaseServer));
								return;
							}
							m_Stage = Stages.stPlaying;
						}
						break;

					case Stages.stPlaying:
						if (! TRApp.Playing) // end of playing
						{
							m_Stage = Stages.stAfterPlaying;
							BeginInvoke(new InvokeDelegate(SaveChanges));
						}
						break;
				}
			}
			catch (Exception ex)
			{
				ShowClientErr("Error on process Total Recorder event:\n"
					+ ex.Message);
			}
		}
		#endregion

		#region Total Recorder Automation
		private bool LoadServer()
		{
			bool HasError = false;
			try
			{
				try
				{
					m_TRApp = new TotalRecorderApplication();
				}
				catch (Exception ex)
				{
					throw new Exception("Unable to load Total Recorder server:\n\n"
						+ ex.Message);
				}

				m_TRMajorEvent = new
					_ITotalRecorderApplicationEvents_OnMajorEventEventHandler(
					ProcessEvent);
				m_TRMinorEvent = new
					_ITotalRecorderApplicationEvents_OnMinorEventEventHandler(
					ProcessEvent);

				_ITotalRecorderApplicationEvents_Event EventsIntf =
					(_ITotalRecorderApplicationEvents_Event) m_TRApp;

				EventsIntf.OnMajorEvent += m_TRMajorEvent;
				EventsIntf.OnMinorEvent += m_TRMinorEvent;

				m_TRSavedMessageMode = m_TRApp.MessageMode;
				btRunSample.Enabled  = false;
			}
			catch (Exception ex)
			{
				HasError = true;
				ShowClientErr(ex.Message);
				ReleaseServer();
			}
			return ! HasError;
		}

		private void ReleaseEvents()
		{
			lock (this)
			{
				if (m_TRApp        == null
				 || m_TRMajorEvent == null
				 || m_TRMinorEvent == null)
					return;

				_ITotalRecorderApplicationEvents_Event EventsIntf =
					(_ITotalRecorderApplicationEvents_Event) m_TRApp;
				EventsIntf.OnMajorEvent -= m_TRMajorEvent;
				EventsIntf.OnMinorEvent -= m_TRMinorEvent;
				m_TRMajorEvent = null;
				m_TRMinorEvent = null;
			}
		}

		private void ReleaseServer()
		{
			try
			{
				ReleaseEvents();

				lock (this)
				{
					if (m_TRApp == null)
						return;

					m_TRApp.MessageMode = m_TRSavedMessageMode;
					if (m_TRSavedRCP != null)
						m_TRApp.RecordingParameters = m_TRSavedRCP;
					m_TRSavedRCP = null;

					if (m_TRDoc != null)
					{
						if (m_Stage != Stages.stNothing)
						{
							m_TRApp.Playing   = false;
							m_TRApp.Recording = false;
						}
						object Save = (bool)false;
						m_TRDoc.Close(Save, null);
					}

					m_TRDoc = null;
					m_TRApp = null;
				}

				GC.Collect();  // to free Total Recorder server
				SetSyncData(0, 0, "");
			}
			catch (Exception ex)
			{
				ShowClientErr(ex.Message);
			}

			m_Stage = Stages.stNothing;
			btRunSample.Enabled = true;
		}

		private void StartRecording()
		{
			try
			{
				m_TRApp.MessageMode = true;
				if (m_TRApp.AutoJob)
					throw new Exception(
						"Total recorder is busy - it is in AutoJob mode.");
				if (m_TRApp.Recording)
					throw new Exception(
						"Total recorder is busy - it is recording now.");
				if (m_TRApp.ModalDialog)
					throw new Exception(
						"Total recorder is busy - modal dialog is active.");
				if (! m_TRApp.ActiveDocument.Saved)
					throw new Exception(
						"Can not start recording - current document is not saved.");

				m_TRDoc = m_TRApp.ActiveDocument;
				m_TRDoc.Close(null, null);

				TotalRecorderRecordingParameters RCPNew =
					new TotalRecorderRecordingParameters();
				TotalRecorderSoundFormat RCFmtNew =
					new TotalRecorderSoundFormat();

				RCFmtNew.Clear();
				RCFmtNew.FormatTag     = 1;     // PCM
				RCFmtNew.SamplesPerSec = 11025; // 11025 Hz
				RCFmtNew.BitsPerSample = 8;     // 8 bit
				RCFmtNew.Channels      = 1;     // mono
				RCFmtNew.Validate();
				if (! RCFmtNew.Valid)
					throw new Exception("Can not set format to phone quality.");

				RCPNew.Source   = 1;    // SoundBoard
				RCPNew.UseMixer = true; // Use curr.mixer settings
				RCPNew.Format   = RCFmtNew;

				m_TRSavedRCP = m_TRApp.RecordingParameters;
				m_TRApp.RecordingParameters = RCPNew;
				m_TRApp.Recording = true;
				if (! m_TRApp.Recording)
					throw new Exception("Failed to start recording.");
				m_Stage = Stages.stRecording;
				SetSyncData(0, 0, "");
			}
			catch (Exception ex)
			{
				ShowClientErr(ex.Message);
				ReleaseServer();
			}
		}

		private void SaveChanges()
		{
			try
			{
				SaveFileDialog SaveDlg = new SaveFileDialog();
				SaveDlg.Filter = "Wave files (*.wav)|*.wav|All files (*.*)|*.*";
				SaveDlg.FilterIndex = 1;
				SaveDlg.FileName = "TestFile";
				SaveDlg.RestoreDirectory = true;
				if (SaveDlg.ShowDialog() == DialogResult.OK)
				{
					ReleaseEvents();
					m_TRDoc.SaveAs(SaveDlg.FileName, null);
				}
			}
			catch (Exception ex)
			{
				ShowClientErr(ex.Message);
			}

			ReleaseServer();
		}
		#endregion

		#region MainForm control events
		private void btRunSample_Click(object sender, EventArgs e)
		{
			if (LoadServer())
			{
				StartRecording();
			}
		}

		private void btExit_Click(object sender, EventArgs e)
		{
			Close();
		}

		private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
		{
			ReleaseServer();
		}
		#endregion

	}
}
