//	VirtualDub - Video processing and capture application
//	Copyright (C) 1998-2001 Avery Lee
//
//	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.

// *******************************************************************
// *** AVS Modification 										   ***
// *** Tobias Minich, August 2002								   ***
// ***															   ***
// *** Since I'm a c++ newbie, i took the HexEditor as a template. ***
// *** Thanks to Toff for cleaning it up :)						   ***

// Tweakable line modification by christophe.paris@free.fr (Toff)

#pragma warning(disable : 4786)

#define f_AVSVIEWER_CPP

#define _WIN32_WINNT 0x0500

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <crtdbg.h>

#include <windows.h>
#include <commctrl.h>
#include <commdlg.h>

//#include <Richedit.h>
// 08-11-2002, Cyrius : added a test for RichEdit 2.0 features
// With my VC98 version I have version 2.1 but it doesn't contain SETTEXTEX, ST_SELECTION and EM_SETTEXTEX
// Platform SDK seems needed
//#if (!defined(_RICHEDIT_VER) | (_RICHEDIT_VER < 0x0200) | !defined(EM_SETTEXTEX))
//  #error You need Richedit.h (Platform SDK) v2 or higher
//#endif

#include <vector>
#include <set>
#include <algorithm>
#include <string>

#define INCLUDE_DEPRECATED_FEATURES
#include "Scintilla.h"
#include "SciLexer.h"
#include "avsconst.h"

#include "..\resource.h"
#include "..\oshelper.h"
#include "..\gui.h"
#include "..\error.h"
#include "..\list.h"

#include "AVSViewer.h"
#include "..\ProgressDialog.h"
#include "..\virtualdubmod_messages.h"

extern HINSTANCE g_hInst;

extern HMODULE g_hAVSLexer;

using namespace std;

//////////////////////////////////////////////////////////////////////////////

extern const char szAVSEditorClassName[]="birdyAVSEditor";
extern const char szAVSViewerClassName[]="birdyAVSViewer";
static const char g_szAVSWarning[]="Script editor warning";

const char avsBasicWords[] = "end return global function clip int bool float assert default string last true false asserteval try catch";

const char avsInternalWords[] = "AviSource OpenDMLSource AviFileSource WavSource DirectShowSource SegmentedAviSource SegmentedDirectShowSource "
     "BlankClip Blackness IPCSource AddBorders Blur Sharpen ConvertToYUY2 ConvertBackToYUY2 ConvertToRGB ConvertToRGB24 ConvertToRGB32 "
     "Crop CropBottom GeneralConvolution GreyScale HSIAdjust Layer Mask ResetMask ColorKeyMask Letterbox Levels MergeChroma MergeLuma ReduceBy2 "
     "HorizontalReduceBy2 VerticalReduceBy2 RGBAdjust BicubicResize BilinearResize LanczosResize PointResize SpatialSoften TemporalSoften "
     "Tweak AlignedSplice UnalignedSplice AssumeFPS ChangeFPS ConvertFPS DeleteFrame Dissolve DuplicateFrame FadeIn FadeIn2 FadeOut FadeOut2 "
     "FreezeFrame Loop SelectEven SelectOdd SelectEvery Trim AssumeFrameBased AssumeFieldBased AssumeBFF AssumeTFF Bob ComplementParity "
     "DoubleWeave Interleave Pulldown SeparateFields SwapFields Weave Amplify AmplifydB AssumeSampleRate AudioDub ConvertAudioTo16bit ConvertToMono "
     "DelayAudio EnsureVBRMP3Sync GetLeftChannel GetRightChannel KillAudio LowPassAudio HighPassAudio MixAudio MonoToStereo Normalize ResampleAudio "
     "Animate FixBrokenChromaUpsampling FixLuminance FlipVertical PeculiarBlend Reverse Subtitle ColorBars Compare Histogram MessageClip "
	 "ShowFiveVersions ShowFrameNumber ShowSMPTE StackHorizontal StackVertical Subtract Version LoadVirtualDubPlugin LoadPlugin SetMemoryMax "
	 "LeftStr RightStr MidStr FindStr RevStr StrLen Sign Nop Rand Select Exist VersionNumber VersionString IsRGB24 IsRGB32 Int Frac Float Value " 
	 "HexValue SetWorkingDir";

struct less_nocase {
	static bool compare_chars(char x, char y) {
		return toupper(x) < toupper(y);
	}
	bool operator()(const string& x, const string& y) const {
		return lexicographical_compare(x.begin(), x.end(),
			y.begin(), y.end(),
			compare_chars);
	}
};

set<string,less_nocase> AVSToken;
//vector<string> AVSToken;

char *avsWords;

//////////////////////////////////////////////////////////////////////////////

class AVSEditor {
private:
	const HWND	hwnd;
	HWND	hwndStatus;
	HWND	hwndRef;
	char	lpszFileName[MAX_PATH];
	HFONT	hfont;
	void*	coloredit;

	HWND	hwndView;
	WNDPROC OldAVSViewWinProc; // Toff

	bool	bEnableWrite;
//	bool	bTrim;
	bool	bLineNumbers;
	int (*fnScintilla)(void*,int,int,int);
	void * ptrScintilla;
	int		scriptType;

public:
	AVSEditor(HWND);
	~AVSEditor();

	static LRESULT APIENTRY AVSEditorWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) throw();
	static LRESULT APIENTRY SubAVSEditorWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) throw();
private:
	void Init() throw();
	void Open() throw();
	bool Commit() throw();

	void SetStatus(const char *format, ...) throw();
	void UpdateStatus() throw();
	void UpdateLineNumbers();
	void SetAStyle(int style, COLORREF fore, COLORREF back = RGB(0xff,0xff,0xff), int size = 0, const char *face = NULL);

	LRESULT Handle_WM_COMMAND(WPARAM wParam, LPARAM lParam) throw();
	LRESULT Handle_WM_SIZE(WPARAM wParam, LPARAM lParam) throw();
	LRESULT Handle_WM_NOTIFY(HWND hwndFrom, UINT code) throw();

	int SendMessageSci(int Message, int wParam = 0, int lParam = 0) 
		{return fnScintilla(ptrScintilla, Message, wParam, lParam);}

	void SetScriptType(int type);
	void CheckBracing();
	void DoCalltip();
	
	// Toff ----->
	void RemoveLineCommentInRange(int line);
	bool CommentUncommentLine(int lineNumber, bool comment);
	// <----- Toff
};

////////////////////////////

AVSEditor::AVSEditor(HWND _hwnd) : hwnd(_hwnd), hfont(0)
{

}

AVSEditor::~AVSEditor() {
	if (hfont)
		DeleteObject(hfont);
}

// Toff ----->

bool AVSEditor::CommentUncommentLine(int lineNumber, bool comment)
{
	char buff[2];

	// Get start of line index
	int line_index = SendMessageSci(SCI_POSITIONFROMLINE, lineNumber, 0);
	SendMessageSci(SCI_SETSEL, line_index, line_index+1);
	SendMessageSci(SCI_GETSELTEXT, 0, (LPARAM)buff);	
  
	if(buff[0] == '#')
	{
		if(!comment)
		{
			// This line must be uncommented
			buff[0] = '\0';
			SendMessageSci(SCI_REPLACESEL, 0, (LPARAM)buff);
			return true;
		}
	}
	else
	{
		if(comment)
		{
			// This line must be commented
			SendMessageSci(SCI_SETSEL, line_index, line_index);
			buff[0] = '#';
			buff[1] = '\0';
			SendMessageSci(SCI_REPLACESEL, 0, (LPARAM)buff);
			return true;
		}
	}

	return false; // No change
}

void AVSEditor::RemoveLineCommentInRange(int line)
{
//	CHARRANGE chr;
	char *buff = NULL;
	int charIndex = 0;
	int charStart = 0;
	int charEnd = 0;
	int lineNumber = 0;

//	int totalLineNumber = SendMessage(hwndView,EM_GETLINECOUNT ,0,0);
	int totalLineNumber = SendMessageSci(SCI_GETLINECOUNT ,0,0);

	// Get cursor position
//	SendMessage(hwndView, EM_EXGETSEL, 0, (LPARAM)&chr);
//	charIndex = chr.cpMin;
	charStart = SendMessageSci(SCI_GETSELECTIONSTART, 0, 0);
	charEnd = SendMessageSci(SCI_GETSELECTIONEND, 0, 0);
	charIndex = charStart;
//	lineNumber = SendMessage(hwndView, EM_LINEFROMCHAR, charIndex, 0);
	lineNumber = SendMessageSci(SCI_LINEFROMPOSITION, charIndex, 0);

	// Go up and find the "#StartTweaking" line
	const char* startLineText = "#StartTweaking";
	int startLineLen = strlen(startLineText);

	for( ;lineNumber>=0;lineNumber--) {
//		if (SendMessage(hwndView, EM_LINELENGTH, lineNumber, 0) != startLineLen) continue;
		buff = new char[SendMessageSci(SCI_LINELENGTH, lineNumber)];
		SendMessageSci(SCI_GETLINE, lineNumber, (LPARAM)buff);
		if (strncmp(startLineText,buff,startLineLen) != 0 ) {
			delete [] buff;
			buff = NULL;
			continue;
		}
		break;
	}
	if (buff) delete [] buff;
	buff = NULL;

	if (lineNumber<0) return;

	lineNumber++;
	int firstLine = lineNumber;

	const char* stopLineText = "#StopTweaking";
	int stopLineLen = strlen(stopLineText);
	int lastLine = -1;

	for( ;lineNumber<totalLineNumber;lineNumber++) {
		buff = new char[SendMessageSci(SCI_LINELENGTH, lineNumber)];
		SendMessageSci(SCI_GETLINE, lineNumber, (LPARAM)buff);
		if (strncmp(stopLineText,buff,stopLineLen) == 0) {
			lastLine = lineNumber;
			break;
		}
// 	  	CommentUncommentLine(lineNumber, ((lineNumber - firstLine) != line));		
		delete [] buff;
		buff = NULL;
	}
	if (buff) delete [] buff;
	for (lineNumber = firstLine; lineNumber<lastLine; lineNumber++)
 	  	CommentUncommentLine(lineNumber, ((lineNumber - firstLine) != line));		
//	while( (SendMessage(hwndView, EM_LINELENGTH, charIndex, 0) != startLineLen ||
//		   SendMessage(hwndView, EM_GETLINE, lineNumber, (LPARAM)buff) > 0) &&
//		   strncmp(startLineText,buff,startLineLen) != 0 )
/*	while( (SendMessageSci(SCI_LINELENGTH, lineNumber, 0) != startLineLen ||
		   SendMessageSci(SCI_GETLINE, lineNumber, (LPARAM)buff) > 0) &&
		   strncmp(startLineText,buff,startLineLen) != 0 )
	{
		lineNumber--;
		if(lineNumber < 0)
		{
			return;
		}
//		charIndex = SendMessage(hwndView, EM_LINEINDEX, lineNumber, 0);
		charIndex = SendMessageSci(SCI_POSITIONFROMLINE, lineNumber, 0);
	}

	lineNumber++;
	int firstLine = lineNumber;

	StatusDebug(lineNumber, SendMessageSci(SCI_LINELENGTH, 3, 0), 0);

//	charIndex = SendMessage(hwndView, EM_LINEINDEX, firstLine, 0);
	charIndex = SendMessageSci(SCI_POSITIONFROMLINE, firstLine, 0);

	// Go down and stop on the "#StopTweaking" line
	const char* stopLineText = "#StopTweaking";
	int stopLineLen = strlen(stopLineText)+1;

//	while( (SendMessage(hwndView, EM_LINELENGTH, charIndex, 0) != stopLineLen ||
//		   SendMessage(hwndView, EM_GETLINE, lineNumber, (LPARAM)buff) > 0) &&
//		   strncmp(stopLineText,buff,stopLineLen) != 0 )
	while( (SendMessageSci(SCI_LINELENGTH, lineNumber, 0) != stopLineLen ||
		   SendMessageSci(SCI_GETLINE, lineNumber, (LPARAM)buff) > 0) &&
		   strncmp(stopLineText,buff,stopLineLen) != 0 )
	{
		CommentUncommentLine(lineNumber, ((lineNumber - firstLine) != line));		
		lineNumber++;
		if(lineNumber >= totalLineNumber)
		{
			break;
		}
//		charIndex = SendMessage(hwndView, EM_LINEINDEX, lineNumber, 0);
		charIndex = SendMessageSci(SCI_POSITIONFROMLINE, lineNumber, 0);
	}
*/
	// Restore original position
//	SendMessage(hwndView, EM_EXSETSEL, 0, (LPARAM)&chr);
	SendMessageSci(SCI_SETSEL, charStart, charEnd);
}

LRESULT AVSEditor::SubAVSEditorWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) throw()
{
	AVSEditor *pcd = (AVSEditor *)GetWindowLong(
		(HWND)GetWindowLong(hwnd, GWL_HWNDPARENT), 0);

	if(!pcd)
	{
		return 0;
	}

	switch (msg)
	{
		case WM_KEYDOWN:
			if ((GetKeyState(VK_SHIFT)>=0) && (GetKeyState(VK_MENU)>=0) && (GetKeyState(VK_CONTROL) < 0))
			{
				if(wParam >= VK_F1 && wParam <= VK_F12)
				{
					pcd->RemoveLineCommentInRange(wParam - VK_F1);
					if (pcd->Commit())
						PostMessage(pcd->hwndRef, WM_COMMAND, ID_REFRESH, 0);
					break;
				}
				if(wParam >= 0x30 /*VK_0*/ && wParam <= 0x31 /*VK_1*/)
				{
					pcd->SetScriptType(wParam - 0x30);
					break;
				}
//				if (wParam == VK_SPACE)
//				{
//					pcd->SendMessageSci(SCI_AUTOCSHOW, 0, (int) avsInternalWords);
//				}
			}
/*			if (GetKeyState(VK_SHIFT) < 0)
			{
				if (wParam == VK_SPACE)
				{
					pcd->SendMessageSci(SCI_AUTOCSHOW, 0, (int) avsInternalWords);
					return -1;
				}
			}*/
			int tmp;
			tmp = CallWindowProc(pcd->OldAVSViewWinProc, hwnd, msg,	wParam, lParam); 
			pcd->UpdateStatus();
			return tmp;

		default:
			return CallWindowProc(pcd->OldAVSViewWinProc, hwnd, msg,
				wParam, lParam); 
	}

	return 0;
}

// <----- Toff

void AVSEditor::Init() {
	hwndStatus = CreateStatusWindow(WS_CHILD|WS_VISIBLE, "", hwnd, 500);

	hwndView = CreateWindowEx(
		WS_EX_CLIENTEDGE,
//		szAVSViewerClassName,
//		RICHEDIT_CLASS,
		"Scintilla",
		"",
		WS_VISIBLE|WS_CHILD|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE,
		0,0,50,50,
		hwnd,
		NULL,
		g_hInst,
		NULL);

	lpszFileName[0] = 0;

	fnScintilla = (int (__cdecl *)(void *,int,int,int))SendMessage(hwndView,SCI_GETDIRECTFUNCTION,0,0);
	ptrScintilla = (void *)SendMessage(hwndView,SCI_GETDIRECTPOINTER,0,0);

	bLineNumbers = TRUE;
	UpdateLineNumbers();	

//	SetAStyle(STYLE_DEFAULT, RGB(0,0,0), RGB(0xff,0xff,0xff), 11, "Courier New");
//	SendMessageSci(SCI_STYLECLEARALL);	// Copies global style to all others
	SetScriptType(SCRIPTTYPE_NONE);

	VDM_FILENAME *fn = new VDM_FILENAME;
	fn->name = new char[MAX_PATH];
	PostMessage(hwndRef, WM_VDM_REQUESTFILENAME, (WPARAM) hwnd, (LPARAM) fn);

	// Toff ----->
	// Subclass richedit
	OldAVSViewWinProc = (WNDPROC)SetWindowLong(hwndView, GWL_WNDPROC,
		(DWORD)SubAVSEditorWndProc);	
	// <----- Toff

	//if (lpszFileName[0] != 0) Open();
}

void AVSEditor::UpdateLineNumbers() {
	SendMessageSci(SCI_SETMARGINTYPEN, 1, SC_MARGIN_NUMBER);
	char *gw = "9";
	int pixelWidth = 4 + 5 * SendMessageSci(SCI_TEXTWIDTH, STYLE_LINENUMBER, (int) gw);
	SendMessageSci(SCI_SETMARGINWIDTHN, 1, bLineNumbers?pixelWidth:0);
}

void AVSEditor::Open() {
 	char *lpszBuf;
	FILE *f;
	int n, r;

	lpszBuf = NULL;
	f = fopen(lpszFileName, "rb");
	fseek(f, 0, SEEK_END);
	r = ftell(f);
	fseek(f, 0, SEEK_SET);
	lpszBuf = (char *) malloc(r+1);
	n=fread(lpszBuf, sizeof( char ), r, f);
	*(lpszBuf+n) = (char) 0;
	fclose(f);
//    SendMessage(hwndView, WM_SETTEXT, 0, (LPARAM) lpszBuf);
	SendMessageSci(SCI_CLEARALL, 0, 0);
    SendMessageSci(SCI_SETTEXT, 0, (LPARAM) lpszBuf);
	free(lpszBuf);

	char buf[512];

	wsprintf(buf, "VirtualDubMod Script Editor - [%s]", lpszFileName);
	SetWindowText(hwnd, buf);

	// for Japanese
    // SetAStyle(STYLE_DEFAULT, RGB(0,0,0), RGB(0xff,0xff,0xff), 11, "Courier New");
	SetAStyle(STYLE_DEFAULT, RGB(0,0,0), RGB(0xff,0xff,0xff), 11, "‚l‚r ƒSƒVƒbƒN");
	SendMessageSci(SCI_STYLECLEARALL);	// Copies global style to all others

	if (!_stricmp(strrchr((lpszFileName), (int) '.'), ".avs")) {
		SetScriptType(SCRIPTTYPE_AVS);
	} else {
		SetScriptType(SCRIPTTYPE_NONE);
	}

}

void AVSEditor::SetScriptType(int type){
	scriptType = type;
	// for Japanese
    // SetAStyle(STYLE_DEFAULT, RGB(0,0,0), RGB(0xff,0xff,0xff), 11, "Courier New");
	SetAStyle(STYLE_DEFAULT, RGB(0,0,0), RGB(0xff,0xff,0xff), 11, "‚l‚r ƒSƒVƒbƒN");
	SendMessageSci(SCI_STYLECLEARALL);	// Copies global style to all others
	switch(type) {
		case SCRIPTTYPE_AVS: {
			if (!g_hAVSLexer) {
				MessageBox(hwnd, "AviSynthLexer.lexer is missing.\rNo syntax coloring available.", "Script Editor Error", MB_OK|MB_ICONERROR);
			}
			SendMessageSci(SCI_SETLEXERLANGUAGE, 0, (int) "avslex");
			SendMessageSci(SCI_SETSTYLEBITS, 7);

			SendMessageSci(SCI_SETKEYWORDS, 0, (int) avsBasicWords);
			SendMessageSci(SCI_SETKEYWORDS, 1, (int) avsInternalWords);
			//	SendMessageSci(SCI_SETKEYWORDS, 2, (int) avsInternalWords);

			const COLORREF black = RGB(0, 0, 0);
			const COLORREF white = RGB(0xFF, 0xFF, 0xFF);
			const COLORREF red = RGB(0xFF, 0, 0);
			const COLORREF offWhite = RGB(0xFF, 0xFB, 0xF0);
			const COLORREF darkGreen = RGB(0, 0x80, 0);
			const COLORREF darkBlue = RGB(0, 0, 0x80);
			const COLORREF darkRed = RGB(0x80,0,0);
	
			// for Japanese
			// SetAStyle(SCE_AVS_DEFAULT, black, white, 11, "Courier New");
			SetAStyle(SCE_AVS_DEFAULT, RGB(0,0,0), RGB(0xff,0xff,0xff), 11, "‚l‚r ƒSƒVƒbƒN");
		
			SetAStyle(SCE_AVS_NUMBER, darkBlue);

			// Commands
			SetAStyle(SCE_AVS_BASIC, black);
			SendMessageSci(SCI_STYLESETBOLD, SCE_AVS_BASIC, 1);
			SetAStyle(SCE_AVS_INTERNAL, darkGreen);
			SendMessageSci(SCI_STYLESETBOLD, SCE_AVS_INTERNAL, 1);

			// Comment
			SetAStyle(SCE_AVS_COMMENT, RGB(0,0,0));
			SendMessageSci(SCI_STYLESETITALIC, SCE_AVS_COMMENT, 1);

			SetAStyle(SCE_AVS_OPERATOR, darkBlue);
			SetAStyle(SCE_AVS_STRING, darkRed);
			SetAStyle(SCE_AVS_FUZZY, red);
			SetAStyle(SCE_AVS_ERROR, white, red);

			SetAStyle(34, white, black);  // Brace Highlight
			SetAStyle(35, white, red);    // Brace Badlight

			SendMessageSci(SCI_AUTOCSETIGNORECASE, TRUE);
			SendMessageSci(SCI_AUTOCSETFILLUPS, 0, (int) " (,.");
		} 
		break;

		default: {
			scriptType = SCRIPTTYPE_NONE;
			SendMessageSci(SCI_SETLEXER, SCLEX_NULL);
		}
	}
	UpdateStatus();
}

bool AVSEditor::Commit() {
    int s;
	char *lpszBuf;
	FILE *f;
	//GETTEXTLENGTHEX gte;
//	GETTEXTEX gt;

	if (lpszFileName[0] == 0) {
		char szName[MAX_PATH];
		OPENFILENAME ofn;

		szName[0] = 0;

		memset(&ofn, 0, sizeof ofn);

		ofn.lStructSize			= 0x4c;	//sizeof(OPENFILENAME); stupid beta include files
		ofn.hwndOwner			= hwnd;
		ofn.lpstrFilter			= "All files (*.*)\0*.*\0";
		ofn.lpstrCustomFilter	= NULL;
		ofn.nFilterIndex		= 1;
		ofn.lpstrFile			= szName;
		ofn.nMaxFile			= sizeof szName;
		ofn.lpstrFileTitle		= NULL;
		ofn.lpstrInitialDir		= NULL;
		ofn.lpstrTitle			= NULL;
		ofn.Flags				= OFN_EXPLORER | OFN_ENABLESIZING;
		ofn.lpstrDefExt			= NULL;

		if (GetSaveFileName(&ofn)) {
			strcpy(lpszFileName, szName);
		} else {
			return false;
		}

	}

//	s = SendMessage(hwndView, WM_GETTEXTLENGTH, 0, 0);
	s = SendMessageSci(SCI_GETTEXTLENGTH, 0, 0);
	lpszBuf = (char *) malloc(s);
//	gt.cb = s;
//	gt.flags = GT_USECRLF;
//	gt.codepage = CP_ACP;
//	gt.lpDefaultChar = NULL;
//	gt.lpUsedDefChar = NULL;
//	SendMessage(hwndView, EM_GETTEXTEX, (WPARAM) &gt, (LPARAM) lpszBuf);
	SendMessageSci(SCI_GETTEXT, s+1, (LPARAM) lpszBuf);
	f = fopen(lpszFileName, "wb");
	fwrite(lpszBuf, sizeof( char ), s, f);
	fclose(f);
	free(lpszBuf);
	return true;
}

void AVSEditor::SetStatus(const char *format, ...) throw() {
	char buf[1024];
	va_list val;

	va_start(val,format);
	_vsnprintf(buf, sizeof buf, format, val);
	va_end(val);

	SetWindowText(hwndStatus, buf);
}

void AVSEditor::UpdateStatus() throw() {
	char buf[1024];
	int posx, posy, c;
//	POINT pt;

	//SendMessage(hwndView, EM_GETSEL, (WPARAM) &posx, (LPARAM) &posy);
//	GetCaretPos(&pt);
//	c = SendMessage(hwndView, EM_CHARFROMPOS, 0, (LPARAM) &pt);
//	posy = SendMessage(hwndView, EM_LINEFROMCHAR, c, 0);
//	posx = c - SendMessage(hwndView, EM_LINEINDEX, posy, 0);
	c = SendMessageSci(SCI_GETCURRENTPOS, 0, 0);
	posy = SendMessageSci(SCI_LINEFROMPOSITION, c, 0);
	posx = c - SendMessageSci(SCI_POSITIONFROMLINE, posy, 0);
	wsprintf(buf, "%d:%d", posy+1, posx+1);
	SendMessage(hwndStatus, SB_SETTEXT, 2, (LPARAM) &buf);
	SendMessage(hwndStatus, SB_SETTEXT, 1, (LPARAM) scripttypeName[scriptType]);
}

void AVSEditor::SetAStyle(int style, COLORREF fore, COLORREF back, int size, const char *face) {
//This function is "lent" from the c++ sample for scintilla
	SendMessageSci(SCI_STYLESETFORE, style, fore);
	SendMessageSci(SCI_STYLESETBACK, style, back);
	if (size >= 1)
		SendMessageSci(SCI_STYLESETSIZE, style, size);
	if (face) 
		SendMessageSci(SCI_STYLESETFONT, style, reinterpret_cast<LPARAM>(face));
}


LRESULT AVSEditor::Handle_WM_COMMAND(WPARAM wParam, LPARAM lParam) throw() {
	if ((HWND) lParam == hwndView) {
		switch (HIWORD(wParam)) {
/*		case SCEN_CHANGE:
			CheckBracing();
			break;
*/		}
	} else switch(LOWORD(wParam)) {
	case ID_FILE_EXIT:
		DestroyWindow(hwnd);
		break;
	case ID_FILE_OPEN:
		{
			char szName[MAX_PATH];
			OPENFILENAME ofn;

			szName[0] = 0;

			memset(&ofn, 0, sizeof ofn);

			ofn.lStructSize			= 0x4c;	//sizeof(OPENFILENAME); stupid beta include files
			ofn.hwndOwner			= hwnd;
			ofn.lpstrFilter			= "All files (*.*)\0*.*\0";
			ofn.lpstrCustomFilter	= NULL;
			ofn.nFilterIndex		= 1;
			ofn.lpstrFile			= szName;
			ofn.nMaxFile			= sizeof szName;
			ofn.lpstrFileTitle		= NULL;
			ofn.lpstrInitialDir		= NULL;
			ofn.lpstrTitle			= NULL;
			ofn.Flags				= OFN_EXPLORER | OFN_ENABLESIZING;
			ofn.lpstrDefExt			= NULL;

			if (GetOpenFileName(&ofn)) {
				strcpy(lpszFileName, szName);
				Open();
			}
		}
		break;
	case ID_FILE_NEW:
		{
			lpszFileName[0] = 0;
			SendMessageSci(SCI_CLEARALL, 0, 0);
			SetScriptType(SCRIPTTYPE_NONE);
			SetWindowText(hwnd, "VirtualDubMod Script Editor");
		}
		break;
	case ID_REFRESH:
		{
			if (Commit())
				PostMessage(hwndRef, WM_COMMAND, ID_REFRESH, 0);
		}
		break;
	case ID_AVS_SAVE_OPEN:
		{
			if (Commit()) {
				VDM_FILENAME *fn = new VDM_FILENAME;
				fn->name = new char[strlen(lpszFileName)];
				strcpy(fn->name, lpszFileName);
				PostMessage(hwndRef, WM_VDM_SENDFILENAME, 0, (LPARAM) fn);
			}
		}
		break;
	case ID_AVS_FILE_SAVE:
		Commit();
		break;
	case ID_FILE_REVERT:
		if (IDOK==MessageBox(hwnd, "Discard all changes?", g_szAVSWarning, MB_OKCANCEL)) {
			Open();
		}
		break;

	case ID_EDIT_UNDO:
		{
			SendMessageSci(SCI_UNDO, 0, 0);
		}
		break;

	case ID_EDIT_REDO:
		{
			SendMessageSci(SCI_REDO, 0, 0);
		}
		break;

	case ID_AVS_INSERT_POS:
		{
			VDM_POS *pos = new VDM_POS;
			PostMessage(hwndRef, WM_VDM_REQUESTPOS, (WPARAM) hwnd, (LPARAM) pos);
		}
		break;

	case ID_AVS_INSERT_RANGE:
		{
			VDM_RANGE *range = new VDM_RANGE;
			range->tag = FALSE;
			PostMessage(hwndRef, WM_VDM_REQUESTRANGE, (WPARAM) hwnd, (LPARAM) range);
		}
		break;

	case ID_AVS_INSERT_TRIM:
		{
			VDM_RANGE *range = new VDM_RANGE;
			range->tag = TRUE;
			PostMessage(hwndRef, WM_VDM_REQUESTRANGE, (WPARAM) hwnd, (LPARAM) range);
		}
		break;

	case ID_AVS_INSERT_FRAMESET:
		{
			VDM_FRAMESET *fs = new VDM_FRAMESET;
			PostMessage(hwndRef, WM_VDM_REQUESTFRAMESET, (WPARAM) hwnd, (LPARAM) fs);
		}
		break;

	case ID_AVS_EDIT_LINE:
		{
			bLineNumbers = !bLineNumbers;
			UpdateLineNumbers();
			CheckMenuItem(GetMenu(hwnd), ID_AVS_EDIT_LINE, MF_BYCOMMAND | bLineNumbers?MF_CHECKED:MF_UNCHECKED);
		}
		break;

	case ID_AVS_SCRIPT_NONE:
		{
			SetScriptType(SCRIPTTYPE_NONE);
		}
		break;

	case ID_AVS_SCRIPT_AVS:
		{
			SetScriptType(SCRIPTTYPE_AVS);
		}
		break;

	case ID_AVS_COMMENT_LINES:
		{
			int first = SendMessageSci(SCI_GETSELECTIONSTART);
			int firstline = SendMessageSci(SCI_LINEFROMPOSITION, first);
			int last = SendMessageSci(SCI_GETSELECTIONEND);
			int lastline = SendMessageSci(SCI_LINEFROMPOSITION, last);
			for(int i = (first>last)?lastline:firstline;i <= ((first>last)?firstline:lastline);i++){
				CommentUncommentLine(i, TRUE);
			}
		}
		break;

	case ID_AVS_UNCOMMENT_LINES:
		{
			int first = SendMessageSci(SCI_GETSELECTIONSTART);
			int firstline = SendMessageSci(SCI_LINEFROMPOSITION, first);
			int last = SendMessageSci(SCI_GETSELECTIONEND);
			int lastline = SendMessageSci(SCI_LINEFROMPOSITION, last);
			for(int i = (first>last)?lastline:firstline;i <= ((first>last)?firstline:lastline);i++){
				CommentUncommentLine(i, FALSE);
			}
		}
		break;

	case ID_AVS_STARTCOMPLETE:
		if (scriptType==SCRIPTTYPE_AVS) {
			if (SendMessageSci(SCI_AUTOCACTIVE)){
				SendMessageSci(SCI_AUTOCCANCEL);
			} else {
				int pos = SendMessageSci(SCI_GETCURRENTPOS);
				int line = SendMessageSci(SCI_LINEFROMPOSITION, pos);
				int posinline = pos - SendMessageSci(SCI_POSITIONFROMLINE, line);
				char *bline = new char[SendMessageSci(SCI_LINELENGTH, line)];
				SendMessageSci(SCI_GETLINE, line, (int) bline);
				*(bline + posinline) = 0;
				strrev(bline);
				char *found = strpbrk(bline, " .(,");
				int thelen;
				if (found) {
					thelen = found - bline;
				} else {
					thelen = strlen(bline);
				}
				SendMessageSci(SCI_AUTOCSHOW, thelen, (int) avsWords);
			}
		}
		break;

/*	case ID_HELP_WHY:
		MessageBox(hwnd,
			"I need a quick way for people to send me parts of files that don't load properly "
			"in VirtualDub, and this is a handy way to do it. Well, that, and it's annoying to "
			"check 3GB AVI files if your hex editor tries to load the file into memory.",
			"Why is there a hex editor in VirtualDub?",
			MB_OK);
		break;*/

	case ID_HELP_KEYS:
		MessageBox(hwnd,
			"Ctrl-N: New File\n"
			"Ctrl-O: Open File\n"
			"F2, Ctrl-S: Save File\n"
			"F5: Save File & Refresh\n"
			"F7: Save File & Loads it as movie into the main window\n"
			"Ctrl-P: Inserts current position in the main window\n"
			"Ctrl-R: Inserts current range in the main window (from,to)\n"
			"Ctrl-T: Inserts Trim  to the current range in the main window\n"
			"Ctrl-Fx (x = 1-12): [Toff]s Tweaking Mod (see special help)",
			"Keyboard commands",
			MB_OK);
		break;

	case ID_HELP_TOFF:
		MessageBox(hwnd,
			"In your avs code you can add #StartTweaking and #StopTweaking line to delimitate the line you want to work on, like this:\n\n"
			"  mpeg2source(\"darkcity.d2v\",0)\n\n"
			"  #StartTweaking\n"
			"  #Convolution3d (preset=\"movieLQ\")\n"
			"  Convolution3d (preset=\"movieHQ\")\n"
			"  #StopTweaking\n\n"
			"  #StartTweaking\n"
			"  #SimpleResize(512,384)\n"
			"  BicubicResize(512,384)\n"
			"  #BilinearResize(512,384)\n"
			"  #LanczosResize(512,384)\n"
			"  #StopTweaking\n\n"
			"Then you have to place the text cursor between the 2 marker lines and"
			"press Ctrl-F1 to uncomment the first line and comment all other lines, Ctrl-F2 to uncomment the second line and comment all other lines...\n"
			"This way you can quickly see differences.",
			"[Toff]s Tweaking Mod",
			MB_OK);
		break;

	}

	return 0;
}

LRESULT AVSEditor::Handle_WM_SIZE(WPARAM wParam, LPARAM lParam) throw() {
	HDWP hdwp;
	RECT r, rstatus;

	GetClientRect(hwnd, &r);
	GetWindowRect(hwndStatus, &rstatus);
	MapWindowPoints(NULL, hwnd, (LPPOINT)&rstatus, 2);

	hdwp = BeginDeferWindowPos(2);
	DeferWindowPos(hdwp, hwndStatus, NULL, 0, r.bottom-rstatus.bottom, r.right, rstatus.bottom-rstatus.top, SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOCOPYBITS);
	DeferWindowPos(hdwp, hwndView, NULL, 0, 0, r.right, r.bottom - (rstatus.bottom-rstatus.top), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE);
	EndDeferWindowPos(hdwp);

	int st[3];
	st[0] = r.right-150;
	st[1] = r.right-75;
	st[2] = -1;
	SendMessage(hwndStatus, SB_SETPARTS, 3, (LPARAM) &st);
	UpdateStatus();

	return 0;
}

LRESULT AVSEditor::Handle_WM_NOTIFY(HWND hwndFrom, UINT code) throw() {
	if (hwndFrom == hwndView) {
		switch(code){
		case SCN_UPDATEUI:
			CheckBracing();
			break;
/*		case SCN_CHARADDED:
			DoCalltip();
			break;*/
/*		case SCN_MODIFIED:
			CheckBracing();
			break;*/
		}
	}
	return 0;
}

void AVSEditor::CheckBracing() {
	int pos = SendMessageSci(SCI_GETCURRENTPOS);
	if(pos>0){
	 	SendMessageSci(SCI_BRACEHIGHLIGHT, INVALID_POSITION, INVALID_POSITION);
		if ((char) SendMessageSci(SCI_GETCHARAT, pos-1)==')') {
			int pos2 = SendMessageSci(SCI_BRACEMATCH, pos-1);
			if (pos2==INVALID_POSITION){
				SendMessageSci(SCI_BRACEBADLIGHT, pos-1);
			} else {
				SendMessageSci(SCI_BRACEHIGHLIGHT, pos2, pos-1);
			}
		}
	}
}

void AVSEditor::DoCalltip() {
	int pos = SendMessageSci(SCI_GETCURRENTPOS);
	if(pos>0){
		char ch = SendMessageSci(SCI_GETCHARAT, pos-1);
		if (ch=='(') {
			if (!SendMessageSci(SCI_CALLTIPACTIVE)){
				SendMessageSci(SCI_CALLTIPSHOW, 5, (int) "Test(a, b)\rTest2");
			}
		}
		if (ch==')') {
			if (SendMessageSci(SCI_CALLTIPACTIVE)){
				SendMessageSci(SCI_CALLTIPCANCEL);
			}
		}
	}
}

////////////////////////////

LRESULT APIENTRY AVSEditor::AVSEditorWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) throw() {
	AVSEditor *pcd = (AVSEditor *)GetWindowLong(hwnd, 0);

	switch(msg) {

	case WM_NCCREATE:
		if (!(pcd = new AVSEditor(hwnd)))
			return FALSE;

		SetWindowLong(hwnd, 0, (LONG)pcd);
		return DefWindowProc(hwnd, msg, wParam, lParam);

	case WM_CREATE:
		CREATESTRUCT *cs;
		cs = (CREATESTRUCT *) lParam;
		pcd->hwndRef = (HWND) cs->lpCreateParams;
		pcd->Init();
//		return 0;		// intentional fall through to WM_SIZE

	case WM_SIZE:
		return pcd->Handle_WM_SIZE(wParam, lParam);

	case WM_DESTROY:
		delete pcd;
		SetWindowLong(hwnd, 0, 0);
		break;

	case WM_SETFOCUS:
		SetFocus(pcd->hwndView);
		return 0;

	case WM_COMMAND:
		return pcd->Handle_WM_COMMAND(wParam, lParam);

	case WM_CLOSE:
		DestroyWindow(hwnd);
		return 0;

	case WM_INITMENU:
		{
			HMENU hMenu = (HMENU)wParam;
			DWORD dwEnableFlags;

			dwEnableFlags = (SendMessage(pcd->hwndView, SCI_CANUNDO, 0, 0) ? (MF_BYCOMMAND|MF_ENABLED) : (MF_BYCOMMAND|MF_GRAYED));
			EnableMenuItem(hMenu,ID_EDIT_UNDO, dwEnableFlags);
			
			dwEnableFlags = (SendMessage(pcd->hwndView, SCI_CANREDO, 0, 0) ? (MF_BYCOMMAND|MF_ENABLED) : (MF_BYCOMMAND|MF_GRAYED));
			EnableMenuItem(hMenu,ID_EDIT_REDO, dwEnableFlags);

			CheckMenuRadioItem(hMenu, ID_AVS_SCRIPT_NONE, ID_AVS_SCRIPT_AVS, ID_AVS_SCRIPT_NONE+pcd->scriptType, MF_BYCOMMAND);

		/*	EnableMenuItem(hMenu,ID_FILE_SAVE, dwEnableFlags);
			EnableMenuItem(hMenu,ID_FILE_REVERT, dwEnableFlags);
			EnableMenuItem(hMenu,ID_EDIT_TRUNCATE, dwEnableFlags);

			dwEnableFlags = (pcd->hFile != INVALID_HANDLE_VALUE ? (MF_BYCOMMAND|MF_ENABLED) : (MF_BYCOMMAND|MF_GRAYED));
			*/
		}
		return 0;

	case WM_NOTIFY:
		{
			NMHDR *phdr = (NMHDR *) lParam;
			return pcd->Handle_WM_NOTIFY(phdr->hwndFrom, phdr->code);
//			if (phdr->code == WN_KEYDOWN) {
//				MessageBox(pcd->hwnd, "Test", "TEST", MB_OK);
//			}
		}

	case WM_VDM_SENDFILENAME:
		{
			VDM_FILENAME *fn = (PVDM_FILENAME) lParam;
			if (*fn->name != 0) {
				if (IsAVSScript(fn->name)) {
					strcpy(pcd->lpszFileName, fn->name);
					pcd->Open();
				}
			}
			delete [] fn->name;
			delete [] fn;
		}
		return 0;

	case WM_VDM_SENDPOS:
		{
			char buf[50];
			VDM_POS *pos = (PVDM_POS) lParam;
			//SETTEXTEX st;
			//st.flags = ST_SELECTION;
			//st.codepage = CP_ACP;
			wsprintf(buf, "%d", pos->pos);
			SendMessage(pcd->hwndView, SCI_REPLACESEL, 0, (LPARAM) &buf);
			delete [] pos;
		}
		return 0;

	case WM_VDM_SENDRANGE:
		{
			char buf[50];
			VDM_RANGE *range = (PVDM_RANGE) lParam;
			//SETTEXTEX st;
			//st.flags = ST_SELECTION;
			//st.codepage = CP_ACP;
			wsprintf(buf, (range->tag)?"Trim(%d,%d)":"%d,%d", range->range.from, range->range.to);
			SendMessage(pcd->hwndView, SCI_REPLACESEL, 0, (LPARAM) &buf);
			delete [] range;
		}
		return 0;

	case WM_VDM_SENDFRAMESET:
		{
			string buffer;
			char buf[50];
			VDM_FRAMESET *fs = (PVDM_FRAMESET) lParam;
			//SETTEXTEX st;
			//st.flags = ST_SELECTION;
			//st.codepage = CP_ACP;
			PVDM_BASIC_RANGE range;
			for(int i = 0; i<fs->count; i++) {
				range = (fs->ranges + (i*sizeof(VDM_BASIC_RANGE)));
				wsprintf(buf, "Trim(%d,%d)", range->from, range->to);
				if (i>0) buffer += " ++ ";
				buffer += buf;
			}
			SendMessage(pcd->hwndView, SCI_REPLACESEL, 0, (LPARAM) buffer.c_str());
			if (fs->count==0) MessageBox(hwnd, "No Frameset defined in VirtualDubMod", "VirtualDubMod Error", MB_OK|MB_ICONERROR);
			delete [] fs->ranges;
			delete [] fs;
		}
		return 0;

	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
	return 0;
}

///////////////////////////////////////////////////////////////////////////

bool IsAVSScript(char *fn) {return !_stricmp(strrchr(fn, (int) '.'), ".avs");}

void InitAVSEditor() {
	char *temp = new char[sizeof(avsInternalWords)];
	avsWords = new char[sizeof(avsInternalWords)+sizeof(avsBasicWords)+2];
	char *token;
	string *c;

	strcpy(temp, avsInternalWords);
	token = strtok(temp, " ");
	while (token != NULL) {
		c = new string;
		*c = token;
		AVSToken.insert(*c);
		token = strtok(NULL, " ");
	}

	strcpy(temp, avsBasicWords);
	token = strtok(temp, " ");
	while (token != NULL) {
		c = new string;
		*c = token;
		AVSToken.insert(*c);
		token = strtok(NULL, " ");
	}

	set<string,less_nocase>::iterator walkit;
	walkit = AVSToken.begin();
	strcpy(avsWords, walkit->c_str());
	for(walkit++; walkit!=AVSToken.end();walkit++) {
		strcat(avsWords, " ");
		strcat(avsWords, walkit->c_str());
	}

	delete [] temp;
}

void DeinitAVSEditor() {
	delete [] avsWords;
}


ATOM RegisterAVSEditor() {
	WNDCLASS wc1;

	wc1.style			= 0;
	wc1.lpfnWndProc		= AVSEditor::AVSEditorWndProc;
	wc1.cbClsExtra		= 0;
	wc1.cbWndExtra		= sizeof(AVSEditor *);
	wc1.hInstance		= g_hInst;
	wc1.hIcon			= LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_AVSEDIT));
    wc1.hCursor			= LoadCursor(NULL, IDC_ARROW);
	wc1.hbrBackground	= NULL; //(HBRUSH)(COLOR_WINDOW+1);
	wc1.lpszMenuName	= MAKEINTRESOURCE(IDR_AVSVIEWER_MENU);
	wc1.lpszClassName	= AVSEDITORCLASS;

	return RegisterClass(&wc1) /*&& LoadLibrary("Riched20.dll")*/;

}
HWND AVSEdit(HWND hwndParent, HWND refWND) {
	return CreateWindow(
		AVSEDITORCLASS,
		"VirtualDubMod Script Editor",
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		hwndParent,
		NULL,
		g_hInst,
		refWND);
}
