#!/usr/bin/env ruby
# Copyright (c) 2006, Horst Gutmann <zerok@zerokspot.com>
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
# 
#     * Redistributions of source code must retain the above copyright notice, this
#      list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright notice, 
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#     * Neither the name of the author nor the names of its contributors may
#       be used to endorse or promote products derived from this software without 
#       specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
require 'rexml/streamlistener'
require 'rexml/document'
require 'ostruct'
require 'optparse'
require 'erb'
#--------------------------------------------------------------------
class Header
  attr_reader :children, :parent, :label, :id, :level, :notoc
  attr_writer :children, :parent, :label, :id, :level, :notoc
  def initialize
    @children = Array.new
    @parent = nil
    @label = nil
    @level = 0
    @id = nil
    @notoc = false
  end
  def to_s
    if @notoc 
      return ""
    end
    s = ""
    if @level == 0
      s << "<ul id=\"_maintoc\">"
      @children.each {|c| s << c.to_s }
      s << "</ul>"
      return s
    end
    s << "<li>"
    if @label
      if @id
        s << "<a href=\"##{@id}\">#{@label}</a>"
      else 
        s << @label
      end
    end
    if @children.length > 0
      s << "<ul>"
      @children.each {|c| s << c.to_s }
      s << "</ul>"
    end
    s << "</li>"
    s
  end
end#class Header
#--------------------------------------------------------------------
class HtmlTocListener
  attr_reader :root
  def initialize
    @stack = Array.new
    @current_element = nil
    @in_header = false
    @current_header_level = 0
    @root = Header.new
    @current_header = @root
    @previous_header = @root
  end
  include REXML::StreamListener
  def header?(name)
    get_header_level(name)  
  end
  def get_header_level(name)
    name =~ /^h([2-6])$/
    $1
  end
	def tag_start(name,attrs)
		@stack << name
		if header?(name)
		  # Secure previous header
		  if @current_header
		    @previous_header = @current_header
	    end
		  @in_header = true
		  @current_header = Header.new
		  @current_header.level = get_header_level(name).to_i
      @current_header.id = attrs["id"] if attrs["id"]
      if attrs["class"] and attrs["class"].split(" ").include?("notoc")
        @current_header.notoc = true
      end
		  if @current_header.level == @previous_header.level
		    # Add this header to the parent of  the previous current_header
		    @previous_header.parent.children << @current_header
		    @current_header.parent = @previous_header.parent
		  elsif @current_header.level > @previous_header.level
		    @current_header.parent = @previous_header
		    @previous_header.children << @current_header
		  else
		    # @previous_header.level > @current_header.level
		    @previous_header.parent.parent.children << @current_header
		    @current_header.parent = @previous_header.parent.parent
		  end
		end
		if $DEBUG
		  p "> #{name}"
		end
		@current_element = name
	end
	def tag_end(name)
		name = @stack.pop
		if $DEBUG
		  p "< #{name}"
	  end
	  if header?(name)
	    @in_header = false
    end
	end
	def text(t)
	  if @in_header
	    @current_header.label = t
    end
  end
	def method_missing(id)
	  p "Missing method for #{id}"
  end
end#class HtmlTocListener
#--------------------------------------------------------------------
options = OpenStruct.new
opts = OptionParser.new(ARGV) do |o|
  o.on("-i","--input FILE","Path to input file"){|f| options.input = f}
  o.on("-o","--output FILE","Path to output file"){|f| options.output = f}
end
opts.parse!
if options.input.nil? or options.output.nil?
  puts opts.help
  exit
end
if not File.exists?(options.input)
  raise "Input file not found"
end
if File.expand_path(options.input) == File.expand_path(options.output)
  raise "Never write to the input file"
end
#--------------------------------------------------------------------

listener = HtmlTocListener.new
File.open(options.input) do |fp|
  REXML::Document.parse_stream(fp,listener)
end
tpl = File.read(options.input)
toc = "-->#{listener.root.to_s}<!--"
File.open(options.output,"w+") do |fp|
  fp.write(ERB.new(tpl).result(binding))
end