// EIKDIAL.CPP
//
// Copyright (c) 1997-1999 Symbian Ltd.  All rights reserved.
//

#include <eikdial.h>

#include <e32hal.h>
#include <s32stor.h>
#include <s32file.h>
#include <eikenv.h>
#include <eikedwin.h>
#include <eikpanic.h>

#include <eikdial.hrh>
#include <eikon.rsg>
#include <eikdialg.hrh>

const TUid KProteaV01DialSettingsUID={268435814};
EXPORT_C TEikDialSettings::TEikDialSettings()
: iIncludeAreaCode(0), iToneTime(187500), iDelayTime(125000), iPauseTime(1000000)
	{
	CCoeEnv::Static()->ReadResource(iDialOut,R_EIK_TBUF_DIALOUT);
	}

EXPORT_C void TEikDialSettings::ExternalizeL(RWriteStream& aStream) const
	{
	aStream << iDialOut;
	aStream.WriteInt32L(iToneTime.Int());
	aStream.WriteInt32L(iDelayTime.Int());
	aStream.WriteInt32L(iPauseTime.Int());
	aStream.WriteInt32L(iIncludeAreaCode);
	}

EXPORT_C void TEikDialSettings::InternalizeL(RReadStream& aStream)
	{
	aStream >> iDialOut;
	iToneTime = aStream.ReadInt32L();
	iDelayTime = aStream.ReadInt32L();
	iPauseTime = aStream.ReadInt32L();
	iIncludeAreaCode = aStream.ReadInt32L();
	}

EXPORT_C void TEikDialSettings::SetDialOutString(const TDesC& aString)
	{
	iDialOut=aString;
	}

EXPORT_C void TEikDialSettings::GetDialOutString(TDes& aString) const
	{
	aString=iDialOut;
	}

EXPORT_C void TEikDialSettings::SetSoundTimes(const TTimeIntervalMicroSeconds32 aToneTime, 
								  const TTimeIntervalMicroSeconds32 aDelayTime,
								  const TTimeIntervalMicroSeconds32 aPauseTime)
	{
	iToneTime = aToneTime;
	iDelayTime = aDelayTime;
	iPauseTime = aPauseTime;
	}

EXPORT_C void TEikDialSettings::GetSoundTimes(TTimeIntervalMicroSeconds32& aToneTime,
								  TTimeIntervalMicroSeconds32& aDelayTime,
								  TTimeIntervalMicroSeconds32& aPauseTime) const
	{
	aToneTime = iToneTime;
	aDelayTime = iDelayTime;
	aPauseTime = iPauseTime;
	}

EXPORT_C void TEikDialSettings::SetIncludeAreaCode(TBool aIncludeAreaCode)
	{
	iIncludeAreaCode=aIncludeAreaCode;
	}

EXPORT_C TBool TEikDialSettings::IncludeAreaCode() const
	{
	return iIncludeAreaCode;
	}


//
// class CEikDtmfPlayer - active object so dialling can be cancelled
//

EXPORT_C CEikDtmfPlayer::CEikDtmfPlayer(TInt aPriority, MEikDtmfPlayerObserver* aObserver)
: CActive(aPriority), iObserver(aObserver)
	{
	__DECLARE_NAME(_S("CEikDtmfPlayer"));
	}

EXPORT_C CEikDtmfPlayer::~CEikDtmfPlayer()
	{
	Cancel();
	}

EXPORT_C TBool CEikDtmfPlayer::IsDialing() const
	{
	return IsActive();
	}

TInt CEikDtmfPlayer::SetUpSoundDevice(TTimeIntervalMicroSeconds32 aToneTime,
		TTimeIntervalMicroSeconds32 aDelayTime, TTimeIntervalMicroSeconds32 aPauseTime)
	{
	TInt err=iSoundDevice.Open();
	if (err!=KErrNone)
		return err;

	TSoundCaps caps;
	iSoundDevice.Caps(caps);
	err=caps().iService;
	if (!(err&KCapsDtmfAvailable))
		return KErrNotSupported;

	TSoundConfig config;
	iSoundDevice.Config(config);
	config().iDtmfToneOnLength=aToneTime;
	config().iDtmfToneOffLength=aDelayTime;
	config().iDtmfPauseLength=aPauseTime;

	config().iVolume=EVolumeMedium;
	err=iSoundDevice.SetConfig(config);

	return err;
	}

EXPORT_C TInt CEikDtmfPlayer::Play(const TDesC& aDialString, TTimeIntervalMicroSeconds32 aToneTime,
		TTimeIntervalMicroSeconds32 aDelayTime, TTimeIntervalMicroSeconds32 aPauseTime)
// sets off an active object to dial a series of numbers
// this if this returns KErrNone active object is guaranteed to run
// this function locks machine for 1 sec to allow user to position the phone - could
// use a special CTimer class to avoid this
	{
	__ASSERT_ALWAYS(!IsActive(), Panic(EEikPanicDtmfPlayerActive));
	__ASSERT_ALWAYS(aDialString.Length()!=0, Panic(EEikPanicNothingToDial));

	TInt error = SetUpSoundDevice(aToneTime, aDelayTime, aPauseTime);
	if (error != KErrNone)
		return error;

	User::After(1000000); // 1 secs to allow user to position machine
	iSoundDevice.PlayDtmf(iStatus, aDialString);
	SetActive();

	return KErrNone;
	}

EXPORT_C TInt CEikDtmfPlayer::Play(TUint aDialChar, TTimeIntervalMicroSeconds32 aToneTime,
		TTimeIntervalMicroSeconds32 aDelayTime, TTimeIntervalMicroSeconds32 aPauseTime)
// dials a single character
	{
	__ASSERT_ALWAYS(!IsActive(), Panic(EEikPanicDtmfPlayerActive));

	TInt error = SetUpSoundDevice(aToneTime, aDelayTime, aPauseTime);
	if (error != KErrNone)
		return error;

	TDialString string;
	string.Append(aDialChar);
	iSoundDevice.PlayDtmf(iStatus, string);
	User::WaitForRequest(iStatus);
	iSoundDevice.Close();

	return iStatus.Int();
	}

void CEikDtmfPlayer::DoCancel()
	{
	iSoundDevice.PlayCancel();
	iSoundDevice.Close();
	}

void CEikDtmfPlayer::RunL()
	{
	iSoundDevice.Close();
	iObserver->HandleCompletion(iStatus.Int());
	}


//
// base class of all dialling dialogs
//

EXPORT_C CEikDialDialogBase::CEikDialDialogBase()
	{
	__DECLARE_NAME(_S("CEikDialDialogBase"));
	iEikonEnv->RootWin().DisableKeyClick(ETrue);
	}

EXPORT_C CEikDialDialogBase::~CEikDialDialogBase()
	{
	iEikonEnv->RootWin().DisableKeyClick(EFalse);
	delete iDtmfPlayer;
	}

EXPORT_C void CEikDialDialogBase::ConstructL()
// constructs dtmf player and gets any dial settings from system.ini file
	{
	iDtmfPlayer = new(ELeave) CEikDtmfPlayer(CActive::EPriorityHigh, this);
	CActiveScheduler::Add(iDtmfPlayer);

	TBool readAreaCodeFromResourceFile=ETrue;
	CDictionaryStore* iniFile=CDictionaryFileStore::SystemLC(iEikonEnv->FsSession());
	if (iniFile->IsPresentL(KProteaV01DialSettingsUID))
		{
		RDictionaryReadStream stream;
		stream.OpenLC(*iniFile,KProteaV01DialSettingsUID);
		TRAPD(err, stream >> iDialSettings);
		if (err == KErrNone)
			readAreaCodeFromResourceFile=EFalse;
		else if (err != KErrEof)
			User::Leave(err);
		CleanupStack::PopAndDestroy(); // stream
		}
	CleanupStack::PopAndDestroy(); //iniFile

	if (readAreaCodeFromResourceFile)
		{
		TBuf<1> includeAreaCode;
		iEikonEnv->ReadResource(includeAreaCode, R_DIAL_INCLUDE_AREA_CODE);
		if (includeAreaCode[0]=='0')
			iDialSettings.SetIncludeAreaCode(EFalse);
		else if (includeAreaCode[0]=='1')
			iDialSettings.SetIncludeAreaCode(ETrue);
		}
	}

EXPORT_C void CEikDialDialogBase::HandleCompletion(TInt aStatus)
	{
	if (aStatus==KErrNone)
		return;

	TBuf<100> errorText;
	switch(aStatus)
		{
	case KErrAccessDenied:
		{
		TSoundInfoV1Buf soundInfo;
		UserHal::SoundInfo(soundInfo);
		if (!soundInfo().iSoundEnabled || !soundInfo().iSoundDriverEnabled)
			{
			iEikonEnv->ReadResource(errorText, R_DIAL_CANT_PLAY_SOUND);
			break;
			}
		}
	default:
		iEikonEnv->GetErrorText(errorText, aStatus);
		break;
		}
	iEikonEnv->AlertWin(errorText);
	}

EXPORT_C void CEikDialDialogBase::DoDialing(const TDesC& aPhoneNumber)
	{
	if (aPhoneNumber.Length()==0)
		{
		iEikonEnv->InfoMsg(R_DIAL_NOTHING_TO_DIAL);
		return;
		}

	TTimeIntervalMicroSeconds32 toneTime;
	TTimeIntervalMicroSeconds32 delayTime;
	TTimeIntervalMicroSeconds32 pauseTime;
	iDialSettings.GetSoundTimes(toneTime, delayTime, pauseTime);

	TInt error = iDtmfPlayer->Play(aPhoneNumber, toneTime, delayTime, pauseTime);
	if (error != KErrNone)
		HandleCompletion(error); // provides neccessary info
	}

EXPORT_C void CEikDialDialogBase::DoDialChar(TUint aChar)
	{
	if (iDtmfPlayer->IsDialing())
		iDtmfPlayer->Cancel();

	TTimeIntervalMicroSeconds32 toneTime;
	TTimeIntervalMicroSeconds32 delayTime;
	TTimeIntervalMicroSeconds32 pauseTime;
	iDialSettings.GetSoundTimes(toneTime, delayTime, pauseTime);

	TInt error = iDtmfPlayer->Play(aChar, toneTime, delayTime, pauseTime);
	if (error != KErrNone)
		HandleCompletion(error); // provides neccessary info
	}


EXPORT_C TBool CEikDialDialogBase::IsDiallable(TChar aChar)
	{
	if (aChar.IsDigit() || aChar==',' || aChar=='*' || aChar=='#')
		return ETrue;
	return EFalse;
	}

//
// free input dial dialog - called directly from DATA or from dial dialogs
//


EXPORT_C CEikFreeDialDialog::CEikFreeDialDialog()
	{
	__DECLARE_NAME(_S("CEikFreeDialDialog"));
	}

void CEikFreeDialDialog::PreLayoutDynInitL()
	{
	ConstructL();
	}

void CEikFreeDialDialog::HandleControlStateChangeL(TInt aControlId)
	{
	if (aControlId!=EFreeDialDlgEdwin)
		return;

	TDialString number;
	GetEdwinText(number, EFreeDialDlgEdwin);
	TInt numberLength=number.Length();
	if (numberLength==0 || numberLength<=iStringLength)
		{
		iStringLength=numberLength;
		return;
		}

	TInt cursorPos=((CEikEdwin *)Control(EFreeDialDlgEdwin))->CursorPos();
	if (cursorPos==0)
		return;

	if (numberLength==iStringLength+1)
		{
		TChar lastChar=number[cursorPos-1];
		if (IsDiallable(lastChar))
			{
			iStringLength = numberLength;
			DoDialChar(lastChar);
			}
		else
			{
			number.Delete(cursorPos-1, 1);

			SetEdwinTextL(EFreeDialDlgEdwin, &number);
			((CEikEdwin *)Control(EFreeDialDlgEdwin))->SetCursorPosL(cursorPos-1, EFalse);
			iEikonEnv->InfoMsg(R_DIAL_UNDIALLABLE_CHAR);
			}
		}
	else
		{
		TDialString diallableString;
		for (TInt pos=0; pos<numberLength; ++pos)
			{
			if (CEikDialDialogBase::IsDiallable(number[pos]))
				diallableString.Append(number[pos]);
			}
		if (diallableString.Length()!=numberLength)
			{
			iStringLength=diallableString.Length();
			SetEdwinTextL(aControlId, &diallableString);
			if (cursorPos>iStringLength)
				cursorPos=iStringLength;
			((CEikEdwin *)Control(aControlId))->SetCursorPosL(cursorPos, EFalse);
			iEikonEnv->InfoMsg(R_DIAL_UNDIALLABLE_CHAR);
			}
		}
	}

TBool CEikFreeDialDialog::OkToExitL(TInt aKeyCode)
	{
	TBool wasDialing=iDtmfPlayer->IsDialing();
	if (wasDialing)
		iDtmfPlayer->Cancel();

	switch (aKeyCode)
		{
	case EEikBidCancel:
		return !wasDialing;
	case EFreeDialDlgButClear:
		ClearPhoneNumberL();
		return EFalse;
	case EEikBidOk:
		Redial();
		return EFalse;
	default:
		return EFalse;
		}
	}

void CEikFreeDialDialog::ClearPhoneNumberL()
	{
	iStringLength=0;
	TBuf<1> empty;
	SetEdwinTextL(EFreeDialDlgEdwin, &empty);
	}

void CEikFreeDialDialog::Redial()
	{
	TDialString number;
	GetEdwinText(number, EFreeDialDlgEdwin);
	DoDialing(number);
	}

//
// common code for Agenda and Data dial dialogs
//

EXPORT_C CEikDialDialog::CEikDialDialog()
	{
	__DECLARE_NAME(_S("CEikDialDialog"));
	}

EXPORT_C TBool CEikDialDialog::IsDiallable(const TDesC& aString, TInt& aStartPos)
// arbitrarily chosen that string has to contain 3 digits within an area only containing
// diallable characters
	{
	aStartPos=-1;
	TInt noDigits=0;
	TChar ch;
	for (TInt pos=0; pos<aString.Length(); pos++)
		{
		ch=aString[pos];
		if (CEikDialDialogBase::IsDiallable(ch))
			{
			if (aStartPos<0)
				aStartPos=pos;
			++noDigits;
			}
		else if (ch=='+')
			{
			if (aStartPos<0)
				aStartPos=pos;
			}
		else if (ch!=' ' && ch!='.' && ch!='-' && ch!='(' && ch!=')')
			{
			noDigits=0;
			aStartPos=-1;
			}
		if (noDigits>=3)
			return ETrue;
		}
	return EFalse;
	}

EXPORT_C void CEikDialDialog::HandleControlStateChangeL(TInt aControlId)
	{
	if (aControlId<EDialDlgEdwin1 || aControlId>EDialDlgEdwin5)
		return;

	TDialString number;
	GetEdwinText(number, aControlId);
	TInt numberLength=number.Length();
	if (numberLength==0)
		return;

	TInt cursorPos=((CEikEdwin *)Control(aControlId))->CursorPos();
	if (cursorPos==0)
		return;

	TDialString diallableString;
	for (TInt pos=0; pos<numberLength; ++pos)
		{
		if (CEikDialDialogBase::IsDiallable(number[pos]))
			diallableString.Append(number[pos]);
		}
	if (diallableString.Length()!=numberLength)
		{
		SetEdwinTextL(aControlId, &diallableString);
		if (cursorPos>diallableString.Length())
			cursorPos=diallableString.Length();
		((CEikEdwin *)Control(aControlId))->SetCursorPosL(cursorPos, EFalse);
		iEikonEnv->InfoMsg(R_DIAL_UNDIALLABLE_CHAR);
		}
	}

EXPORT_C TBool CEikDialDialog::OkToExitL(TInt aKeyCode)
	{
	TBool wasDialing=iDtmfPlayer->IsDialing();
	if (wasDialing)
		iDtmfPlayer->Cancel();

	switch (aKeyCode)
		{
	case EEikBidTab:
		ExecuteFreeDialDialogL();
		return EFalse;
	case EEikBidSpace:
		Dial();
		return EFalse;
	case EEikBidOk:
		DialOut();
		return EFalse;
	case EEikBidCancel:
		return !wasDialing;
	default:
		return EFalse;
		}
	}

EXPORT_C void CEikDialDialog::SetPhoneNumberL(TInt aControlId, const TDesC& aString, TInt aStartPos)
// strip out extra text but this may also contain various other brackets etc
	{
	TDialString number;
	TInt stringLength=aString.Length();
	TChar ch;
	TInt pos=aStartPos;
	while (pos<stringLength)
		{
		ch=aString[pos];
		if (CEikDialDialogBase::IsDiallable(ch) || ch==' ' || ch=='+' || ch== '(' || ch== ')')
			number.Append(ch);
		else if (ch=='.' || ch=='-')
			number.Append(' ');
		if (number.Length()==KMaxDialString)
			break;
		++pos;
		}

	SetWorldExtension(number);
	SetEdwinTextL(aControlId, &number);
	}

EXPORT_C void CEikDialDialog::SetWorldExtension(TDes& aPhoneNumber) const
	{
	TDialString result;
	TBool phoneNumberStartsWithPlus = aPhoneNumber[0]=='+';
	if (phoneNumberStartsWithPlus || SetFullPhoneNumber(aPhoneNumber)==KErrNone)
		{ // can be resolved
		// get locations for dial resolver
		TDialLocation location;
		SetHomeLocation(location);
		
		// check for +44 (0) 171 format
		if (phoneNumberStartsWithPlus)
			{
			TInt start=aPhoneNumber.Locate('(');
			if (start != KErrNotFound)
				{
				TInt end=aPhoneNumber.Locate(')');
				if (end != KErrNotFound && end>start)
					{
					TDialString natPref;
					TDialString natCode;
					for (TInt pos=1; pos<end; ++pos)
						{
						TChar ch=aPhoneNumber[pos];
						if (CEikDialDialogBase::IsDiallable(ch))
							{
							if (pos<start)
								natCode.Append(ch);
							else if (pos>start && pos<end)
								natPref.Append(ch);
							}
						}
					if (natPref == location.NatPrefCode() && natCode==location.NatCode())
						{
						aPhoneNumber.Delete(start, end-start+1);
						natPref=_L(" ");
						aPhoneNumber.Insert(start, natPref);
						}
					else
						{ // avoid the most common case
						TCountryData data;
						TWorldId id;
						RWorldServer wld;
						wld.Connect();
						TInt ret = wld.FirstCountry(id);
						while (ret != KErrNotFound)
							{
							wld.CountryData(data, id);
							if (natCode== data.iNatCode && natPref==data.iNatPref)
								{
								aPhoneNumber.Delete(start, end-start+1);
								natPref=_L(" ");
								aPhoneNumber.Insert(start, natPref);
								break;
								}
							ret = wld.NextCountry(id);
							}
						wld.Close();
						}
					}
				}
			}

// call dial resolver
		TCityDialOptions includeAreaCode;
		if (iDialSettings.IncludeAreaCode())
			includeAreaCode=EDialCityAllowDuplicateAreaCode;
		else
			includeAreaCode=EDialCityStripDuplicateAreaCode;
		TelephoneNumber::Parse(result, location, aPhoneNumber, includeAreaCode);
		}
	else
		result=aPhoneNumber;

	TChar ch;
	TInt length=result.Length();
	aPhoneNumber.Zero();
	for (TInt pos=0; pos<length; pos++)
		{  // removes any remaining + or ()
		ch=result[pos];
		if (CEikDialDialogBase::IsDiallable(ch))
			aPhoneNumber.Append(ch);
		if (aPhoneNumber.Length()==EDialPhoneNumberMaxLength)
			break;
		}
	}

TInt CEikDialDialog::SetFullPhoneNumber(TDes& aPhoneNumber) const
	{
	TBuf<60> phoneNumber=aPhoneNumber;

	TCountryData data;
	TCityData cityData;
	TWorldId id;
	RWorldServer wld;
	wld.Connect();
	wld.DefaultCountry(id);
	wld.CountryData(data, id);
	wld.CityData(cityData, id);
	wld.Close();
	
	if (data.iNatCode.Length()==0 || data.iNatPref.Length()==0 || data.iIntlPref.Length()==0)
		return KErrNotFound; // dont have enough info to do anything clever

	if (aPhoneNumber.Length() <= data.iNatPref.Length()+cityData.iAreaCode.Length())
		return KErrNotFound; // number is too short to resolve

	TDialString code=aPhoneNumber;
	code.SetLength(data.iIntlPref.Length());
	if (code==data.iIntlPref)
		return KErrNotFound; // dont try and resolve full phone numbers

	code.SetLength(data.iNatPref.Length());
	if (code!=data.iNatPref)
		return KErrNotFound;

	phoneNumber.Delete(0, data.iNatPref.Length());
	code=_L("+");
	code.Append(data.iNatCode);
	if (cityData.iAreaCode.Length()>0)
		code.Append('(');
	phoneNumber.Insert(0, code);
	if (cityData.iAreaCode.Length()>0)
		{
		TInt prefLength=code.Length();
		code.Zero();
		code.Append(')');
		phoneNumber.Insert(prefLength+cityData.iAreaCode.Length(), code);
		}

	if (phoneNumber.Length()>KMaxDialString)
		phoneNumber.SetLength(KMaxDialString);
	aPhoneNumber=phoneNumber;
	return KErrNone;
	}

void CEikDialDialog::SetHomeLocation(TDialLocation& aLocation) const
	{
	TCountryData coData;
	TCityData ciData;
	TWorldId id;
	RWorldServer wld;
	wld.Connect();
	wld.Home(id);
	wld.CountryData(coData, id);
	wld.CityData(ciData, id);
	wld.Close();

	aLocation.SetIntlPrefCode(coData.iIntlPref);
	aLocation.SetNatPrefCode(coData.iNatPref);
	aLocation.SetNatCode(coData.iNatCode);
	aLocation.SetAreaCode(ciData.iAreaCode);
	}

EXPORT_C void CEikDialDialog::Dial()
	{
	TDialString number;
	GetEdwinText(number, IdOfFocusControl());
	
	DoDialing(number);
	}

EXPORT_C void CEikDialDialog::DialOut()
	{
	TDialString number;
	GetEdwinText(number, IdOfFocusControl());

	TDialString string;
	iDialSettings.GetDialOutString(string);
	number.Insert(0, string);
	
	DoDialing(number);
	}

EXPORT_C void CEikDialDialog::ExecuteFreeDialDialogL() const
	{
	CEikFreeDialDialog* dlg=new(ELeave) CEikFreeDialDialog();
	dlg->ExecuteLD(R_FREE_DIAL_DIALOG);
	iEikonEnv->RootWin().DisableKeyClick(ETrue);
	}
