#! /usr/bin/env ruby

require "getoptlong"

##
# IFace data stucture
#
class IFace
	attr_accessor :cat

	def initialize
		@cat = []
		changeCat(nil)
	end

	def changeCat(_name)
		@currentCat = IFaceCat.new(_name)
		@cat << @currentCat
	end

	def addEmpty
		@currentCat.addEntry(IFaceEmpty.new)
	end

	def addComment(_text)
		@currentCat.addEntry(IFaceComment.new(_text))
	end

	def addVal(_name, _code)
		@currentCat.addEntry(IFaceVal.new(_name, _code))
	end

	def addFun(_name, _code, _return, _args)
		@currentCat.addEntry(IFaceFun.new(_name, _code, _return, _args))
	end

	def addGet(_name, _code, _return, _args)
		@currentCat.addEntry(IFaceGet.new(_name, _code, _return, _args))
	end

	def addSet(_name, _code, _return, _args)
		@currentCat.addEntry(IFaceSet.new(_name, _code, _return, _args))
	end

	def addEvt(_name, _code, _return, _args)
		@currentCat.addEntry(IFaceEvt.new(_name, _code, _return, _args))
	end
end

class IFaceCat
	attr_reader :name, :entries

	def initialize(_name)
		@name = _name
		@entries = []
	end

	def addEntry(_entry)
		@entries << _entry
	end
end

class IFaceEmpty
	def accept(visitor)
		visitor.visitIFaceEmpty(self)
	end
end

class IFaceComment
	attr_reader :text

	def initialize(_text)
		@text = _text
	end

	def accept(visitor)
		visitor.visitIFaceComment(self)
	end
end

class IFaceVal
	attr_reader :name, :code

	def initialize(_name, _code)
		@name = _name
		@code = _code
	end

	def accept(visitor)
		visitor.visitIFaceVal(self)
	end
end

class IFaceFunArg
	attr_reader :name, :type
	def initialize(_name, _type)
		@name = _name
		@type = _type
	end
end

class IFaceFun < IFaceVal
	attr_reader :return, :args

	def initialize(_name, _code, _return, _args)
		super(_name, _code)
		@return = _return
		@args = []
		_args.each do |arg|
			@args << IFaceFunArg.new(arg[1], arg[0])
		end
	end

	def accept(visitor)
		visitor.visitIFaceFun(self)
	end
end

class IFaceGet < IFaceFun
	def initialize(_name, _code, _return, _args)
		super
	end

	def accept(visitor)
		visitor.visitIFaceGet(self)
	end
end

class IFaceSet < IFaceFun
	def initialize(_name, _code, _return, _args)
		super
	end

	def accept(visitor)
		visitor.visitIFaceSet(self)
	end
end

class IFaceEvt < IFaceFun
	def initialize(_name, _code, _return, _args)
		super
	end

	def accept(visitor)
		visitor.visitIFaceEvt(self)
	end
end

##
# IFace parser
#
class IFaceParser
	def initialize(_input, _output)
		@input = _input
		@output = _output
		@iface = IFace.new
	end

	def process
		@input.each_line do |line|
			case line
			when /^##/
			when /^$/
				processEmpty
			when /^#(.*)/
				processComment $1
			else
				command, args = line.split(/\s+/, 2)
				processCommand(command, args)
			end
		end
		@iface
	end

	def processEmpty
		@iface.addEmpty
	end

	def processComment(_comment)
		@iface.addComment(_comment)
	end

	def processCommand(_cmd, _args)
		unless _cmd.empty?
			process = "process" + _cmd[0..0].upcase + _cmd[1..-1]
			if respond_to?(process)
				method(process).call(_args)
			else
				puts "*** Unknown cmd: #{_cmd} #{_args}"
			end
		end
	end

	def processCat(_cat)
		@iface.changeCat(_cat)
	end

	def processVal(_val)
		name, value = _val.split(/\s*=\s*/)
		@iface.addVal(name, value)
	end

	def parseFun(_fun)
		returnType, _fun = _fun.split(/\s+/, 2)
		name, _fun = _fun.split(/\s*=\s*/, 2)
		_fun =~ /(\d+)\s*\((.*)\)/
		code = $1
		_fun = $2.split(/\s*,\s*/, -1)
		args = _fun.collect do |arg|
			argType, argName = arg.split
			argType, argName = nil, nil if argName == ""
			[argType, argName]
		end
		[name, code, returnType, args]
	end

	def processFun(_fun)
		name, code, returnType, args = parseFun(_fun)
		@iface.addFun(name, code, returnType, args)
	end

	def processGet(_fun)
		name, code, returnType, args = parseFun(_fun)
		@iface.addGet(name, code, returnType, args)
	end

	def processSet(_fun)
		name, code, returnType, args = parseFun(_fun)
		@iface.addSet(name, code, returnType, args)
	end

	def processEvt(_fun)
		name, code, returnType, args = parseFun(_fun)
		@iface.addEvt(name, code, returnType, args)
	end

	def processEnu(_enu)
	end

	def processLex(_lex)
	end
end

##
# Code to generate Scintilla.rb from an IFace data structure
#
class ScintillaIFaceToRuby
	def initialize(_iface, _output)
		@iface = _iface
		@output = _output
		@reserved = Hash["end", "last", "setFocus", "setFocusFlag"]
	end

	def generateHeader
		@output.puts("# This file is automatically generated from Scintilla.iface")
		@output.puts("# DO NOT MODIFY")
		@output.puts
	end

	def generate
		generateHeader
		@output.puts("module Fox")
		@output.puts("  class FXScintilla")
		@iface.cat.each do |cat|
			@output.puts("    # #{cat.name}")
			cat.entries.each do |entry|
				entry.accept(self)
			end
		end
		@output.puts("  end")
		@output.puts("end")
	end

	def visitIFaceEmpty(_empty)
		@output.puts
	end

	def visitIFaceComment(_comment)
		@output.puts("    ##{_comment.text}")
	end

	def visitIFaceVal(_val)
		return if _val.name == "KeyMod"
		return if _val.name == "Lexer"
		@output.puts("    #{name(_val.name)} = #{_val.code}")
	end

	def visitIFaceFun(_fun)
		stringresult = _fun.args[1].type == "stringresult" and _fun.return == "int"
		stringresult1 = (stringresult and _fun.args[0].name == nil)

		name = name(_fun.name[0..0].downcase + _fun.name[1..-1])
		@output.print("    def #{name}")
		args = _fun.args.collect do |arg|
			if stringresult and arg.type == "stringresult"
				nil
			else
				name(arg.name)
			end
		end
		args.compact!
		@output.print("(#{args.join(', ')})") unless args.empty?
		@output.puts
		
		if stringresult and !stringresult1
			@output.puts("      buffer = \"\".ljust(#{_fun.args[0].name})")
		end

		returnValue = "sendMessage(#{_fun.code}"
		_fun.args.each do |arg|
			if stringresult and !stringresult1 and arg.type == "stringresult"
				returnValue += ", buffer"
			else
				returnValue += ", #{arg.name ? typeArg(arg.type, name(arg.name)) : 0}"
			end
		end
		returnValue += ")"

		if stringresult and !stringresult1
			@output.puts("      #{returnValue}")
			@output.puts("      buffer")
		else
			@output.puts("      #{typeRet(_fun.return, returnValue)}")
		end
		@output.puts("    end")
	end

	def visitIFaceGet(_get)
		visitIFaceFun(_get)
	end

	def visitIFaceSet(_set)
		visitIFaceFun(_set)
	end

	def visitIFaceEvt(_evt)
		name = "SCN_" + _evt.name.upcase
		@output.puts("    #{name} = #{_evt.code}")
	end

	def typeArg(_type, _value)
		case _type
		when "colour"
			"#{_value} & 0xffffff"
		else
			_value
		end
	end

	def typeRet(_type, _value)
		case _type
		when "bool"
			"#{_value} == 1 ? true : false"
		else
			_value
		end
	end

	def name(_name)
		name = @reserved[_name]
		name ? name : _name
	end
end

def main
	options = GetoptLong.new(["-i", GetoptLong::REQUIRED_ARGUMENT],
													 ["-o", GetoptLong::REQUIRED_ARGUMENT])

	input = output = nil
	options.each do |opt, arg|
		case opt
		when "-i"
			input = File.open(arg, File::RDONLY)
		when "-o"
			output = File.open(arg, File::CREAT|File::WRONLY|File::TRUNC)
		end
	end
	input = $stdin unless input
	output = $stdout unless output

	parser = IFaceParser.new(input, output)
	iface = parser.process

	gen = ScintillaIFaceToRuby.new(iface, output)
	gen.generate
ensure
	input.close
	output.close
end

main
