//This file is part of Msg_Export a Miranda IM plugin
//Copyright (C)2002 Kennet Nielsen ( http://sourceforge.net/projects/msg-export/ )
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.


#include <windows.h>
#include <Richedit.h>

#include <iostream>
#include <fstream>

using namespace std;


#include "Utils.h"
#include "Glob.h"
#include "FileViewer.h"

#include "resource.h"

#include <stdio.h>
#include <basetsd.h>

//#include <map>

#define szFileViewDB "FileV_"

#define WM_RELOAD_FILE (WM_USER+10)

using namespace std;

static UINT UM_FIND_CMD = RegisterWindowMessage( FINDMSGSTRING );

#define ID_FV_FONT			0x0010
#define ID_FV_COLOR			0x0020
#define ID_FV_SYNTAX_HL    0x0030
#define ID_FV_SAVE_AS_RTF  0x0040
//	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
//	ASSERT(IDM_ABOUTBOX < 0xF000);


// Specifies if history is opened internaly or externaly
bool bUseIntViewer = true;

// External program used to view files
tstring sFileViewerPrg;

// handle to the RichEditDll. We need to load this dll to use a RichEdit.
HMODULE hRichEditDll = NULL;


#define CONT(i) ((in[i]&0xc0) == 0x80)
#define VAL(i, s) ((in[i]&0x3f) << s)

void swap(char &c1, char &c2) {
    char ch;
    ch=c1;
    c1=c2;
    c2=ch;
}

int DecodeUTF8(const char *pcBuff,int /*iBufSize*/,char *pcOutBuf) {
	int iBytesInOut=0;
	int /*cp,*/i;
	char ch,*p;

//Parse UTF-8 sequence
//Support only chars up to three bytes (UCS-4 - go away!)
//Warning: Partial decoding is possible!
    i=0;
    ch=pcBuff[i];
    if(!(ch&0x80)) {
        pcOutBuf[iBytesInOut++]=ch;
        pcOutBuf[iBytesInOut++]='\0';
    }
    else if(!(ch&0x20)) {
        pcOutBuf[iBytesInOut++]=(ch>>2)&0x07;
        i++;
        pcOutBuf[iBytesInOut++]=(pcBuff[i]&0x3F)|(ch<<6);
        swap(pcOutBuf[iBytesInOut-1],pcOutBuf[iBytesInOut-2]);
    }
    else if(!(ch&0x10)) {
        i++;

        pcOutBuf[iBytesInOut++]=(ch<<4)|((pcBuff[i]>>2)&0x0F);
        ch=pcBuff[i];
        i++;
        pcOutBuf[iBytesInOut++]=(pcBuff[i]&0x3F)|(ch<<6);
        swap(pcOutBuf[iBytesInOut-1],pcOutBuf[iBytesInOut-2]);
    }
    else {
        p=(char*)&pcBuff[i];
        pcOutBuf[iBytesInOut++]='\x3F';
        pcOutBuf[iBytesInOut++]='\0';
        if(!(ch&0x08)) i+=3;
        else if(!(ch&0x04)) i+=4;
        else if(!(ch&0x02)) i+=5;
    }

    i++;

	return i;
}


int __utf8_get_char(const char *in, int *chr)
{                                       /* 2-byte, 0x80-0x7ff */
	return DecodeUTF8(in,256,(char *)chr);
}

// there is one instance of CLHistoryDlg for every history dialog on screeen.
class CLHistoryDlg
{
	public:
		HWND hWnd;

		HANDLE hContact;
		tstring sPath;

		WNDPROC wpOrigEditProc;

		HWND hFindDlg;
		FINDREPLACE fr;
		_TCHAR acFindStr[100];

		bool bFirstLoad;
		bool bUtf8File;

		CLHistoryDlg( HANDLE hContact ) : hContact( hContact )
		{
			hFindDlg = NULL;
			acFindStr[0] = 0;
			ZeroMemory( &fr , sizeof( fr ));
			fr.lStructSize = sizeof( fr );
			fr.hInstance = hInstance;
			fr.Flags = FR_NOUPDOWN|FR_HIDEUPDOWN;//|FR_MATCHCASE|FR_WHOLEWORD;
						     // FR_DOWN|FR_FINDNEXT|FR_NOMATCHCASE;
			fr.lpstrFindWhat = acFindStr;
			fr.wFindWhatLen = sizeof(acFindStr);
			bFirstLoad = true;
			bUtf8File = false;
		}
};

// List of all open history windows 
list< CLHistoryDlg* > clHistoryDlgList;
// CRITICAL_SECTION used to access the window list
// this is nesery because UpdateFileViews is called from callback thread.
CRITICAL_SECTION csHistoryList;


// CLStreamRTFInfo is used when reading RTF into rich edit from a stream.
// RTF is used when Syntax highlighting is used.
class CLStreamRTFInfo
{
	private:
		HANDLE hFile;
		bool bHeaderWriten;
		bool bTailWriten;
		bool bCheckFirstForNick;
		bool bLastColorMyNick;

		// buffer size supplyed on win XP 4092 byte when streamin in
		// optimal size it to fully use this buffer but we can guess 
		// how may bytes need converting in the file we are reading.
		BYTE abBuf[3300];
		char szMyNick[100];
		int nNickLen;
		static int nOptimalReadLen;

		int nWriteHeader( char * pszTarget , int nLen );
	public:
		bool bUtf8File;
		CLStreamRTFInfo( HANDLE hFile )
		{
			this->hFile = hFile;
			bHeaderWriten = false;
			bTailWriten = false;
			bCheckFirstForNick = false;
			bLastColorMyNick = false;
			bUtf8File = false;
			nNickLen = 0;
		}
		int nLoadFileStream( LPBYTE pbBuff , LONG cb );
};
int CLStreamRTFInfo::nOptimalReadLen = 3300;

/////////////////////////////////////////////////////////////////////
// Member Function : nWriteHeader
// Type            : Private / Public / Protected
// Parameters      : pszTarget - ?
//                   nLen      - ?
// Returns         : int
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 030204 , 04 February 2003
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

int CLStreamRTFInfo::nWriteHeader( char * pszTarget , int nLen )
{
	COLORREF cMyText = DBGetContactSettingDword(NULL,"SRMsg","Font3Col",RGB(64,0,128));
	COLORREF cYourText = DBGetContactSettingDword(NULL,"SRMsg","Font0Col",RGB(240,0,0));

/* original header !!
			"{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0\\fnil\\fcharset0 Courier New;}}\r\n"
			"{\\colortbl ;\\red%d\\green%d\\blue%d;\\red%d\\green%d\\blue%d;}\r\n"
			"\\viewkind4\\uc1\\pard\\cf2\\lang1033\\f0\\fs16 " ,

*/
	char szRtfHeader[400];
	int nSrcLen = _snprintf( szRtfHeader , sizeof( szRtfHeader ) ,
			"{\\rtf1\\ansi\r\n"
			"{\\colortbl ;\\red%d\\green%d\\blue%d;\\red%d\\green%d\\blue%d;}\r\n"
			"\\viewkind4\\uc1\\pard\\cf2 " ,
				GetRValue( cMyText )   ,GetGValue( cMyText )   ,GetBValue( cMyText ) ,
				GetRValue( cYourText ) ,GetGValue( cYourText ) ,GetBValue( cYourText ) );

	if( nSrcLen > nLen )
	{
		MessageBox( NULL , LPGENT("Failed to write to the RichEdit the buffer was to small."),MSG_BOX_TITEL,MB_OK );
		return 0; // target buffer to small
	}

	memcpy( pszTarget , szRtfHeader , nSrcLen );
	bHeaderWriten = true;
	return nSrcLen;
}

const char szNewLine[] = "\n\\par ";
const char szRtfEnd[] = "\r\n\\par }\r\n\0";

/////////////////////////////////////////////////////////////////////
// Member Function : nLoadFileStream
// Type            : Private / Public / Protected
// Parameters      : pbBuff - ?
//                   cb     - ?
// Returns         : int
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 030204 , 04 February 2003
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

int CLStreamRTFInfo::nLoadFileStream( LPBYTE pbBuff , LONG cb )
{
	if( bTailWriten )
		return 0;

	if( nOptimalReadLen < 500 )
	{
		MessageBox( NULL , LPGENT("Error: Optimal buffer size decrecied to a to low size !!"),MSG_BOX_TITEL,MB_OK );
		return 0;
	}

	DWORD dwRead;
	DWORD dwToRead = nOptimalReadLen;

	if( ! ReadFile(hFile , abBuf, dwToRead , &dwRead, (LPOVERLAPPED)NULL) )
		return 0;

	DWORD dwCurrent = 0;
	DWORD n = 0;
	if( ! bHeaderWriten )
	{
		if( dwRead >= 3 )
		{
			bUtf8File = bIsUtf8Header( abBuf );
			if( bUtf8File )
				n = 3;
		}
		dwCurrent += nWriteHeader( (char*)pbBuff , cb );
		
		tstring sMyNick = NickFromHandle(0);

		nNickLen = WideCharToMultiByte(bUtf8File ? CP_UTF8 : CP_ACP , 0, sMyNick.c_str(), (int)sMyNick.length() , szMyNick , sizeof( szMyNick ), NULL , NULL );
	}
	else
	{
		if( bCheckFirstForNick )
		{
			// Test against "<<" also 
			if( ( (memcmp( abBuf , szMyNick , nNickLen ) == 0) || 
				   (abBuf[0] == '<' && abBuf[1] == '<')
				 ) != bLastColorMyNick )
			{
				// we shut only get here if we need to change color !!
				bLastColorMyNick = !bLastColorMyNick;
				// change color 
				memcpy( &pbBuff[dwCurrent] , bLastColorMyNick ? "\\cf1 " : "\\cf2 ", 5 );
			}
			bCheckFirstForNick = false;
		}
	}

	bool bIsFileEnd = dwRead < dwToRead;

	for( ; n < dwRead ; n++ )
	{
		// worst case is a file ending with \n or a unicode letter. resulting in a big unicode string 
		// here we need szNewLine and szRtfEnd. the 10 is a small safty margin.
		if( dwCurrent + (sizeof( szNewLine ) + sizeof( szRtfEnd ) + 10 ) > (DWORD)cb )
		{
			// oh no !!! we have almost reached the end of the windows supplyed buffer 
			// we are writing to. we need to abort mision *S*!!
			// and rewinde file
			// we will adjust the optima buffer size 
			nOptimalReadLen -= 50;
			SetFilePointer( hFile , n - dwRead , NULL , FILE_CURRENT );
			return dwCurrent;
		}

		if( abBuf[n] == '\n' )
		{
			memcpy( &pbBuff[dwCurrent] , szNewLine , sizeof( szNewLine )-1 );
			dwCurrent += sizeof( szNewLine )-1;

			if( n + 1 >= dwRead )
			{
				// this is an anoing case because here we have read \n as the last char in the file
				// this means that the if the next data read from file begins with <UserNick> it has 
				// to be highlighted
				if( !bIsFileEnd )
					bCheckFirstForNick = true;
				break; 
			}

			if( abBuf[n+1] == ' ' || abBuf[n+1] == '\t' || abBuf[n+1] == '\r' )
				continue;

			if( n + nNickLen >= dwRead )
			{
				if( !bIsFileEnd )
				{
					// here we have a problem we haven't read this data yet 
					// the data we need to compare to is still in the file.
					// we can't read more data from the file because the we 
					// might just move the problem. if file contains \n\n\n\n\n ... 
					
					LONG lExtraRead = (n+1) - dwRead;
					if( lExtraRead >= 0 )
						MessageBox( NULL , LPGENT("Internal error !! (lExtraRead >= 0)"),MSG_BOX_TITEL,MB_OK );
					SetFilePointer( hFile , lExtraRead , NULL , FILE_CURRENT );
					bCheckFirstForNick = true;
					return dwCurrent;
				}

				if( ! bLastColorMyNick )
					continue;
				// else the last color user was my nick 
				// we needd to change color to the other user color.
				
				
				/* old code !!
				DWORD dwAddedToBuf; 
				if( ! ReadFile(hFile , &abBuf[dwRead], dwNeeded , &dwAddedToBuf, (LPOVERLAPPED)NULL) )
					return 0;
				dwToRead += dwNeeded;
				dwRead += dwAddedToBuf;*/
			}
			else
			{
				// the data we need is here just compare 
				if( ( ( memcmp( &abBuf[n+1] , szMyNick , nNickLen ) == 0) ||
					   ( abBuf[n+1] == '<' && abBuf[n+2] == '<') 
					 ) == bLastColorMyNick )
					continue;
			}
			// we shut only get here if we need to change color !!
			bLastColorMyNick = !bLastColorMyNick;

			// change color 
			memcpy( &pbBuff[dwCurrent] , bLastColorMyNick ? "\\cf1 " : "\\cf2 ", 5 );
			dwCurrent += 5;
			continue;
		}
		else if( abBuf[n] == '\\' || abBuf[n] == '}' || abBuf[n] == '{')
		{
			pbBuff[dwCurrent++]='\\';
		}
		else if( bUtf8File && (abBuf[n] & 0x80) )
		{
			int nValue;
			int nLen = __utf8_get_char( (const char *)&abBuf[n] , &nValue );
			if(nLen+n>dwRead) {
				SetFilePointer(hFile,n-dwRead,NULL,FILE_CURRENT);
				break;
			}
			dwCurrent += sprintf( (char*)&pbBuff[dwCurrent] , "\\u%d?" , nValue );
			//continue;
/*			// Then we have an extended char in the UTF8 file. 
			// we need to convert this to UCS-2 and then to \uN in the RTF
			int nUtf8Len = 1;
			while( ( (n + nUtf8Len) < dwRead ) && ((abBuf[ n + nUtf8Len ] & 0xC0) == 0x80) ) 
				nUtf8Len++;
			wchar_t szWstr[2];
			if( MultiByteToWideChar( CP_UTF8 , 0 , (char*)&abBuf[n] , nUtf8Len , szWstr , 2 ) == 1 )
			{
				if( (int)(szWstr[0]) != nValue )
					__utf8_get_char( (const char *)&abBuf[n] , &nValue );

//				dwCurrent += sprintf( (char*)&pbBuff[dwCurrent] , "\\u%d?" , (int)(szWstr[0]) );
//				n += nUtf8Len - 1;
//				continue;
			}*/
			n += nLen-1;
			continue;
		}
		pbBuff[dwCurrent++] = abBuf[n];
	}

	if( bIsFileEnd )
	{// write end 
		memcpy( &pbBuff[dwCurrent] , szRtfEnd , sizeof( szRtfEnd )-1 );
		dwCurrent += sizeof( szRtfEnd )-1;
		bTailWriten = true;
	}
	//memcpy( pbBuff , abBuf , dwRead );
	return dwCurrent;
}


/////////////////////////////////////////////////////////////////////
// Member Function : Initilize
// Type            : Global
// Parameters      : None
// Returns         : void
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 021213 , 13 December 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

void Initilize()
{
	InitializeCriticalSection( &csHistoryList );
}

/////////////////////////////////////////////////////////////////////
// Member Function : Uninitilize
// Type            : Global
// Parameters      : None
// Returns         : void
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 021213 , 13 December 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

void Uninitilize()
{
	DeleteCriticalSection( &csHistoryList );
}

/////////////////////////////////////////////////////////////////////
// Member Function : UpdateFileViews
// Type            : Global
// Parameters      : pszFile - File which has been updated
// Returns         : void
// Description     : Send a message to alle to windows that need updating
//                   
// References      : -
// Remarks         : -
// Created         : 021213 , 13 December 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

void UpdateFileViews( const _TCHAR * pszFile )
{
	EnterCriticalSection( &csHistoryList );

	list< CLHistoryDlg* >::const_iterator iterator;
	for( iterator = clHistoryDlgList.begin() ; iterator != clHistoryDlgList.end() ; ++iterator )
	{
		CLHistoryDlg* pcl = (*iterator);
		if( pcl->sPath == pszFile )
		{
			PostMessage( pcl->hWnd , WM_RELOAD_FILE , 0 , 0 );
		}
	}
	LeaveCriticalSection( &csHistoryList );
}

/////////////////////////////////////////////////////////////////////
// Member Function : bOpenExternaly
// Type            : Global
// Parameters      : hContact - ?
// Returns         : Returns true if 
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 021010 , 10 October 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

bool bOpenExternaly( HANDLE hContact )
{
	tstring sPath = GetFilePathFromUser( hContact );

	if( sFileViewerPrg.empty() )
	{
		SHELLEXECUTEINFO st = {0};
		st.cbSize = sizeof(st);
		st.fMask = SEE_MASK_INVOKEIDLIST;
		st.hwnd = NULL;
		st.lpFile = sPath.c_str();
		st.nShow = SW_SHOWDEFAULT;
		ShellExecuteEx(&st);
		return true;
	}
	tstring sTmp = sFileViewerPrg;
	sTmp += _T(" ");
	sTmp += sPath;
	//sTmp += '\"';
	
	STARTUPINFO sStartupInfo = { 0 };
	GetStartupInfo(&sStartupInfo); // we parse oure owne info on 
	sStartupInfo.lpTitle = (_TCHAR*)sFileViewerPrg.c_str();
	PROCESS_INFORMATION stProcesses = {0};

	if( ! CreateProcess( NULL,
				(_TCHAR*)sTmp.c_str(), 
				NULL, 
				NULL, 
				FALSE,
				CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP ,
				NULL,
				NULL,
				&sStartupInfo,
				&stProcesses ) )
	{
		DisplayLastError( LPGENT("Faile to execute external file view") );
	}
	return true;
}


/////////////////////////////////////////////////////////////////////
// Member Function : bGetInternalViewer
// Type            : Global
// Parameters      : None
// Returns         : Returns true if 
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 021016 , 16 October 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

bool bUseInternalViewer()
{
	return bUseIntViewer;
}

/////////////////////////////////////////////////////////////////////
// Member Function : bUseInternalViewer
// Type            : Global
// Parameters      : bNew - ?
// Returns         : Returns true if 
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 021016 , 16 October 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

bool bUseInternalViewer( bool bNew )
{
	bUseIntViewer = bNew;
   if( bUseIntViewer && !hRichEditDll )
   {
      hRichEditDll = LoadLibraryA("RICHED32.DLL");
		if( !hRichEditDll )
		{
			DisplayLastError( LPGENT("Failed to load Rich Edit ( RICHED32.DLL )" ));
			return false;
		}
   }
   else if( !bUseIntViewer && hRichEditDll )
   {
      if( ::FreeLibrary(hRichEditDll) == 0 )
		{
			DisplayLastError( LPGENT("Failed to unload Rich Edit ( RICHED32.DLL )") );
			hRichEditDll = NULL;
			return false;
		}
      hRichEditDll = NULL;
   }
	return true;
}


/////////////////////////////////////////////////////////////////////
// Member Function : RichEditStreamLoadFile
// Type            : Global
// Parameters      : dwCookie - ?
//                   pbBuff   - ?
//                   cb       - ?
//                   pcb      - ?
// Returns         : DWORD CALLBACK
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 021010 , 10 October 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

DWORD CALLBACK RichEditStreamLoadFile(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
   ReadFile((HANDLE)dwCookie, pbBuff, (DWORD)cb, (DWORD *)pcb, 	(LPOVERLAPPED)NULL);
	return (DWORD) ( *pcb >= 0 ? NOERROR : ( *pcb = 0, E_FAIL ) );
}

DWORD CALLBACK RichEditRTFStreamLoadFile(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
	*pcb = ((CLStreamRTFInfo *)dwCookie)->nLoadFileStream( pbBuff, cb );
	if( *pcb )
		return NOERROR;
	return (DWORD)E_FAIL;
}

DWORD CALLBACK RichEditStreamSaveFile(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
	WriteFile((HANDLE)dwCookie, pbBuff, cb , (DWORD*)pcb, 	(LPOVERLAPPED)NULL);
	return *pcb != cb;
}

/*
DWORD dwCurPos = 0;
DWORD dwDataRead = 0;
BYTE * pabFileData = NULL;

DWORD CALLBACK RichEditStreamLoadFile(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
	*pcb = 0;
	while( dwCurPos < dwDataRead && *pcb < cb )
	{
		pbBuff[ *pcb ] = pabFileData[ dwCurPos ];
		dwCurPos++;
		(*pcb)++;
	}
	return (DWORD) ( *pcb >= 0 ? NOERROR : ( *pcb = 0, E_FAIL ) );
}
*/
/////////////////////////////////////////////////////////////////////
// Member Function : bLoadFile
// Type            : Global
// Parameters      : hwndDlg  - ?
//                   hContact - ?
// Returns         : Returns true if 
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 021010 , 10 October 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

bool bLoadFile( HWND hwndDlg , CLHistoryDlg * pclDlg )
{
	DWORD dwStart = GetTickCount();

	HWND hRichEdit = GetDlgItem( hwndDlg , IDC_RICHEDIT );
	if( ! hRichEdit )
	{
		MessageBox( hwndDlg , LPGENT("Failed to get handle to RichEdit!"),MSG_BOX_TITEL,MB_OK );
		return false;
	}


	HANDLE hFile = CreateFile( pclDlg->sPath.c_str() , GENERIC_READ ,
		FILE_SHARE_READ | FILE_SHARE_WRITE , NULL ,
		OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL );


	if( hFile == INVALID_HANDLE_VALUE )
	{
		int nDBCount = (int)CallService( MS_DB_EVENT_GETCOUNT , (WPARAM)(pclDlg->hContact) ,0 );
		_TCHAR szTmp[1500];

		if( nDBCount == -1 )
		{
			mir_sntprintf(szTmp, 1499, LPGENT("Failed to open file\r\n%s\r\n\r\nContact handle is invalid") , pclDlg->sPath.c_str());
		}
		else
		{
			mir_sntprintf( szTmp , 1499, LPGENT("Failed to open file\r\n%s\r\n\r\nMiranda database contains %d events") , pclDlg->sPath.c_str() , nDBCount );
		}
		
		SETTEXTEX stText = {0};
		stText.codepage =	CP_ACP;
		SendMessage( hRichEdit , EM_SETTEXTEX, (WPARAM) &stText, (LPARAM) szTmp	);
		return false;
	}

	POINT ptOldPos;
	SendMessage( hRichEdit , EM_GETSCROLLPOS , 0 , (LPARAM) &ptOldPos );

	bool bScrollToBottom = true;
	if( pclDlg->bFirstLoad )
		pclDlg->bFirstLoad = false;
	else
	{
		SCROLLINFO sScrollInfo = { 0 };
		sScrollInfo.cbSize = sizeof( SCROLLINFO );
		sScrollInfo.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
		if( GetScrollInfo( hRichEdit,SB_VERT,&sScrollInfo) )
			bScrollToBottom = sScrollInfo.nPos + (int)sScrollInfo.nPage + 50 > sScrollInfo.nMax;
	}


	HMENU hSysMenu = GetSystemMenu( hwndDlg , FALSE );
	bool bUseSyntaxHL = (GetMenuState( hSysMenu  , ID_FV_SYNTAX_HL , MF_BYCOMMAND ) & MF_CHECKED)!=0;

/*
	DWORD dwSize = GetFileSize( hFile , NULL );
	dwCurPos = 0;
	pabFileData = new BYTE[ dwSize ];
	ReadFile( hFile , pabFileData, dwSize  , &dwDataRead, 	(LPOVERLAPPED)NULL);
*/
	

	// SendMessage( hRichEdit , EM_SETBKGNDCOLOR, 0 , RGB( 0 , 0 , 128 ) );
	// SendMessage( hRichEdit , EM_SETTEXTMODE, TM_RICHTEXT ,0);

	// DWORD dw = SendMessage( hRichEdit , EM_GETLIMITTEXT,  NULL,  NULL);

	EDITSTREAM eds;
	eds.dwError = 0;

	if( bUseSyntaxHL )
	{
		SendMessage( hRichEdit ,        // handle to destination window 
					EM_EXLIMITTEXT,       // message to send
					0,    // not used; must be zero
					0x7FFFFFFF );   

		CLStreamRTFInfo clInfo( hFile );
		eds.dwCookie = (DWORD)&clInfo;
		eds.pfnCallback = RichEditRTFStreamLoadFile;

		SendMessage(hRichEdit, EM_STREAMIN, (WPARAM)SF_RTF , (LPARAM)&eds);
		pclDlg->bUtf8File = clInfo.bUtf8File;
	}
	else
	{
		eds.dwCookie = (DWORD )hFile;
		eds.pfnCallback = RichEditStreamLoadFile;

		SendMessage(hRichEdit, EM_STREAMIN, (WPARAM)SF_TEXT , (LPARAM)&eds);
	}
   
	CloseHandle( hFile );
	//delete [] pabFileData;

	_TCHAR szTmp[100];
	mir_sntprintf( szTmp , 99, LPGENT("File open time %d\n") , GetTickCount() - dwStart );
	OutputDebugString( szTmp );


	GETTEXTLENGTHEX sData = { 0 };
	sData.flags = GTL_NUMCHARS;
	sData.flags = GTL_DEFAULT;

	DWORD dwDataRead = (DWORD)SendMessage( hRichEdit , EM_GETTEXTLENGTHEX, (WPARAM)&sData , 0 );
	SendMessage(  hRichEdit , EM_SETSEL , dwDataRead - 1, dwDataRead - 1 );

	if( ! bScrollToBottom )
		SendMessage( hRichEdit , EM_SETSCROLLPOS , 0 , (LPARAM) &ptOldPos );

	mir_sntprintf( szTmp , 99, LPGENT("With scroll to bottom %d\n") , GetTickCount() - dwStart );
	OutputDebugString( szTmp );

	return true;
}

/////////////////////////////////////////////////////////////////////
// Member Function : bAdvancedCopy
// Type            : Global
// Parameters      : hwnd - handle to RichEdit control
// Returns         : Returns true if text was copied to the clipboard
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 030730 , 30 juli 2003
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

bool bAdvancedCopy(HWND hwnd)
{
	CHARRANGE sSelectRange;
	SendMessage( hwnd, EM_EXGETSEL, 0 , (LPARAM)&sSelectRange );
	int nSelLenght = sSelectRange.cpMax - sSelectRange.cpMin + 1; // +1 for null termination
	if( nSelLenght > 1 )
	{
		OpenClipboard(NULL);
		EmptyClipboard();

		_TCHAR * pszSrcBuf = new _TCHAR[ nSelLenght];
		pszSrcBuf[0] = 0;
		SendMessage( hwnd, EM_GETSELTEXT, 0 , (LPARAM)pszSrcBuf );

		HANDLE hDecMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, nSelLenght );
		_TCHAR * pszCurDec = (_TCHAR*)GlobalLock(hDecMem);

		bool bInSpaces = false;
		for( _TCHAR * pszCur = pszSrcBuf ; pszCur[0] ; pszCur++ )
		{
			if( bInSpaces )
			{
				if( pszCur[0] == ' ' )
					continue;
				bInSpaces = false;
			}

			if( pszCur[0] == '\n' )
			{
				bInSpaces = true;
			}
			pszCurDec[0] = pszCur[0];
			pszCurDec++;
		}
		pszCurDec[0] = 0;
		GlobalUnlock(hDecMem);

		SetClipboardData(CF_TEXT,hDecMem);
		delete [] pszSrcBuf;
		CloseClipboard();
		return true;
	}
	return false;
}

/////////////////////////////////////////////////////////////////////
// Member Function : EditSubclassProc
// Type            : Global
// Parameters      : hwnd   - ?
//                   uMsg   - ?
//                   wParam - ?
//                   lParam - ?
// Returns         : LRESULT CALLBACK
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 021013 , 13 October 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

LRESULT CALLBACK EditSubclassProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	CLHistoryDlg * pclDlg = (CLHistoryDlg *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
	switch( msg )
	{
		case WM_CONTEXTMENU:
		{
			HMENU nMenu = LoadMenu( hInstance, MAKEINTRESOURCE( IDR_FV_EDIT ) );
			HMENU nSubMenu = GetSubMenu( nMenu , 0 );
			POINT pt;
			pt.x=(short)LOWORD(lParam);
			pt.y=(short)HIWORD(lParam);

			if(pt.x==-1 && pt.y==-1)
			{
				DWORD dwStart,dwEnd;
				SendMessage(  hwnd , EM_GETSEL , (WPARAM)&dwStart, (LPARAM)&dwEnd );
				SendMessage(  hwnd , EM_POSFROMCHAR, (WPARAM)&pt, (LPARAM)dwEnd);
				ClientToScreen( hwnd , &pt );
			}
			TrackPopupMenu( nSubMenu , TPM_RIGHTBUTTON , pt.x , pt.y , 0 , hwnd , 0 );

			DestroyMenu( nSubMenu );
			DestroyMenu( nMenu );
			return TRUE;
		}
		case WM_GETDLGCODE:
		{
			return DLGC_WANTARROWS;
		}
		case WM_COPY:
		{ // not working for "CTRL + C" 
			if( bAdvancedCopy( hwnd ) )
				return TRUE;
			break;
		}
		case WM_KEYDOWN:
		{
			if( (wParam == VK_INSERT || wParam == 'C') && (GetKeyState(VK_CONTROL) & 0x80) )
			{
				if( bAdvancedCopy( hwnd ) )
					return TRUE;
			}
			break;
		}
		case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{
				case ID_EDIT_COPY:
				{
					SendMessage( hwnd , WM_COPY, 0, 0 );
					return TRUE;
				}
			}
		}
	}
	if( msg == UM_FIND_CMD )
	{
		FINDREPLACE *fr = (FINDREPLACE *)lParam;
		if( fr->Flags & FR_DIALOGTERM )
		{
			pclDlg->hFindDlg = NULL;
			return 0;
		}

		FINDTEXT ft = { 0 };

		if( fr->Flags & FR_FINDNEXT) 
		{
			ft.lpstrText = fr->lpstrFindWhat;

			SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&ft.chrg);
			ft.chrg.cpMin = ft.chrg.cpMax+1;
			ft.chrg.cpMax = -1;
			LRESULT res = SendMessage(hwnd, EM_FINDTEXT, (WPARAM)fr->Flags,(LPARAM)&ft);
			if(res == -1) {
				ft.chrg.cpMin = 0;
				res = (int)SendMessage(hwnd, EM_FINDTEXT, (WPARAM)fr->Flags,(LPARAM)&ft);
				if(res == -1) 
				{
					MessageBox( hwnd , LPGENT("Search string was not found !"),MSG_BOX_TITEL,MB_OK );
					return 0;
				}
			}			
			ft.chrg.cpMin = LONG(res);
			ft.chrg.cpMax = LONG(res + _tcslen(fr->lpstrFindWhat));
			SendMessage(hwnd , EM_EXSETSEL, 0, (LPARAM)&ft.chrg);
			return 0;
		}
		
	}
	return CallWindowProc(pclDlg->wpOrigEditProc, hwnd, msg, wParam, lParam); 
}

/////////////////////////////////////////////////////////////////////
// Member Function : SetWindowsCtrls
// Type            : Global
// Parameters      : hwndDlg - ?
// Returns         : void
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 021001 , 01 October 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

void SetWindowsCtrls( HWND hwndDlg )
{
	RECT rNewSize;
	GetClientRect( hwndDlg , &rNewSize );

	RECT rCurSize;

	const int nSpacing = 12;
	

	HWND hButton = GetDlgItem( hwndDlg , IDOK );
	GetWindowRect( hButton , &rCurSize );
	int nButtonHeight = rCurSize.bottom - rCurSize.top;

	SetWindowPos( GetDlgItem( hwndDlg , IDC_RICHEDIT ) , 0 , 
		nSpacing , nSpacing , 
		rNewSize.right - (nSpacing * 2) ,
		rNewSize.bottom - ( nSpacing * 3 + nButtonHeight ), 
		SWP_NOZORDER );


	int nButtonWidth = rCurSize.right - rCurSize.left;
	int nButtonSpace = (rNewSize.right - ( 3 * nButtonWidth )) / 4;
	int nButtonTop = rNewSize.bottom - ( nSpacing + nButtonHeight );
	int nCurLeft = nButtonSpace;

	SetWindowPos( GetDlgItem( hwndDlg , IDC_FV_FIND ) , 0 , 
		nCurLeft , nButtonTop , 0 , 0 , SWP_NOZORDER | SWP_NOSIZE );
	
	nCurLeft += nButtonSpace + nButtonWidth;

	SetWindowPos( GetDlgItem( hwndDlg , IDC_FV_EXTERNAL ) , 0 , 
		nCurLeft , nButtonTop , 0 , 0 , SWP_NOZORDER | SWP_NOSIZE );

	nCurLeft += nButtonSpace + nButtonWidth;
	
	SetWindowPos( hButton , 0 , 
		nCurLeft , nButtonTop , 0 , 0 , SWP_NOZORDER | SWP_NOSIZE );

}

/////////////////////////////////////////////////////////////////////
// Member Function : SetRichEditFont
// Type            : Global
// Parameters      : hRichEdit    - RichEdit to set the font in
//                   bUseSyntaxHL - Is Syntax hilighting is used the color 
//												will not be set							
// Returns         : void
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 030205 , 05 February 2003
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

void SetRichEditFont(HWND hRichEdit, bool bUseSyntaxHL )
{
	CHARFORMAT ncf = { 0 };
	ncf.cbSize = sizeof( CHARFORMAT );
	ncf.dwMask = CFM_BOLD | CFM_FACE | CFM_ITALIC | CFM_SIZE | CFM_UNDERLINE;
	ncf.dwEffects = DBGetContactSettingDword( NULL , MODULE , szFileViewDB "TEffects" , 0 );
	ncf.yHeight = DBGetContactSettingDword( NULL , MODULE , szFileViewDB "THeight" , 165 );
	_tcscpy( ncf.szFaceName  , _DBGetString( NULL , MODULE , szFileViewDB "TFace" , _T("Courier New")).c_str() );

	if( ! bUseSyntaxHL )
	{
		ncf.dwMask |= CFM_COLOR;
		ncf.crTextColor = DBGetContactSettingDword( NULL , MODULE , szFileViewDB "TColor" , 0 );
	}
	SendMessage(hRichEdit, EM_SETCHARFORMAT, (WPARAM)SCF_ALL, (LPARAM)&ncf);

}

/////////////////////////////////////////////////////////////////////
// Member Function : DlgProcFileViewer
// Type            : Global
// Parameters      : hwndDlg - ?
//                   msg     - ?
//                   wParam  - ?
//                   lParam  - ?
// Returns         : static BOOL CALLBACK
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 020929 , 29 September 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

static INT_PTR CALLBACK DlgProcFileViewer(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	CLHistoryDlg * pclDlg = (CLHistoryDlg *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);

	switch (msg)
	{
		case WM_INITDIALOG:
		{
			SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam);
			CLHistoryDlg * pclDlg = (CLHistoryDlg *)lParam;
#ifdef _UNICODE
			EnableWindow( GetDlgItem( hwndDlg , IDC_FV_FIND ) , FALSE );
#endif
			SendMessage(hwndDlg, WM_SETICON, ICON_BIG, 
				(LPARAM)LoadIcon( hInstance, MAKEINTRESOURCE(IDI_EXPORT_MESSAGE)));

			HWND hRichEdit = GetDlgItem( hwndDlg , IDC_RICHEDIT );
			pclDlg->wpOrigEditProc = (WNDPROC) SetWindowLongPtr( hRichEdit, GWLP_WNDPROC, (LONG) EditSubclassProc); 
			
			SetWindowLongPtr( hRichEdit, GWLP_USERDATA, (LONG) pclDlg ); 
			
			SendMessage( hRichEdit , EM_SETEVENTMASK  , 0    , ENM_LINK);
			SendMessage( hRichEdit , EM_AUTOURLDETECT , TRUE , 0 );
			
			HMENU hSysMenu = GetSystemMenu( hwndDlg , FALSE );

			InsertMenu( hSysMenu , 0 , MF_SEPARATOR | MF_BYPOSITION , 0 , 0 );
			InsertMenu( hSysMenu , 0 , MF_STRING | MF_BYPOSITION , ID_FV_SAVE_AS_RTF, LPGENT("Save as RTF") );

			InsertMenu( hSysMenu , 0 , MF_SEPARATOR | MF_BYPOSITION , 0 , 0 );

			BYTE bUseCC = (BYTE)DBGetContactSettingByte( NULL , MODULE , szFileViewDB "UseCC" , 0 );
			InsertMenu( hSysMenu , 0 , MF_STRING | MF_BYPOSITION | ( bUseCC ? MF_CHECKED : 0 ) , ID_FV_COLOR, LPGENT("Color...") );

			if( bUseCC )
			{
				SendMessage( hRichEdit , EM_SETBKGNDCOLOR, 0 , 
					DBGetContactSettingDword( NULL , MODULE , szFileViewDB "CustomC" , RGB(255,255,255) )
				);
			}

			InsertMenu( hSysMenu , 0 , MF_STRING | MF_BYPOSITION , ID_FV_FONT, LPGENT("Font...") );


			bool bUseSyntaxHL = DBGetContactSettingByte( NULL , MODULE , szFileViewDB "UseSyntaxHL" , 1 )!=0;
			InsertMenu( hSysMenu , 0 , MF_STRING | MF_BYPOSITION | ( bUseSyntaxHL ? MF_CHECKED : 0 ) , ID_FV_SYNTAX_HL, LPGENT("Syntax highlight") );

			SetRichEditFont( hRichEdit , bUseSyntaxHL );

			TranslateDialogDefault(hwndDlg);

			int cx= DBGetContactSettingDword( NULL , MODULE , szFileViewDB "cx" , 0 );
			int cy= DBGetContactSettingDword( NULL , MODULE , szFileViewDB "cy" , 0 );

			if( cx > 0 && cy > 0)
			{
				int x = DBGetContactSettingDword( NULL , MODULE , szFileViewDB "x" , 0 );
				int y = DBGetContactSettingDword( NULL , MODULE , szFileViewDB "y" , 0 );

				SetWindowPos( hwndDlg , NULL , x , y , cx , cy , SWP_NOZORDER );
			}

			pclDlg->sPath = GetFilePathFromUser( pclDlg->hContact );

			SetWindowsCtrls( hwndDlg );


			bLoadFile(hwndDlg , pclDlg );

			{ // set Title 
				_TCHAR szFormat[200];
				_TCHAR szTitle[200];
				if( GetWindowText( hwndDlg , szFormat , sizeof( szFormat ) ) )
				{
					const _TCHAR * pszNick = NickFromHandle( pclDlg->hContact );
					tstring sPath = pclDlg->sPath;
					string::size_type n = sPath.find_last_of( '\\' );
					if( n != sPath.npos )
						sPath.erase( 0 , n + 1 );

					if( _sntprintf( szTitle , sizeof( szTitle ) , szFormat , pszNick , sPath.c_str() , (pclDlg->bUtf8File ? _T("UTF8"):_T("ANSI")) ) > 0 )
					{
						SetWindowText( hwndDlg , szTitle);
					}
				}
			}

			return TRUE; 
		}
		case WM_RELOAD_FILE:
		{
			bLoadFile(hwndDlg , pclDlg );
			return TRUE;
		}
		case WM_SIZE:
		case WM_SIZING:
		{
			SetWindowsCtrls( hwndDlg );
			return TRUE;
		}
		case WM_NCDESTROY:
		{
			EnterCriticalSection( &csHistoryList );
			clHistoryDlgList.remove( pclDlg );
			LeaveCriticalSection( &csHistoryList );

			delete pclDlg;
         SetWindowLongPtr(hwndDlg,GWLP_USERDATA,NULL);
         return 0;
      }
      case WM_DESTROY:
		{
			RECT rSize;
			if( GetWindowRect( hwndDlg , &rSize ) )
			{
				// first we make sure the window has resonable dimentions.
				// if it is minimized it will not have that.
				if( rSize.left <= -32000 || rSize.top <= -32000 )
					return 0;
				if( rSize.right <= -32000 || rSize.bottom <= -32000 )
					return 0;
				DBWriteContactSettingDword( NULL , MODULE , szFileViewDB "x" , rSize.left );
				DBWriteContactSettingDword( NULL , MODULE , szFileViewDB "y" , rSize.top );
				DBWriteContactSettingDword( NULL , MODULE , szFileViewDB "cx" , rSize.right - rSize.left );
				DBWriteContactSettingDword( NULL , MODULE , szFileViewDB "cy" , rSize.bottom - rSize.top );
			}
			return 0;
		}
		case WM_SYSCOMMAND:
		{
			HMENU hSysMenu = GetSystemMenu( hwndDlg , FALSE );
			bool bUseSyntaxHL = (GetMenuState( hSysMenu  , ID_FV_SYNTAX_HL , MF_BYCOMMAND ) & MF_CHECKED)!=0;
			HWND hRichEdit = GetDlgItem( hwndDlg , IDC_RICHEDIT );

			if ((wParam & 0xFFF0) == ID_FV_FONT)
			{
				LOGFONT lf = { 0 };
				lf.lfHeight = 14L;
				
				{	DWORD dwEffects = DBGetContactSettingDword( NULL , MODULE , szFileViewDB "TEffects" , 0 );
					lf.lfWeight = (dwEffects & CFE_BOLD) ? FW_BOLD : 0;
					lf.lfUnderline = (dwEffects & CFE_UNDERLINE) != 0;
					lf.lfStrikeOut = (dwEffects & CFE_STRIKEOUT) != 0;
					lf.lfItalic = (dwEffects & CFE_ITALIC) != 0;
				}
				_tcscpy(lf.lfFaceName, _DBGetString( NULL , MODULE , szFileViewDB "TFace" , _T("Courier New")).c_str());
				CHOOSEFONT cf = { 0 };
				cf.lStructSize = sizeof( cf );
				cf.hwndOwner = hwndDlg;
				cf.lpLogFont = &lf;
				cf.rgbColors = DBGetContactSettingDword( NULL , MODULE , szFileViewDB "TColor" , 0 );
				cf.Flags = CF_EFFECTS | CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;

				if( ChooseFont( &cf ) )
				{
					DWORD dwEffects = (lf.lfWeight == FW_BOLD ?  CFE_BOLD : 0) |
							  			   (lf.lfItalic          ?  CFE_ITALIC : 0) |
										   (lf.lfStrikeOut 	   ? CFE_STRIKEOUT : 0) |
										   (lf.lfUnderline    ?  CFE_UNDERLINE : 0);

					DBWriteContactSettingDword( NULL , MODULE , szFileViewDB "TEffects" , dwEffects );
					DBWriteContactSettingDword( NULL , MODULE , szFileViewDB "THeight" , cf.iPointSize * 2 );
					DBWriteContactSettingDword( NULL , MODULE , szFileViewDB "TColor" , cf.rgbColors );
					DBWriteContactSettingTString( NULL , MODULE , szFileViewDB "TFace" , lf.lfFaceName );
					SetRichEditFont( hRichEdit , bUseSyntaxHL );
				}
				return TRUE;
			}
			else if ((wParam & 0xFFF0) == ID_FV_COLOR)
			{
				BYTE bUseCC = ! DBGetContactSettingByte( NULL , MODULE , szFileViewDB "UseCC" , 0 );
				if( bUseCC )
				{
					CHOOSECOLOR cc = {0};
					cc.lStructSize = sizeof( cc );
					cc.hwndOwner = hwndDlg;
					cc.rgbResult = DBGetContactSettingDword( NULL , MODULE , szFileViewDB "CustomC" , RGB(255,255,255) );
					cc.Flags = CC_ANYCOLOR | CC_FULLOPEN | CC_RGBINIT;
					static COLORREF MyCustColors[16] = { 0xFFFFFFFF };
					cc.lpCustColors = MyCustColors;
					if( ChooseColor( &cc ) )
					{
						SendMessage( hRichEdit , EM_SETBKGNDCOLOR, 0 , cc.rgbResult );
						DBWriteContactSettingDword( NULL , MODULE , szFileViewDB "CustomC" , cc.rgbResult );
					}
					else
					{
						/*DWORD dwError =*/ CommDlgExtendedError();
						return TRUE;
					}
				}
				else
				{
					SendMessage( hRichEdit , EM_SETBKGNDCOLOR, TRUE , 0 );
				}
				CheckMenuItem( hSysMenu , ID_FV_COLOR , MF_BYCOMMAND | (bUseCC ? MF_CHECKED : 0) );
				DBWriteContactSettingByte( NULL , MODULE , szFileViewDB "UseCC" , bUseCC );
				return TRUE;
			}
			else if ((wParam & 0xFFF0) == ID_FV_SYNTAX_HL)
			{
				// we use the current state from the menu not the DB value
				// because we want to toggel the option for this window
				// still the new option selected will be stored.
				// so user may open 2 windows, now he can set SyntaxHL in both.

				bUseSyntaxHL = !bUseSyntaxHL;
				CheckMenuItem( hSysMenu , ID_FV_SYNTAX_HL , MF_BYCOMMAND | (bUseSyntaxHL ? MF_CHECKED : 0) );
				DBWriteContactSettingByte( NULL , MODULE , szFileViewDB "UseSyntaxHL" , bUseSyntaxHL );

				if( bUseSyntaxHL )
					bLoadFile(hwndDlg , pclDlg );
				else
					SetRichEditFont( hRichEdit , bUseSyntaxHL );

				return TRUE;
			}
			else if ((wParam & 0xFFF0) == ID_FV_SAVE_AS_RTF)
			{
				tstring sFile = pclDlg->sPath;
				sFile += _T(".rtf");
				HANDLE hFile = CreateFile( sFile.c_str() , GENERIC_WRITE ,
					FILE_SHARE_READ , NULL , CREATE_ALWAYS , FILE_ATTRIBUTE_NORMAL , NULL );

				if( hFile == INVALID_HANDLE_VALUE  )
				{
					DisplayLastError( LPGENT("Failed to create file") );
					return TRUE;
				}

				EDITSTREAM eds;
				eds.dwCookie = (DWORD )hFile;
				eds.dwError = 0;
				eds.pfnCallback = RichEditStreamSaveFile;
				LRESULT nWriteOk = SendMessage(hRichEdit, EM_STREAMOUT, (WPARAM)SF_RTF , (LPARAM)&eds);
				if( nWriteOk <= 0 || eds.dwError != 0 )
				{
					DisplayLastError( LPGENT("Failed to save file") );
					CloseHandle( hFile );
					return TRUE;
				}
				CloseHandle( hFile );
				tstring sReport = LPGENT("History was saved successfully in file\r\n");
				sReport += sFile;
				MessageBox( NULL , sReport.c_str() ,MSG_BOX_TITEL ,MB_OK );
				return TRUE;
			}
			return FALSE;
		}

		case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{
				case IDCANCEL:
				case IDOK:
					DestroyWindow(hwndDlg);
					return TRUE;
				case IDC_FV_EXTERNAL:
					bOpenExternaly( pclDlg->hContact );
					return TRUE;
				case IDC_FV_FIND:
				{
					if( pclDlg->hFindDlg )
					{
						BringWindowToTop( pclDlg->hFindDlg );
						return TRUE;
					}
					pclDlg->fr.hwndOwner = GetDlgItem( hwndDlg , IDC_RICHEDIT );
					pclDlg->hFindDlg = FindText( &pclDlg->fr );
					return TRUE;
				}
			}
			break;
		}
		case WM_NOTIFY:
		{
			if( ((NMHDR*)lParam)->idFrom == IDC_RICHEDIT ) 
			{
				if( ((NMHDR*)lParam)->code == EN_LINK )
				{
					ENLINK* pstLink = (ENLINK*)lParam;
					if( pstLink->msg == WM_LBUTTONUP )
					{
						_TCHAR szUrl[ 500 ];
						if( (pstLink->chrg.cpMax - pstLink->chrg.cpMin) > (sizeof( szUrl ) - 2) )
							return FALSE;

						TEXTRANGE stToGet;
						stToGet.chrg = pstLink->chrg;
						stToGet.lpstrText = szUrl;
						if( SendMessage( pstLink->nmhdr.hwndFrom , EM_GETTEXTRANGE , 0 , (LPARAM)&stToGet ) > 0 )
						{
							CallService(MS_UTILS_OPENURL,1,(LPARAM)szUrl);
						}
						return TRUE;
					}
				}
			}
			break;
		}
		case WM_CLOSE:
		{
			DestroyWindow(hwndDlg);
			return TRUE;
		}
	}
	return FALSE;
//FALSE;//DefWindowProc( hwndDlg, msg, wParam, lParam );
//DefDlgProc( hwndDlg, msg, wParam, lParam );
}



/////////////////////////////////////////////////////////////////////
// Member Function : bShowFileViewer
// Type            : Global
// Parameters      : hContact - ?
// Returns         : Returns true if 
// Description     : 
//                   
// References      : -
// Remarks         : -
// Created         : 020929 , 29 September 2002
// Developer       : KN   
/////////////////////////////////////////////////////////////////////

bool bShowFileViewer( HANDLE hContact )
{
	CLHistoryDlg * pcl = new CLHistoryDlg( hContact );
	pcl->hWnd = CreateDialogParam( hInstance,MAKEINTRESOURCE(IDD_FILE_VIEWER),NULL,DlgProcFileViewer,(LPARAM)pcl);
	if( pcl->hWnd )
	{
		EnterCriticalSection( &csHistoryList );
		clHistoryDlgList.push_front( pcl );
		LeaveCriticalSection( &csHistoryList );

		ShowWindow( pcl->hWnd , SW_SHOWNORMAL );
		return true;
	}
	DisplayLastError( LPGENT("Failed to create history dialog") );
	delete pcl;
	return false;
}