' Simple IDE for wxBasic
' (c) 2004 David Cuny
'
' This file also requires IDE_TIPS.TXT, which contains the prototypes
'   for wxWidgets routines
'
' TO DO HINTS:
'
' - inheritance: add if it's not in the list yet
' - sort the list: bubble sort of some kind
' - select from list: copy text to the screen
' - autocomplete: for routines and datatypes
' - methods of classes: for example, "myWindow.Show("
'
' TO DO:
' Check arg names to allow dropping files onto executable
' Add 'Cancel' to dialog options (on exit, etc.)


Option Explicit

Dim     AppName         = "wxBasic Editor",     ' application name
        ID_RUN          = wxID_HIGHEST + 1,     ' custom event
        ID_FIND         = wxID_HIGHEST + 2,     ' search/find
        ID_REPEAT       = wxID_HIGHEST + 3,     ' search/repeat
        ID_REPLACE      = wxID_HIGHEST + 4,     ' search/replace
        wxID_ED         = wxID_HIGHEST + 5,     ' editor event
        fileName        = ""                    ' current file name

' Main window
Dim frame as wxFrame = new wxFrame( Nothing, -1, AppName & " - Untitled Document" )

' Search controls
Dim findDialog = -1
Dim findData as wxFindReplaceData = new wxFindReplaceData( wxFR_REPLACEDIALOG )


' Text edit control
    Dim ed as wxStyledTextCtrl = new wxStyledTextCtrl(frame, wxID_ED)

' Default font
  '  Dim font = new wxFont(10, wxMODERN, wxNORMAL, wxNORMAL)
	dim font as wxFont
	if instr( lcase( wxGetOsDescription()), "linux" ) then
		' linux
		font = new wxFont(14, wxDEFAULT, wxNORMAL, wxNORMAL)
	else
		' windows
		font = new wxFont(10, wxMODERN, wxNORMAL, wxNORMAL)
	end if
    ed.StyleSetFont(wxSTC_STYLE_DEFAULT, font)
    ed.StyleClearAll()

' Color scheme
    'ed.StyleSetForeground(0,  wxColour(0x80, 0x80, 0x80))
	ed.StyleSetForeground(0,  wxColour(0x00, 0x00, 0x00))

	' comments are green
    ed.StyleSetForeground(1,  wxColour(0x00, 0x7f, 0x00))
	ed.StyleSetItalic(1,  True)

    ' numbers
	ed.StyleSetForeground(2,  wxColour(0x7f, 0x00, 0x00))

	' keywords are blue
    ed.StyleSetForeground(3,  wxColour(0x00, 0x00, 0x7f))
	ed.StyleSetBold(3,  True)

	' quotes are cyan
    ed.StyleSetForeground(4,  wxColour(0x00, 0x7f, 0x7f))

	' preprocessor
    ed.StyleSetForeground(5,  wxColour(0x00, 0x00, 0x7f))

	' operator
    ed.StyleSetForeground(6,  wxColour(0x7f, 0x00, 0x7f))

	' identifier
    'ed.StyleSetForeground(7,  wxColour(0x7f, 0x00, 0x7f))

	' date
    'ed.StyleSetForeground(8,  wxColour(0x00, 0x7f, 0x7f))


    ' Font
'    ed.StyleSetSpec(2, "fore:#007f00,bold,face:Arial,size:9")
'    ed.StyleSetSpec(2, "fore:#007f00,bold,face:Helvetica,size:9")

    ' Tab Size
    ed.SetTabWidth(4)

	' use line numbers
	ed.setMarginWidth( 1, 36 )
	ed.setMarginType( 1, wxSTC_STYLE_LINENUMBER )

ed.SetLexer(wxSTC_LEX_VB)
ed.SetKeyWords(0,
    "and append as case close const continue common declare delete dim " &
    "do each elsif else elseif end erase exit explict for function global if " &
    "in include input inv is line mod new next noconsole not open option or " &
    "output print qbasic redim return select shared shl shr static step sub then " &
    "to until wend while xor" &
    "abs acos andbits argc argv asc asin atan chdir chr chr$ command " &
    "command$ connect convbase convbase$ cos date date$ hasdir hasdir$ " &
    "direxists fix floor fopen format format$ fputs frac freefile " &
    "getwxapp hex hex$ indexes insert insert$ instr int kill lcase lcase$ " &
    "left left$ len length loc lof log lbound ltrim ltrim$ mainloop mid " &
    "mid$ mkdir notbits orbits quicksort randomize readbyte rename replace " &
    "replace$ reverse reverse$ right right$ rinstr rmdir rnd round rtrim " &
    "rtrim$ run seek sgn shell sin space space$ sqr sqrt str str$ strf strf$ " &
    "string string$ tally tan ticks time time$ typeof typeof$ ubound ucase " &
    "ucase$ val writebyte xorbits" )

' Fairly useless status bar
    Dim status = frame.CreateStatusBar( 1 )
    frame.SetStatusText("Ready")

' Dialog used for Open/Save/Save As
    Dim fileDialog = new wxFileDialog( frame )

' add menubar to the frame
    Dim mBar = new wxMenuBar()
    frame.SetMenuBar(mBar)

' build the "File" dropdown menu
    Dim mFile = new wxMenu()
    mBar.Append(mFile, "&File")

' populate it
    mFile.Append( wxID_NEW, "&New", "Creates a new file" )
    mFile.Append( wxID_OPEN, "&Open...", "Loads an existing file from disk" )
    mFile.Append( wxID_SAVE, "&Save", "Saves current file" )
    mFile.Append( wxID_SAVEAS, "Save &As...", "Saves current file with new name" )
    mFile.AppendSeparator()
    mFile.Append( wxID_EXIT, "E&xit\tAlt-X", "Exit Application" )

Sub saveFile()
    ' This should probably write line by line...
    dim file = fOpen( fileName, "w" )
    fPuts( file, ed.GetText() )
    fClose( file )
End Sub

' Common routine
Sub saveFileAs()
    dim result, theFile
    fileDialog.SetMessage("Save File As")
    fileDialog.SetFilename( fileName )
    fileDialog.SetStyle( wxSAVE )

	' Cancelled?
    If fileDialog.ShowModal() != wxID_OK Then
        Return
	End If

	' FIX! Use path
	theFile = fileDialog.GetFilename()

	' don't accidentally overwrite
	If fileExists( theFile ) then
		result = wxMessageBox( "File already exists. Overwrite?", 
			"Warning", wxOK | wxCANCEL | wxCENTER )
		If result != wxOK then			
            Return
		End If
	End If

	' Save the file
	fileName = theFile
	saveFile()
	frame.SetTitle( AppName & " - " & fileName )

End Sub

Sub saveChanges()
    If ed.GetModify() Then
        Dim dialog = new wxMessageDialog( frame, "Save changes first?",
            "Save Changes?", wxYES_NO + wxCENTRE + wxICON_QUESTION )
        Dim result = dialog.ShowModal()
        // FIXME!
        // delete dialog
        Dialog.Finalize()
    
        If result = wxID_YES Then
            If fileName = "" Then
                saveFileAs()
            Else
                saveFile()
            End If
        End If
    End If
End Sub

Sub onFileNew( event )
    saveChanges()
    ed.EmptyUndoBuffer()
    ed.SetText("")
    fileName = ""
    frame.SetTitle( AppName & " - Untitled Document" )
End Sub
Connect( frame, wxID_NEW, wxEVT_COMMAND_MENU_SELECTED, "onFileNew" )

Sub gotoError()

    ' kill the marker(s)
    ed.MarkerDeleteAll(1)

    ' check for errors
    if fileExists( "wx.err" ) then
        try
            ' read it
            dim errFile = fOpen( "wx.err", "r" )

            ' first line has error message
            dim errText = fGets( errFile )

            ' second line has location of error
            dim errMsg = fGets( errFile )
    
            ' extract the line number
            dim errLine = 0 + wordBetween( errMsg, ", line ", ":" )
    
            ' extract the file name
            dim errFileName = wordBetween( errMsg, "in file \"", "\"" )
    
            ' FIX! Handle long file names?
            if errFileName = fileName then
    
                ' set the status line
                frame.SetStatusText( errText )
            
                ' go to line
                ed.GotoLine( errLine-1 )
    
                ' mark it
                ed.MarkerDefine(1, wxSTC_MARK_SHORTARROW, wxColor( 0x00, 0x00, 0x00 ), wxColor( 0x80, 0x00, 0x00 ) )
                ed.MarkerSetForeground( 1, wxColor( 0x00, 0x00, 0x00 ) )
                ed.MarkerSetBackground( 1, wxColor( 0x80, 0x00, 0x00 ) )
                
                ed.MarkerAdd( errLine-1, 1 )
    
    
            end if
        catch
            ' zap the markers
            ed.MarkerDeleteAll(1)

            ' set the status text
            frame.SetStatusText( "Can't goto error line" )

        finally
            fClose( errFile )
        end try
        


    end if

End Sub

Sub loadFile( name )

    Dim buffer = ""

    ' is there a file?
    if name <> "" then
    
		' does it exist?
		if fileExists( name ) then
			' read the file
			dim handle = fOpen( name, "r" )
			while not eof( handle )
                // FIME: replaced &=
                buffer.append( fGets( handle ) & "\n" )
			end while
			fClose( handle )
		end if
    end if

    ' clear the current file
    ed.SetText("")

    ' insert into the buffer
    ed.InsertText(0, buffer)
    ed.EmptyUndoBuffer()

    ' set name
    fileName = name

    ' set the title
    if fileName <> "" then
        frame.SetTitle( AppName & " - " & fileName )
    else
        frame.SetTitle( AppName & " - Untitled Document" )
    end if

    ' goto error
    gotoError()

End Sub

Sub onFileOpen( event )
    saveChanges()
    fileDialog.SetMessage("Open File")
    fileDialog.SetStyle( wxOPEN )
    If fileDialog.ShowModal() = wxID_OK Then
        ' FIX! Problem with long file names...
        'fileName = fileDialog.GetPath()        
        loadFile( fileDialog.GetFilename() )
    End If
End Sub
Connect( frame, wxID_OPEN, wxEVT_COMMAND_MENU_SELECTED, "onFileOpen" )

Sub onFileSaveAs( event )
    saveFileAs()
End Sub
Connect( frame, wxID_SAVEAS, wxEVT_COMMAND_MENU_SELECTED, "onFileSaveAs" )

Sub onFileSave( event )
    if fileName = "" Then
        onFileSaveAs( event )
    Else
        saveFile()
    End If
End Sub
Connect( frame, wxID_SAVE, wxEVT_COMMAND_MENU_SELECTED, "onFileSave" )


Sub onFileExit( event )
    saveChanges()
    End
End Sub
Connect( frame, wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, "onFileExit" )
Connect( frame, wxEVT_CLOSE_WINDOW, "onFileExit" )

' build the "Edit" dropdown menu
Dim mEdit = new wxMenu()
mBar.Append(mEdit, "&Edit")
mEdit.Append( wxID_CUT, "Cu&t\tCtrl-X", "Deletes selected text and copies it to buffer" )
mEdit.Append( wxID_COPY, "&Copy\tCtrl-C", "Copies selected text to buffer" )
mEdit.Append( wxID_PASTE, "&Paste\tCtrl-V", "Inserts buffer contents at current location" )
mEdit.Append( wxID_CLEAR, "Cl&ear", "Deletes selected text without copying to buffer" )

Sub onEditCut( event )
    ed.Cut()
End Sub
Connect( frame, wxID_CUT, wxEVT_COMMAND_MENU_SELECTED, "onEditCut" )

Sub onEditCopy( event )
    ed.Copy()
End Sub
Connect( frame, wxID_COPY, wxEVT_COMMAND_MENU_SELECTED, "onEditCopy" )

Sub onEditPaste( event )
    ed.Paste()
End Sub
Connect( frame, wxID_PASTE, wxEVT_COMMAND_MENU_SELECTED, "onEditPaste" )


' build the "Search" dropdown menu
Dim mSearch = new wxMenu()
mBar.Append(mSearch, "&Search")
mSearch.Append( ID_FIND, "&Find\tCtrl+F", "Find the specified text" )
mSearch.Append( ID_REPEAT, "Repeat &Last Find\tF3", "Repeat the last find" )
mSearch.Append( ID_REPLACE, "&Replace\tCtrl+H", "Replaces specific text with different text" )

sub searchFind( first )

    dim startSearch, endSearch

    ' get settings
    dim flags = findData.GetFlags()
    dim findString = findData.GetFindString()

    ' direction
    startSearch = ed.GetCurrentPos()
    if andBits( flags, wxFR_DOWN ) then
	' down
        if not first then
            startSearch = startSearch + 1
        end if
        endSearch = ed.GetLength()
    else
    ' up
        if not first then
            startSearch = startSearch - len( findString ) + 1
        end if
        endSearch  = 0
    end if

    ' search
    dim foundAt = ed.FindText( startSearch, endSearch, findString, flags )		

    ' failed?
    if foundAt = -1 then
        ' should probably ask about starting again from the top...
        if findDialog <> -1 then
            findDialog.Show(False)
            findDialog.Finalize()
            findDialog = -1
        end if
    else
        ed.SetSelection(foundAt, foundAt+length(findString))
        ed.EnsureVisible( ed.LineFromPosition(foundAt))
    end if

End Sub


sub doFindReplace( eventType )

    dim startSearch, endSearch, findString, replaceString

    ' close the dialog
    if eventType = wxEVT_COMMAND_FIND_CLOSE then
        ' destroy the dialog
        findDialog.Finalize()
        findDialog = -1
        return
    end if

    ' get settings
    dim flags = findData.GetFlags()
    findString = findData.GetFindString()    

    ' direction
    startSearch = ed.GetCurrentPos()
    if andBits( flags, wxFR_DOWN ) then
	' down
        if eventType = wxEVT_COMMAND_FIND_NEXT then
            startSearch = startSearch + 1
        end if
        endSearch = ed.GetLength()
    else
    ' up
        if eventType = wxEVT_COMMAND_FIND_NEXT then
            startSearch = startSearch - (len( findString ) + 1)
        end if
        endSearch  = 0
    end if

    select case eventType
    case wxEVT_COMMAND_FIND, wxEVT_COMMAND_FIND_NEXT
        ' search
        dim foundAt = ed.FindText( startSearch, endSearch, findString, flags )
    
        ' failed?
        if foundAt = -1 then
            ' should probably ask about starting again from the top...
            if findDialog <> -1 then
                findDialog.Show(False)
                findDialog.Finalize()
                findDialog = -1
            end if
        else
            ed.SetSelection(foundAt, foundAt+length(findString))
            ed.EnsureVisible( ed.LineFromPosition(foundAt))
        end if

    Case wxEVT_COMMAND_FIND_REPLACE
        ' make sure correct text is selected
        if ucase(ed.GetSelectedText()) <> ucase(findData.GetFindString()) then
            return
        end if

        ' remember marker
        foundAt = ed.GetSelectionStart()

        replaceString = findData.GetReplaceString()
        ed.ReplaceSelection(replaceString)

        ' new selection
        ed.SetSelection(foundAt, foundAt+length(replaceString))

    Case wxEVT_COMMAND_FIND_REPLACE_ALL
        dim theCount = 1
        ' replace selection
        doFindReplace( wxEVT_COMMAND_FIND_REPLACE )

        ' find and replace remaining
        while True
            ' look for another to replace
            doFindReplace( wxEVT_COMMAND_FIND_NEXT )
            if findDialog = -1 then
                ' no more, exit loop
                exit while
            end if
            doFindReplace( wxEVT_COMMAND_FIND_REPLACE )
            theCount = theCount + 1
        end while
        ' status
        wxMessageBox( "Replaced " & theCount & " occurance(s)",
                        AppName, wxOK + wxICON_INFORMATION, frame )
    End Select

End Sub


Sub onRepeatFind( event )
    ' repeat find
    doFindReplace( wxEVT_COMMAND_FIND_NEXT )
End Sub
Connect( frame, ID_REPEAT, wxEVT_COMMAND_MENU_SELECTED, "onRepeatFind" )


Sub onFindReplaceDialog( event )
    doFindReplace( event.GetEventType() )
End Sub

Sub onSearchFind( event )

    ' does the dialog exist?
    if findDialog <> -1 then
        return
    end if

    ' create the dialog
    findDialog = new wxFindReplaceDialog( frame, findData, "Find" )

    ' set callbacks
    Connect( findDialog, wxEVT_COMMAND_FIND, "onFindReplaceDialog" )
    Connect( findDialog, wxEVT_COMMAND_FIND_NEXT, "onFindReplaceDialog" )
    Connect( findDialog, wxEVT_COMMAND_FIND_CLOSE, "onFindReplaceDialog" )

    ' display it
    findDialog.Show(True)

End Sub
Connect( frame, ID_FIND, wxEVT_COMMAND_MENU_SELECTED, "onSearchFind" )




Sub onSearchReplace( event )

    ' does the dialog exist?
    if findDialog <> -1 then
        return
    end if

    ' create the dialog
    findDialog = new wxFindReplaceDialog( frame, findData, "Replace", wxFR_REPLACEDIALOG )

    ' set callbacks
    Connect( findDialog, wxEVT_COMMAND_FIND, "onFindReplaceDialog" )
    Connect( findDialog, wxEVT_COMMAND_FIND_NEXT, "onFindReplaceDialog" )
    Connect( findDialog, wxEVT_COMMAND_FIND_REPLACE, "onFindReplaceDialog" )
    Connect( findDialog, wxEVT_COMMAND_FIND_REPLACE_ALL, "onFindReplaceDialog" )
    Connect( findDialog, wxEVT_COMMAND_FIND_CLOSE, "onFindReplaceDialog" )

    ' display it
    findDialog.Show(True)

End Sub
Connect( frame, ID_REPLACE, wxEVT_COMMAND_MENU_SELECTED, "onSearchReplace" )



' build the "Run" dropdown menu
Dim mRun = new wxMenu()
mBar.Append(mRun, "&Run")
mRun.Append( ID_RUN, "Run\tF5", "Run wxBasic program" )

Sub onRunRun( event )
    ' delete the old error file

	dim cmd

    if fileExists("wx.err") then
        Kill("wx.err")
    end if

    ' save the file
    saveChanges()

    ' run the file, if it has a name
    If Len( fileName ) Then
		' what's the operating system?
        if instr( wxGetOsDescription(), "Linux" ) then
			' for linux, use the . to get the version in the current directory
			cmd = "./wxbasic "
		else
            cmd = "wxbasic "
		end if

        wxExecute( cmd & fileName, -1 )
        ' goto error, if any
        gotoError()
    End If

End Sub
Connect( frame, ID_RUN, wxEVT_COMMAND_MENU_SELECTED, "onRunRun" )


' build the "Help" dropdown menu
Dim mHelp = new wxMenu()
mBar.Append(mHelp, "&Help")
mHelp.Append( wxID_HELP, "&About\tF1", "About this program" )

Sub onHelpAbout( event )
    Dim msg = "Simple Editor written in wxBasic.\n" &
            "(c) 2002 David Cuny"
    wxMessageBox( msg, "About wxBasic Editor", wxOK + wxICON_INFORMATION, frame )
End Sub
Connect( frame, wxID_HELP, wxEVT_COMMAND_MENU_SELECTED, "onHelpAbout" )


Function wordBetween( text, before, after )

    ' find position of word in front
    dim at = instr( text, before )
    if at = 0 then
        return ""
    end if

    ' get following portion
    at = at + length( before )
    dim result = Mid( text, at )

    ' find delimiter on the right
    at = instr( result, after )
    if at then
        result = mid( result, 1, at-1 )
    end if

    return result

End Function

sub setup()

	Dim name

    ' Passed a file name?
    if length( command(3) ) then
        ' use the file from the command line
        name = command(3)

    ' is there an error file?
    elseif fileExists("wx.err") then
        try
            ' use the file from the error message
            dim errFile = fOpen( "wx.err", "r" )
            dim errMsg = fGets( errFile )
            errMsg = fGets( errFile )

            fClose( errFile )
    
            ' extract the file name
            name = wordBetween( errMsg, "in file \"", "\"" )
        catch
            name = ""
        end try

    else
        name = ""

    end if

    ' load the file
    loadFile( name )

End Sub










sub bubbleSort( theList )
    dim i, j, tmp, inOrderFlag
	' maximum iterations through the list
    for i = length( theList )-1 to 1 step -1
		' assume the list is in order
        inOrderFlag = true
		' iterate through the list
        for j = 1 to i
			' need to swap positions?
            if (theList[j] > theList[j+1] ) then
				' flag that the list isn't in order
                inOrderFlag = false
				' swap values
                theList[j], theList[j+1] = theList[j+1], theList[j]
            end if
        end for
		' everything in place?
		if inOrderFlag then break
    end for
end sub


dim builtinRoutines = {
	"ABS":"Abs( n As Number) As Number\nReturns the absolute value of the number",
	"ACOS":"ACos( n As Number) As Number\nReturns the Arcosine of the number",
	"ARGV":"Argv( n As Number) As String\nReturns the nth argument from the command line",
	"ASC":"Asc( char As String ) As Integer\nReturns the ASCII value of the first character in the string",
	"ASIN":"ASin( n As Number ) As Number\nReturns the Arcsine of the number",
	"ARCTAN":"ArcTan( n As Number ) As Number\nReturns the Arctangent of the number",
	"CHDIR":"ChDir( directoryName As String )\nChanges the current working directory",
	"CHR":"Chr( n As Number ) As String\nReturns the ASCII character of the number",
	"COMMAND":"Command( cmd As String ) As Integer\nCalls the system shell with the command, returns result code",
	"CONCAT":"Concat( text1 As String, text2 As String ) As String\nConcatenates two strings together",
	"CONNECT":"Connect( object As Object, [id As Integer, ] eventType As Integer, routineName As String )\nAttaches routine as callback to object when event occurs",	
	"COS":"Cos( n As Number ) As Number\nReturns Cosine() of the number",
	"DATE":"Date( text As String ) As Date\nConverts text to a date",
	"DIR":"Dir()",
	"DIREXISTS":"DirExists( directoryName As String ) As Integer\nReturns true if directory exists",
	"DISCONNECT":"Disconnect( object As Object, [id As Integer, ] eventType As Integer )\nDisconnects event handlers from object",
	"EOF":"Eof( fileHandle As String ) As Boolean\nReturns true if at end of file",
	"EXP":"Exp( n As Number ) As Number",
	"FCLOSE":"FClose( fileHandle As Integer )\nCloses file",
	"FGETS":"FGets( fileHandle As Integer ) As String\nReturns next line of text from file",
	"FOPEN":"FOpen( fileName As String, modeString As String ) As Integer\nReturns handle to file.\nMode is \"r\" (read), \"w\" (write) or \"b\" (binary)",
	"FPUTS":"FPuts( fileHandle As Integer, text As String )\nAdds text to file",
	"FILEEXISTS":"FileExists( fileName As String ) As Boolean\nReturns true if file exists",
	"FIX":"Fix( n As Number ) As Number\nReturns integer portion of number",
	"FORMAT":"Format( formatString, ... )",
	"FRAC":"Frac( n As Number ) As Number\nReturns non-integer portion of number",
	"FREEFILE":"FreeFile( fileName as String )\nReturns next free file handle",		
	"HEX":"Hex( number as Integer ) As String\nReturns hexidecimal representation of number",
	"INDEXES":"Indexes( arrayName as Array )",
	"INSERT":"Insert( targetString As String, subString As String, position As Integer )\nInserts substring into target string at position",
	"INSTR":"Instr( [startPosition As Integer, ] searchString As String, searchForString As String ) As Integer\nReturns position of substring in string starting at position",
	"INT":"Int( number As Number ) As Integer\nReturns integer portion of number",
	"KILL":"Kill( fileName As String )\nDeletes file",
	"LCASE":"LCase( text As String ) As String\nReturns string in lower case",
	"LEFT":"Left( text As String, length As Integer ) As String\nRemoves whitespace from left side of string",
	"LEN":"Len( text As String ) As Integer\nReturns length of string",
	"LENGTH":"Length( text As String ) As Integer\nReturns length of string",
	"LOC":"Loc( fileHandle As Integer ) As Integer\nReturns position of file pointer",
	"LOF":"LoF( fileHandle As Integer ) As Integer\nReturns length of file, in bytes",
	"LOG":"Log( number As Number ) As Number",
	"LBOUND":"LBound( arrayName As Array, index as Integer ) As Integer\nReturns lower bound of arrays's nth index",
	"LTRIM":"LTrim( text As String ) As String\nReturns string with whitespace removed from both sides",	
	"MID":"Mid( text As String, startPosition As Integer [, length As Integer] ) As String\nReturns substring of string",
	"MKDIR":"MkDir( directoryName As String )\nCreates a directory in the current directory",
	"NOTBITS":"NotBits( n As Integer ) As Integer\nReturns bitwise NOT of number",
	"ORBITS":"OrBits( n1 As Integer, n2 As Integer ) As Integer\nReturns bitwise OR between two numbers", 
	"RANDOMIZE":"Randomize( [seed As Integer] )\nSets seed for random number generator",
	"READBYTE":"ReadByte( fileHandle As Integer ) As Integer\nReturns byte from file",
	"RENAME":"Rename( oldFileName As String, newFileName As String )\nRenames file",
	"REPLACE":"Replace( [startPosition As Integer, ] sourceString As String, searchForString As String, replaceWithString As String ) As String\nReturns string with occurances of substring replaced with new value",
	"REVERSE":"Reverse( text As String ) As String\nReturns reversed value of string",
	"RIGHT":"Right( text As String, length As Integer) As String\nReturns the rightmost characters in a string",
	"RINSTR":"RInstr( searchString As String, searchForString As String [, optionalStartPosition As Integer] ) As Integer\nReturns position of substring in string",
	"RMDIR":"RmDir( directoryName As String)\nRemoves the requested directory",
	"ROUND":"Round( n As Number) As Integer\nRounds value down to nearest integer",
	"RTRIM":"RTrim( text As String ) As String\nReturns string with whitespace removed from right side",
	"RUN":"Run( command As String )\nExecutes command from shell",
	"SEEK":"Seek( handle As Integer [, filePosition As Integer] )\nMoves to position in file",
	"SIGN":"Sign( n As Number )\nReturns 1 if number is positive, -1 if number is negative",
    "SHELL":"Shell( commandString )\nExecutes commandString, waits until finished.",
	"SIN":"Sin( n As Number )Returns the sin of n.",
	"SPACE":"Space( n As Number )Returns string built of n spaces.",
    "SQR":"Sqr( n As Number )Returns square root of n.",
    "SQRT":"Sqrt( n As Number )Returns square root of n.",
	"STR":"Str( n As Number )Returns the string representation of n.",
	"STRF":"StrF( n As Number )Returns the string representation of n.",
	"STRING":"String( text As String, repetitions as Integer) As String\nReturns a string with string repeated repetition times.",
	"SUBSTR":"Substr( sourceString As String, startPosition As Integer, endPosition As Integer ) As String\nReturns substring from sourceString, starting at startPosition to endPosition.",
	"TALLY":"Tally( [startPosition As Integer, ] searchString As String, searchForString As String) As Integer\nReturns number of times searchForString occurs in searchString.",
	"TAN":"Tan( n As Number ) As Number\nReturns tangent of given angle in radians.",
	"TIMER":"Timer() As Integer\nReturns current timer value.",
	"TIME":"Time() As String\nReturns current time as string in HH:MM:SS format.",
	"TYPEOF":"TypeOf( object As Object ) As String\nReturns string with name of object’s datatype.",
	"UBOUND":"UBound( arrayName As Array, index As Integer ) As Integer\nReturns the upper bound of index index in arrayName.",
	"UCASE":"UCase( text As String ) As String\nReturns string in upper case.",
	"VAL":"Val( text As String ) As Number\nReturns the numeric value of string.",
	"WRITEBYTE":"WriteByte( fileHandle As Integer, byte As Integer )\nWrites a single byte to fileHandle.",
    "XOR":"Xor( n1 As Integer, n2 As Integer ) As Integer\nPerforms bitwise XOR on values" }


dim superList = {}
dim methodList = {}

' load the classes
sub loadClassTips()
    ' skip if hints file not found
    if not fileExists( "ide_tip.txt" ) then return

	dim className, upperClassName, superName, thisMethods
    dim handle = fopen( "ide_tips.txt", "r" )
	while not eof( handle )
		dim text = fgets( handle )
		if instr( text, "%CLASS" ) then
			text = text[8:]
			dim comma = instr( text, "," )
			if comma then
				className = text[:comma-1]
				superName = text[comma+1:]
			else
				className = text
				superName = ""
			end if
			upperClassName = ucase( className )
			superList[className] = superName			
			methodList[className] = {}
			' get pointer to method list
			thisMethods = methodList[className]
		else
            dim builtin = false
			dim ctor = false
            if text[:9] = "%BUILTIN " then
                builtin = true
                text = text[10:]
			elseif text[:6] = "%CTOR " then
				ctor = true
				text = text[7:]
            end if
                
			' method belonging to current class
			dim sep = instr( text, "|" )
			dim methodName = text[1:sep-1]
			dim methodText = text[sep+1:]

            if methodName = upperClassName or builtin then
				' treat this as a built in routine
				builtinRoutines[methodName] = methodText

			elseif ctor then
				' create this as a new class inheriting from the current class
				superList[methodName] = className

				' treat constructor as a builtin
				builtinRoutines[methodName] = methodText

				' no methods in this class
				methodList[methodName] = {}

			else
				' add as a method to the current class
				thisMethods[methodName] = methodText
			end if
		end if
	end while
	fclose( handle )

end sub

loadClassTips()


dim varList = {}
dim routineList = {}

sub scanForDeclarations()

	' this should find out what declaration it is first, and ignore other subs...

	dim name, nameLen, parenAt
	dim i, endNameAt, endPrototypeAt, prototype, lookFor, skipSize
	dim at as integer

	' clear the varlist
	varList = {}

	' get the text from the file
	dim mixedCaseText as string = ed.GetText()
	dim text as string = ucase( mixedCaseText )

	' replace whitespace with spaces
	text = replace( text, "\t", " " )
	text = replace( text, "\r", " " )
	text = replace( text, "\n", " " )

    ' start with the builtin routines as a base
    routineList = clone( builtinRoutines )

	' find all the subs and functions
	for i = 1 to 2
		at = 0

		if i = 1 then
			lookFor = "SUB "
			skipSize = 4
		else
			lookFor = "FUNCTION "
			skipSize = 9
		end if

		while true
			' look for next SUB for FUNCTION
			at = instr( at+1, text, lookFor )

			' no more?
			if at = 0 then
				exit while
			end if
	
			' skip past SUB/FUNCTION
			at += skipSize

			endNameAt = instr( at, text, "(" )
			endPrototypeAt = instr( at, text, ")" )
			name = text[at:endNameAt-1]
			prototype = mixedCaseText[at:endPrototypeAt]

			' add to routine list
			routineList[name] = prototype
	
		end while
	next


	' find all the datatype declarations
	at = 0
	while true
		' look for next "AT"
		at = instr( at+1, text, " AS " )

		' no more?
		if at = 0 then
			exit while
		end if

		' get the datatype that follows the AS
		dim spaceAt as integer = instr( at+4, text, " " )
		dim theType = text[at+4:spaceAt-1]

		' look for space preceeding variable name
		spaceAt = rinstr( at-2, text, " " )
		dim theName = text[spaceAt+1:at-1]

		' ignore OPEN statement
		select case theType
		case "INPUT", "OUTPUT"
		case else
			' add to the list
			varList[theName] = theType
		end select

	end while

end sub


function getPreceedingName( at as integer )
	' step backwards to get the token
	dim i as integer
	for i = at-2 to 0 step -1
		dim c = Chr(ed.GetCharAt(i))        
		select case c
		case "A" to "Z", "a" to "z", "0" to "9", "."
			' keep going
		case else
			break
        end select
	next

	' return the token
	return trim(ed.GetTextRange( i+1, at-1 ))

end function



' These show the state of the parameter tip
dim parmTipAt = 0
dim parmTipLine = 0
dim parmTipText = ""
dim tipYellow = New wxColor( 255, 255, 225 )
ed.CallTipSetBackground( tipYellow )

sub showParmTip()
	' show the parameters for the current method
	' highlight the parameter with focus

	' count the commas so far
	dim commas = 1
	dim i
	for i = parmTipAt to ed.GetCurrentPos()
		if ed.GetCharAt(i) = Asc(",") then
			commas += 1
		end if
	next

	dim startHighlight = 0
	dim endHighlight = 0

	dim parmCommas = 0	
	dim letter, pos
	for each pos, letter in parmTipText

		' start of new parameter?
		if letter = "," or letter = "(" or letter = ")" then
			' increment the comma count
			parmCommas += 1
			if parmCommas = commas then
				' start highlighting
				startHighlight = pos

			elsif parmCommas = commas + 1 then
				' end highlighting
				endHighlight = pos-1
			end if		

		end if

	next

	' FIXME! handle the case where there are too many commas!
		
	ed.CallTipShow( parmTipAt, parmTipText )
	ed.CallTipSetHighlight( startHighlight, endHighlight )		

end sub

function getUnsortedMethods( varType as string )

	' gather all the methods for the datatype, returns an unsorted table
	' the goes though the class and all the superclasses

	dim theKey, theValue

	' holds the collected methods
	dim collected as table = {}

	' iterate through all the inherited classes
	while varType <> Nothing

		' get the methods for this class
		dim theMethods = methodList[varType]

		' no methods?
		if theMethods <> Nothing then

			' iterate through the methods		
			for each theKey, theValue in theMethods
				' is the method not yet in the collection?
				if collected[theKey] = Nothing then
					' add to the table
					collected[theKey] = theValue
				end if
			next

		end if

		' get the superclass
		varType = superList[varType]

	end while

	return collected

end function



function getSortedMethods( varType as string )

	' gather all the methods for the datatype, returns a sorted list
	' the goes though the class and all the superclasses

	dim theKey, theValue

	' holds the collected methods
	dim collected as table = getUnsortedMethods( varType )

	' copy the collection to a list for sorting
	dim theList as list = []
	for each theKey, theValue in collected 
		' add the value to the list
		theList.append( theValue )
	next

	' sort the list
	bubbleSort( theList )

	return theList

end function



dim showingTip  = 0
dim showingMethodTip = 0
dim inEditEvent = false

sub editEvent( event )

	' prevent multiple callbacks
	if inEditEvent then
		return
	end if

	' set flag
	inEditEvent = true

	dim posAt = ed.GetCurrentPos()
	dim lineAt = ed.GetCurrentLine()
	dim tips as string
	dim c = chr(ed.GetCharAt( posAt-1 ))
	dim varType, myMethods, hint

	' is a parm tip currently active?
	if parmTipAt and c <> "(" then
		' end of parameters?
		if c = ")" or posAt < parmTipAt or lineAt <> parmTipLine then
			' zap it
			ed.CallTipCancel()
			parmTipAt = 0
			parmTipLine = 0
		else
			' show the tip
			showParmTip()
		end if
		inEditEvent = false
		return
	end if

	select case c
	case "."
		' get the name of the preceeding variable
		dim varName as string = ucase( getPreceedingName( posAt ) )

		' scan for type declarations
		scanForDeclarations()

		' find the variable
		varType = varList[ varName ]
		if varType <> Nothing then
			dim theMethods = getSortedMethods( varType )

			' find methods
			ed.AutoCompSetSeparator( Asc("\n") )
				
			dim m, n, myList = ""
			for each m, n in theMethods
				mylist &= n & "\n"
			next

			' flag that the tip is showing
			showingMethodTip = posAt	

			'  don't care about case
			ed.AutoCompSetIgnoreCase( true )

			' show the list
			ed.UserListShow( true, myList )

		end if

	case "("
		' make sure the isn't a sub declaration
		dim lineText = ucase( trim( ed.GetLine( lineAt ) ) )
		if lineText[:4] = "SUB " or
		   lineText[:9] = "FUNCTION " then
			inEditEvent = false
			return
		end if
		
		' scan for type declarations
		scanForDeclarations()

		' get the text
		dim name = ucase( getPreceedingName(posAt) )

		' assume no hint is found
		hint = Nothing

		' dot indicates a method call
        dim dotAt = instr( name, "." )
        if dotAt then
            ' look up the datatype
            varType = varList[ name[:dotAt-1]]
            if varType <> Nothing then
                ' get the methods for this datatype
                myMethods = getUnsortedMethods(varType)
                if myMethods <> Nothing then
                    ' look for a hint
                    hint = myMethods[name[dotAt+1:]]
                end if
            end if			
        else
            ' look for the routine
            hint = routineList[name]
        end if

        if hint <> Nothing then
			' put line breaks after commas if hint is long
			if length( hint ) > 60 then
				hint = replace( hint, ",", ",\n" )
			end if

            parmTipText = hint
			ParmTipAt = posAt
			ParmTipLine = lineAt
			showParmTip()
		end if

	end select
	
	inEditEvent = false

end sub

' handle picking from call tip list
sub callTipClick( event as wxStyledTextEvent )
	' zap the tip
	ed.CallTipCancel()

	' get the selected text
	dim text as string = event.GetText()

	' remove the arglist
	dim at as integer = instr( text, "(" )
	text = text[1:at]

	' select the typed text
	ed.SetSelection( showingMethodTip, ed.GetCurrentPos() )

	' replace with the selected text
	ed.ReplaceSelection( text )

end sub
Connect( ed, wxEVT_STC_USERLISTSELECTION, "callTipClick" )



' set up hints
'connect( ed, -1, wxEVT_STC_CHARADDED, "editEvent" )
' the mouse has moved
' connect( ed, -1, wxEVT_MOUSE_CAPTURE_CHANGED, "editEvent" )
 connect( ed, -1, wxEVT_STC_UPDATEUI, "editEvent" )

frame.Show(True)
setup()

' hints
'Dim tipProvider = wxCreateFileTipProvider("tips.txt", 0)
'wxShowTip(Nothing, tipProvider)
'tipProvider = Nothing
