// (C) Фролов Г.В., 1995
// Адрес E-mail: frolov@glas.apc.org

#define 	STRICT

#include	<windows.h>
#include <string.h>
#include <stdlib.h>
#include	<mem.h>
#include	<bwcc.h>
#include	"phone.h"


// Глобальные переменные

HINSTANCE	hInst;			// Идентификатор приложения
int			idComDev;		// Идентификатор COM-порта
char			sBufNum[20];	// Буфер для телефонного номера
BOOL			WaitFlag = FALSE;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR     lpszCmdLine,
	int       nCmdShow)
{
	static DLGPROC lpfnDlgProc;

	hInst = hInstance;

	// Переходник для функции диалоговой панели
	lpfnDlgProc = (DLGPROC)MakeProcInstance((FARPROC)DlgProc, hInst);

	// Создаем модальную диалоговую панель
	DialogBox( hInstance, "PHONE", NULL, lpfnDlgProc );

	return 0;
}


// =====================================
// Функция DldProc
// =====================================

#pragma argsused

BOOL CALLBACK _export
DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	int	iErr;

	switch(msg)
	{
		// Инициализация диалоговой панели
		case WM_INITDIALOG:
		{
			// Посылаем сообщение WM_CONNECT
			PostMessage( hdlg,WM_CONNECT,0,0L );
			return TRUE;
		}

		case WM_CONNECT:
		{
			EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE);

			// Открываем COM-порт и инициализируем модем
			iErr = InitLine();

			EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE);

			// Если возникла ошибка отображаем сообщение
			// и закрываем диалоговую панель
			if( iErr < 0 )
			{
				ShowError( iErr );
				EndDialog( hdlg, 0 );
			}
			return TRUE;
		}

		case WM_COMMAND:
		{
			switch(wParam)
			{
				// Нажата кнопка номеронаберателя
				case	ID_1: case	ID_2: case	ID_3: case	ID_4: case	ID_5:
				case	ID_6:	case	ID_7: case	ID_8: case	ID_9: case	ID_0:
				{
					int	iNum;
					char	sTmp[10];

					// Определяем набранную цифру
					iNum = wParam - ID_0;

					wsprintf( sTmp, "%d", iNum );

					// Добавляем набранную цифру к номеру
					lstrcat(sBufNum, sTmp);

					// Обновляем номер на экране
					SetDlgItemText(hdlg, ID_NUMBER, sBufNum);

					return TRUE;
				}

				// Нажата кнопка "Сброс"
				case ID_CLEAR:
				{
					EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE);

					// Вешаем трубку
					HangUpPhone();

					EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE);

					return TRUE;
				}

				// Удаляем последную цифру номера
				case ID_BACK:
				{
					if( lstrlen( sBufNum ) != 0 )
					{
						sBufNum[lstrlen( sBufNum ) - 1] = '\0';
						SetDlgItemText( hdlg, ID_NUMBER, sBufNum );
					}
					else MessageBeep( MB_ICONASTERISK );

					return TRUE;
				}

				// Нажата кнопка "Набор"
				case ID_DIAL:
				{
					EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE);

					// Набираем номер
					iErr = DialPhone();

					// Если возникла ошибка отображаем сообщение
					if( iErr < 0 )
						ShowError( iErr );

					EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE);
					return TRUE;
				}

				// Нажата кнопка "Выход"
				case IDCANCEL:
				{
					// Закрываем COM-порт
					CloseLine();

					// Закрываем диалоговую панель
					EndDialog( hdlg, 0 );

					return TRUE;
				}
			}
		}
	}
	return FALSE;
}

// =====================================
// Функция InitLine
// =====================================
int	InitLine()
{
	BYTE	bSet;
	DCB	dcb;
	int	iErr;
	char	sTmp[200];
	char	szModemAnswer[200];

	char	szInitMode[80];
	char	szTmpStrBool[3];
	char	szInitModem[100];
	long	lnModemTimeout;

	// Определяем режим работы COM-порта, для этого считываем
	// строку Mode из раздела Port файла phone.ini
	GetPrivateProfileString( "Port", "Mode", "COM1:9600,n,8,1",
									 szInitMode, sizeof(szInitMode),
									 "phone.ini" );

	// Открываем COM-порт, заданный в строке szInitMode
	wsprintf( sTmp, "COM%c", szInitMode[3] );

	idComDev = OpenComm( sTmp, 4915, 4915 );
	if (idComDev < 0)
		return ERR_NO_OPEN;

	// Заполняем структуру DCB в соответствии с szInitMode
	iErr = BuildCommDCB( szInitMode, &dcb );
	if(iErr < 0)
		return ERR_DCB_BUILD;

	// Изменяем отдельные поля структуры DCB в
	//	соответствии с файлом phone.ini

	// Заполняем поля fOutxDsrFlow, fDtrflow, DsrTimeout
	GetPrivateProfileString( "Port", "DsrDtrFlow", "1",
									 szTmpStrBool, sizeof(szTmpStrBool),
									 "phone.ini" );
	
	dcb.fOutxDsrFlow = dcb.fDtrflow = bSet =
									(BYTE) atoi(szTmpStrBool);

	dcb.DsrTimeout = (bSet) ? 3 : 0 ;

	// Заполняем поля fOutxCtsFlow, fRtsflow, CtsTimeout
	GetPrivateProfileString( "Port", "CtsRtsFlow", "1",
									 szTmpStrBool, sizeof(szTmpStrBool),
									 "phone.ini" );

	dcb.fOutxCtsFlow = dcb.fRtsflow = bSet =
									(BYTE) atoi(szTmpStrBool);

	dcb.CtsTimeout = (bSet) ? 3 : 0 ;

	// Заполняем поле fBinary
	GetPrivateProfileString( "Port", "Binary", "1",
									 szTmpStrBool, sizeof(szTmpStrBool),
									 "phone.ini" );
	
	dcb.fBinary = (BOOL) atoi(szTmpStrBool);

	// Заполняем поле fRtsDisable
	GetPrivateProfileString( "Port", "RtsDisable", "0",
									 szTmpStrBool, sizeof(szTmpStrBool),
									 "phone.ini" );

	dcb.fRtsDisable = (BOOL) atoi(szTmpStrBool);


	// Заполняем поле fDtrDisable
	GetPrivateProfileString( "Port", "DtrDisable", "0",
									 szTmpStrBool, sizeof(szTmpStrBool),
									 "phone.ini" );
	
	dcb.fDtrDisable = (BOOL) atoi(szTmpStrBool);

	// Устанавливаем новый режим работы COM-порта
	iErr = SetCommState( &dcb );
	if( iErr < 0 )
		return ERR_DCB_SET;

	// Удаляем данные из входной и выходной очередей COM-порта
	FlushComm(idComDev, 1);
	FlushComm(idComDev, 0);

	// Подаем сигнал DTR
	EscapeCommFunction(idComDev, SETDTR);

	// Определяем команду инициализации модема, для этого считываем
	// строку Init из раздела Modem файла phone.ini
	GetPrivateProfileString( "Modem", "Init", "ATZ",
									 szInitModem, sizeof(szInitModem),
									 "phone.ini" );

	// Добавляем к команде символ перевода строки
	wsprintf( sTmp, "%s\r", szInitModem );

	// Передаем команду инициализации модему
	iErr = WriteComm( idComDev, sTmp, lstrlen( sTmp ) );
	if( iErr < 0 )
		return ERR_WRITE;

	// Определяем время ожидания ответа от модема
	GetPrivateProfileString( "Modem", "ModemTimeout", "1000",
									 sTmp, sizeof(sTmp),
									 "phone.ini" );

	lnModemTimeout = atol( sTmp );

	// Ожидаем от модема ответ "OK"
	iErr = WaitModemAnswer(	idComDev, (LPSTR*)szOkString,
									szModemAnswer, 200, (DWORD)lnModemTimeout);
	if( iErr < 0 )
		return iErr;

	return 0;
}

// =====================================
// Функция
// =====================================

int	DialPhone()
{
	int	iErr;
	char	sTmp[80];
	char	szModemAnswer[200];
	char	szDialModem[80];
	long	lnModemTimeout;


	// Определяем команду набора номера, для этого считываем
	// строку Dial из раздела Modem файла phone.ini
	GetPrivateProfileString( "Modem", "Dial", "ATDP",
									 szDialModem, sizeof(szDialModem),
									 "phone.ini" );

	// Формируем во временном буфере sTmp команду набора номера
	wsprintf( sTmp, "%s%s\r", szDialModem, sBufNum );

	// Удаляем данные из входной и выходной очередей COM-порта
	FlushComm(idComDev, 1);
	FlushComm(idComDev, 0);

	// Передаем модему команду набора номера
	iErr = WriteComm(idComDev, sTmp, lstrlen(sTmp) );
	if( iErr < 0 )
		return ERR_WRITE;

	// Определяем время ожидания ответа от модема
	GetPrivateProfileString( "Modem", "DialTimeout", "30000",
									 sTmp, sizeof(sTmp),
									 "phone.ini" );

	lnModemTimeout = atol( sTmp );

	// Ожидаем ответ от модема
	iErr = WaitModemAnswer(	idComDev, (LPSTR*)szAnswer,
								szModemAnswer, 200, (DWORD)lnModemTimeout);

	BWCCMessageBox(NULL, szModemAnswer, "Ответ модема", MB_OK );

	return iErr;
}

// =====================================
// Функция CloseLine
// =====================================

int	CloseLine()
{
	// Сбрасывааем сигнал DTR
	EscapeCommFunction(idComDev, CLRDTR);

	// Удаляем данные из входной и выходной очередей COM-порта
	FlushComm(idComDev, 1);
	FlushComm(idComDev, 0);

	// Закрываем COM-порт
	CloseComm(idComDev);

	return 0;
}


// ===================================================================
// Функция HangUp
// ===================================================================

int	HangUpPhone()
{
	DWORD	StartTick;
	char	szHangUpString[80], sTmp[80];
	long	lnModemTimeout;

	// Определяем время задержки перед подачей команды "+++"
	GetPrivateProfileString( "Modem", "HangUpTimeout", "2000",
									 sTmp, sizeof(sTmp),
									 "phone.ini" );

	lnModemTimeout = atol( sTmp );


	// Определяем команду разрыва связи, для этого считываем
	// строку Dial из раздела Modem файла phone.ini
	GetPrivateProfileString( "Modem", "HangUp", "ATH0",
									 szHangUpString, sizeof(szHangUpString),
									 "phone.ini" );

	// Формируем во временном буфере sTmp команду разрыва соединения
	wsprintf( sTmp, "%s\r", szHangUpString );

	// Определяем текущий момент времени
	StartTick = GetTickCount();

	// Формируем задержку
	while( StartTick + (DWORD)lnModemTimeout > GetTickCount() ) Idle();

	// Передаем модему последовательность "+++" для перевода его
	// в командный режим
	WriteComm( idComDev, "+++", 3);

	// Определяем текущий момент времени
	StartTick = GetTickCount();

	// Формируем задержку
	while( StartTick + (DWORD)lnModemTimeout > GetTickCount() ) Idle();

	// Передаем модему команду разорвать соединение и повесить трубку
	WriteComm( idComDev, sTmp, lstrlen( sTmp ) );

	EscapeCommFunction(idComDev, CLRDTR);

	return 0;
}

// =====================================
// Функция ShowError
// =====================================

void	ShowError( int iErr )
{
	int	iNum;
	char	szMsgBuff[40];


	// Не известная ошибка соответствующей ошибке
	if(( iErr < -6 ) || ( iErr >= 0 ))
		return;

	// Загружаем из ресурсов порриложения строку
	// с идентификатором iErr
	LoadString( hInst, iErr, szMsgBuff, 40 );

	// Отображаем на экране сообщение об ошибке
	BWCCMessageBox( NULL, szMsgBuff, "Ошибка",
						 MB_OK | MB_ICONSTOP	);

	// Подаем звуковой сигнал
	MessageBeep( MB_ICONASTERISK );

	return;
}

// ===================================================================
// Функция ReadComPort
// ===================================================================

int ReadComPort(int idComDev, LPSTR szDest, int nLength)
{

	COMSTAT ComStat;
	int nTotalRead = 0, nRead = 0;

	while(nLength > nTotalRead)
	{
		// Определяем состояние COM-порта
		GetCommError(idComDev,&ComStat);

		// Если во входной очереди нет данных завершаем функцию
		if (ComStat.cbInQue == 0)
			break;

		// Считываем данные в буфер szDest
		else
			nRead = ReadComm(idComDev,&(szDest[nTotalRead]),
													nLength - nTotalRead);
		// Если функция ReadComm завершилась с ошибкой возвращаем -1

		if (nRead < 0)
			return -1;

		nTotalRead += nRead;
	}

	// Возвращаем число прочитанных из COM-порта байт
	return nTotalRead;
}


// ===================================================================
// Функция WaitModemAnswer
// ===================================================================
int WaitModemAnswer(int idComDev, LPSTR *szWaitDest,
						  LPSTR szModemAnswer, int nLength,
						  DWORD dwTimeOut)
{
	int	nRead;
	int	nTotalChar = 0, i;
	DWORD	dwStartTick;
	BOOL	fFind = FALSE;

   // Определяем текущий момент времени
	dwStartTick = GetTickCount();

	do
	{
		// Считываем данные из COM-порта
		nRead = ReadComPort(idComDev, &szModemAnswer[nTotalChar],
								  nLength - lstrlen(szModemAnswer));

		// В случае ошибки возвращаем ERR_READ
		if(nRead < 0)
			return ERR_READ;

		else if(nRead > 0)
		{
			nTotalChar += nRead;

			// Добавляем в конец полученных данных двоичный ноль
			szModemAnswer[nTotalChar] = '\0';

			// Проверяем получен ли от модема ответ из массива szWaitDest
			for(int i = 0; szWaitDest[i]; i++)
			{
				if(strstr(szModemAnswer, szWaitDest[i]))
				{
					// Если ответ получен, завершаем чтение данных
					// и возвращаем управвление
					fFind = TRUE;
					break;
				}
			}
		}

		// Разрешаем обработку сообщений для других приложений
		else
			Idle();

	// Выходим из цикла если от модема получен ожидаемый ответ,
	// возникла ошибка при чтении данных или истекло время ожидания
	// ответа (nTimeOut * 1000)

	} while (!fFind && nRead >= 0 &&
				dwStartTick + dwTimeOut > GetTickCount() );

	if(nRead >= 0 && !fFind)
		return ERR_TIMEOUT;

	// Возвращаем количесттво пррочитанных символов
	return lstrlen( szModemAnswer );
}

// ===================================================================
// Функция Idle
// ===================================================================
void	Idle(void)
{
	MSG msg;

	while( PeekMessage( &msg, NULL, NULL, NULL, PM_REMOVE ) )
	{
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	}

   return;
}