#!/usr/bin/env ruby

require 'fox'

include Fox

# Node in the class hierarchy tree
class ClassTreeNode
  def initialize(className, parentClassName)
    @className = className
    @parentClassName = parentClassName
    @children = []
  end

  def addChild(child)
    @children.push(child)
  end

  def className
    @className
  end

  def parentClassName
    @parentClassName
  end

  def <=>(other)
    className <=> other.className
  end

  def children
    @children.sort
  end
end

# The class hierarchy tree
class ClassTree
  def initialize
    # Find the Fox module
    @mFox = Fox

    # Get an hash containing class names for Fox
    classes = {}
    @mFox.constants.each { |name|
      c = @mFox.const_get(name)
      if c.kind_of? Class
        if name =~ /FX_/
          className       = name.sub(/FX_/, "FX")
          parentClassName = c.superclass.name.sub(/Fox::FX_/, "FX")
          classNode  = ClassTreeNode.new(className, parentClassName)
          classes[className] = classNode
        end
      end
    }

    # Go back and do this
    roots = []
    classes.each_value { |aValue|
      parentNode = classes[aValue.parentClassName]
      if parentNode
        parentNode.addChild(aValue)
      else
        parentNode = ClassTreeNode.new(aValue.parentClassName, nil)
      end
    }

    # FXObject is the root
    @root = classes["FXObject"]
  end

  def root
    return @root
  end
end

class BrowserWindow < FXMainWindow

  def initialize(app)
    # Call base class initializer first
    super(app, "Browse", nil, nil, DECOR_ALL, 0, 0, 600, 400)

    # Contents
    contents = FXHorizontalFrame.new(self, LAYOUT_FILL_X|LAYOUT_FILL_Y)

    # Horizontal splitter
    splitter = FXSplitter.new(contents, (LAYOUT_SIDE_TOP|LAYOUT_FILL_X|
      LAYOUT_FILL_Y|SPLITTER_TRACKING|SPLITTER_HORIZONTAL))

    # Create a sunken frame to hold the tree list
    groupbox = FXGroupBox.new(splitter, "Classes",
      LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_GROOVE)
    frame = FXHorizontalFrame.new(groupbox,
      LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_SUNKEN|FRAME_THICK)

    # Create the empty tree list
    @treeList = FXTreeList.new(frame, 0, nil, 0,
      (TREELIST_BROWSESELECT|TREELIST_SHOWS_LINES|TREELIST_SHOWS_BOXES|
       TREELIST_ROOT_BOXES|LAYOUT_FILL_X|LAYOUT_FILL_Y))
    @treeList.connect(SEL_COMMAND) { |sender, sel, item|
      getApp().beginWaitCursor
      s = getInstanceMethods(item.to_s).join("\n")
      @methodsText.text = s
      s = getConstants(item.to_s).join("\n")
      @constantsText.text = s
      getApp().endWaitCursor
    }

    # Fill it up based on the tree contents
    populateTree(@treeList, nil, ClassTree.new().root)

    # Tabbed notebook on the right
    tabBook = FXTabBook.new(splitter, nil, 0,
      LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_RIGHT)

    tab1 = FXTabItem.new(tabBook, "Methods")
    page1 = FXHorizontalFrame.new(tabBook, FRAME_THICK|FRAME_RAISED)
    frame1 = FXHorizontalFrame.new(page1,
      FRAME_THICK|FRAME_SUNKEN|LAYOUT_FILL_X|LAYOUT_FILL_Y)
    @methodsText = FXText.new(frame1, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
    @methodsText.text = "List of methods goes here"
    @methodsText.editable = false

    tab2 = FXTabItem.new(tabBook, "Constants")
    page2 = FXHorizontalFrame.new(tabBook, FRAME_THICK|FRAME_RAISED)
    frame2 = FXHorizontalFrame.new(page2,
      FRAME_THICK|FRAME_SUNKEN|LAYOUT_FILL_X|LAYOUT_FILL_Y)
    @constantsText = FXText.new(frame2, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
    @constantsText.text = "List of constants goes here"
    @constantsText.editable = false

    # Cache of classname -> method and classname -> constants lists
    @instanceMethods = {}
    @classConstants = {}
  end

  # Recursively fill up the tree list
  def populateTree(treeList, rootItem, rootNode)
    rootNode.children.each { |childNode|
      childItem = treeList.addItemLast(rootItem, childNode.className)
      populateTree(treeList, childItem, childNode)
    }
  end

  # Create and show the main window
  def create
    super
    @treeList.parent.parent.setWidth(@treeList.font.getTextWidth('MMMMMMMMMMMMMMMM'))
    show(PLACEMENT_SCREEN)
  end

  # Returns an array of instance methods for the named class
  def getInstanceMethods(className)
    methods = @instanceMethods[className]
    if !methods
      c = Fox.const_get(className.sub(/FX/, "FX_"))
      if c
        methods = c.instance_methods
      end
      methods += Fox.const_get(className).instance_methods
      methods.sort!
      @instanceMethods[className] = methods
    end
    methods
  end

  # Returns an array of constants for the named class
  def getConstants(className)
    constants = @classConstants[className]
    if !constants
      klass = Fox.const_get(className)
      constants = klass.constants
      superklass = Fox.const_get(className.sub(/FX/, "FX_"))
      if superklass
        constants -= superklass.superclass.constants
      else
        constants -= klass.superclass.constants
      end
      constants.sort!
      @classConstants[className] = constants
    end
    constants
  end
end

def run
  # Create a new application
  application = FXApp.new("Browser", "FoxTest")

  # Open the display
  application.init(ARGV)

  # Construct the main window
  BrowserWindow.new(application)

  # Create the windows
  application.create

  # Start event loop
  application.run
end

run
