#!/usr/bin/env ruby
#--
# Copyright (C) 2011 Cathal Mc Ginley
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++

# Feature Matrix Generator takes simply-indented text files
# and turns them into pretty HTML tables representing feature
# comparison among several software projects

require 'pp'
require 'rubygems'
require 'kramdown'


class Feature
  attr_reader :text
  def initialize
    @text = ""
  end

  def add_text(txt)
    unless @text.empty?
      @text += "\n"
    end
    unless txt.nil? 
      @text += txt
    end
  end
end

class Section
  attr_accessor :sections, :title, :parent
  attr_accessor :indentation
  attr_accessor :features
  def initialize
    @title = ""
    @sections = []
    @parent = nil
    @current_feature = nil
    @current_abbr
    @features = []
  end

  def got_more_feature_text(feature_text)
    got_feature_text(@current_abbr, feature_text)
  end

  def got_feature_text(project_abbr, feature_text)
    @current_abbr = project_abbr
    unless @current_feature_map
      @current_feature_map = {}
    end
    if @current_feature_map.has_key? project_abbr
      f = @current_feature_map[project_abbr]
    else
      f = Feature.new
      @current_feature_map[project_abbr] = f
    end
    f.add_text(feature_text)
  end

  def feature_end
    if @current_feature_map
      @features << @current_feature_map
      @current_feature_map = nil
    end
  end

end

class Project
  attr_accessor :abbr, :name, :css_name
  def initialize(abbr, name, css_name)
    @abbr = abbr
    @name = name
    @css_name = css_name
  end
end

class FeatureMatrix

  attr_accessor :sections, :projects, :current_section
  def initialize()
    @sections = []
    @projects = []
    @current_section = nil
  end

  def add_project(abbr, name, css_name)
    @projects << Project.new(abbr, name, css_name)
  end

  def new_section(indentation_level)
    s = Section.new
    s.indentation = indentation_level
    if @current_section == nil
      @sections << s
    else
      s.parent = @current_section
      @current_section.sections << s
    end
    @current_section = s
    s 
  end

  def end_section(indentation_level)
    # pop!
    if @current_section
      @current_section = @current_section.parent
    else
      puts "WARN popping section, but section stack already empty"
    end
  end

  def blank_line!
    if @current_section
      @current_section.feature_end
    end
  end
end

class FeatureMatrixParser
  
  def initialize(filenames)
    @filenames = filenames
    @projects_defined = false
    @tab_width = 2
    @indentation = 0
  end
  def parse()
    @matrix = FeatureMatrix.new
    @filenames.each {|f| parse_file(f) }
    blank_line!
    @matrix
  end

  def parse_file(filename)
    @lines = File.open(filename).readlines
    @indent = 0
    # first line is tab settings
    # second line is column shortcuts
    unless @projects_defined
      shortcuts_line = @lines[1]
      projects = shortcuts_line.split()
      # shortcuts = {}
      projects.each do |p|
        abbr, name = p.split("=")
        css_name = name.gsub("[^A-Za-z]", "-").downcase.squeeze("-")
        name = name.gsub("_", " ")      
        @matrix.add_project(abbr, name, css_name)
      end
      @projects_defined = true
    end
    @lines[2 .. -1].each_with_index do |line, index|
      @line_num = index + 3
      process_line(line.rstrip)
    end
  end

  def debug(message)
    puts "#{message}  @line #{@line_num}"
  end

  def blank_line!
    @matrix.blank_line!
  end

  def indentation(line)
    if line[0 .. 0] == ' '
      if line =~ /(^[\s]+)/
        ($1.size / @tab_width)
      else
        0
      end
    else
      0
    end
  end

  def handle_line_for_section(line)
    line = line.strip
    fchar = line[0 .. 0]
    if fchar == "!"
      # HEADER
      @header = line[1 .. -1]
    elsif fchar == "."
      # CONTINUATION of feature for previous project
      if @matrix.current_section
        @matrix.current_section.got_more_feature_text(line[1 .. -1].strip)
      end
    elsif fchar =~ /[A-Z]/
      # FEATURE for project with this abbreviation
      if @matrix.current_section
        @matrix.current_section.got_feature_text(fchar, line[1 .. -1].strip)
      end
    elsif fchar == '#'
      # comment, ignore
    else
      debug "Unknown feature matrix start: #{fchar}"
    end
  end

  def process_line(line)
    if line.empty?
      blank_line!
      
    else
      ind = indentation(line)
      if ind == @indentation
        # same section
        # TODO handle line
        handle_line_for_section(line)
      else
        # indentation has changed
        if (@indentation > ind)
          # TODO handle line
          handle_line_for_section(line)

          diff = @indentation - ind
          diff.times { @matrix.end_section(ind) }
          # @header = nil
        else
          section = @matrix.new_section(ind)
          if @header
            section.title = @header
            @header = nil
          end
          # TODO handle line
          handle_line_for_section(line)
        end 
        @indentation = ind
      end
    end
  end
end

class Htmlizer
  def initialize(matrix)
    @matrix = matrix
    @indentation = 0
  end
  
  def nest(text="&nbsp;")
    indent_span = "<span class='indent"
    if @indentation > 0
      indent_span += "-#{@indentation}"
    end
    indent_span += "'>#{text}"
    indent_span
  end

  def table_header
    tstart = "\n\n  <table class='feature-matrix'>\n" +
      "  <tr class='top'>\n" + 
      "    <td class='nest'></td>\n"
    
    tmid = ""
    @matrix.projects.each do |project|
      tmid += "    <th class='#{project.css_name}'>#{project.name}</th>\n"
    end
    tend = "  </tr>\n"
    tstart + tmid + tend
  end

  def markdown_to_markup(markdown)
    html = Kramdown::Document.new(markdown).to_html.strip
    unless html =~ /<\/p>\n\n<p>/ # i.e. has internal paragraphs
      if html[0 .. 2] == "<p>" && html[-4 .. -1] == "</p>"
        html = html[3..-5]
      end
    end
    #html.gsub!(/<\/p>\n\n<p>/, "<br />\n")
    html
  end

  def output_section(section)
    projects = @matrix.projects
    if section.title      
      # XXXE
      indent_class = "indent"
      if @indentation > 0
        indent_class += "-#{@indentation}"
      end
      header = "  <tr class='section-header'><th class='#{indent_class}' colspan='#{1+@matrix.projects.size}'>" +
        nest(section.title) + "</th></tr>\n"
      @out.puts(header)
    end
    nest_cell = "    <td class='nest'>" + nest + "</td>"
    even_feature = false
    section.features.each do |features_map|
      if even_feature
        @out.puts("  <tr class='feature-highlight'>")
      else        
        @out.puts("  <tr class='feature'>")
      end
      even_feature = !even_feature
      @out.puts(nest_cell)
      projects.each do |p|
        feature = features_map[p.abbr]
        if feature
          markup = markdown_to_markup(feature.text)
          td = "    <td class='#{p.css_name}'>#{markup}</td>"
          @out.puts(td)
        else
          puts "[WARN] missing feature for #{p.name} in section #{section.title}"
        end
      end
      @out.puts("  </tr>")      
    end
    unless section.sections.empty?
      @indentation += 1
      section.sections.each do |sub|
        output_section(sub)
      end
      @indentation -= 1
    end
  end

  def write(outfilename)
    File.open(outfilename, 'wb') do |out|      
      @out = out
      @out.puts(table_header)
      @indentation = 0
      @matrix.sections.each do |section|
        output_section(section)
      end
      @out.puts("</table>\n\n")
      @out = nil
    end
  end
end


parser = FeatureMatrixParser.new(ARGV[0..-2])
matrix = parser.parse
htmlizer = Htmlizer.new(matrix)
htmlizer.write(ARGV[-1])



