#!/usr/bin/ruby

# ./translate-mk-config

require 'optparse'
require 'yaml'
require 'pp'


# All field.NAME.TYPE directives in the current system configuration:
#
# $ find ../config/system* -name 'search*.config' -o -name 'global*.config' | xargs grep -h '^field\.' | sed 's/: .*//' | sed 's/\.[^.]*\./.*./' | sort | uniq -c | sort -rn
#   37 field.*.name
#   15 field.*.help
#   15 field.*.editType
#    7 field.*.visible
#    1 field.*.unique
#    1 field.*.height
# We will need to accomodate all these in the YAML master-file. Status:
#   name      DONE
#   help      DONE
#   editType  DONE (for editType:select only)
#   visible   DONE
#   unique    DONE
#   height    DONE

module Driver
  class Base
    def profile(name, description)
      _fail("profile")
    end

    def section(name, order, fieldList)
      _fail("section")
    end

    # Data members of 'value' passed into field():
    #   description
    #   mandatory
    #   note
    #   selectFrom
    #   usedBy
    #   visible
    #   unique
    #   height
    def field(name, value)
      _fail("field")
    end

    def end()
      # No-op for most drivers -- may be inherited
    end

    def _fail(method)
      raise "abstract method Driver::Base::#{method}() must be overridden in concrete subclass"
    end
  end

  class TorusProfile < Base
    def profile(name, description)
      puts "# Torus profile for '#{name}' records"
      puts "#"
      puts "# #{description}"
    end

    def section(name, order, fieldList)
      puts ""
      puts "# --- Section '#{name}' ---"
      # Order not needed for Torus profile
    end

    def field(name, value)
      note = value['note']
      s = "#{name}"
      s += " mandatory" if value['mandatory']
      s += " # #{note}" if note
      # Description, selectFrom and usedBy not needed for Torus profile
      puts s
    end
  end

  class MKAdmin < Base
    def profile(name, description)
      puts "# Automatically generated data profile for '#{name}' records"
      # Description not needed
    end

    def section(name, order, fieldList)
      puts ""
      puts "# --- Section '#{name}' ---"
      puts "field.#{fieldList[0][0]}.startSection: #{name}"
      # Order not needed
    end

    def field(name, value)
      displayName = value['name']
      description = value['description']
      selectFrom = value['selectFrom']
      height = value['height']
      optionsType = value['multi'] ? 'multiOptions' : 'options'

      puts "field.#{name}._exists: 1";
      puts "field.#{name}.name: #{displayName}" if displayName
      if description
        puts "field.#{name}.help: #{_value2mkadmin(description)}"
      end
      if selectFrom
        puts "field.#{name}.editType: #{optionsType} #{selectFrom.join('|')}"
      end
      puts "field.#{name}.visible: 1" if value['visible']
      puts "field.#{name}.unique: 1" if value['unique']
      puts "field.#{name}.height: #{height}" if height
      # 'mandatory' and 'note' not needed
    end

    def _value2mkadmin(val)
      val.
        gsub('\\', '\\\\'). # Double up any existing backslashes
        gsub('$', '$$'). # Double up any $ signs to prevent ${x} expansion
        gsub(/\n\s+/, " \n"). # Space at start of next line moves to end of this
        gsub(/\n(.)/, '\\' + "\n" + '\1') # Backslashes at end of all lines but last
    end
  end

  class HTML < Base
    def profile(name, description)
      print %[
<h1>The '#{name}' profile</h1>
<p>
  #{description}
</p>
<table class="field-table">
  <tr class="table-heading">
    <th>Name</th>
    <th>Mandatory?</th>
    <th>Used by</th>
    <th>Description</th>
  </tr>
]
    end

    def section(name, order, fieldList)
      print %[
  <tr class="table-subheading">
    <th colspan="4">#{name}</th>
  </tr>
]
    end

    def field(name, value)
      # Same logic as in Masterkey::Admin::fieldname()
      displayName = value['name']
      if !displayName
        displayName = name.gsub(/([A-Z])/, ' \1').capitalize
      end

      usedBy = value['usedBy']
      if !usedBy
        usedBy = ""
      elsif usedBy.is_a? Array
        usedBy = usedBy.join(", ")
      end

      description = "<p class='desc'>#{value['description']}</p>\n"
      if value['note']
        description += "<p class='note'><b>Note</b>: #{value['note']}</p>\n"
      end
      if value['selectFrom']
        x = value['selectFrom'].map { |x| x.sub(/=.*/, "") }.join(', ')
        description += "<p class='select-from'><b>Values:</b> #{x}</p>\n"
      end

      print %[
  <tr>
    <td>#{displayName}</td>
    <td>#{value['mandatory'] ? "Yes" : ""}</td>
    <td>#{usedBy}</td>
    <td>#{description}</td>
  </tr>
]
    end

    def end()
      puts "</table>"
    end
  end

  class FullHTML < HTML
    def profile(name, description)
      print %[<html>
  <head>
    <title>The '#{name}' profile</title>
]
      if $options[:inline]
        puts '      <style type="text/css">'
        ### Only works if you're in the right directory
        File.open('../doc/style.css', 'r') { |f| print f.read() }
        puts '      </style>'
      else
        print '    <link rel="stylesheet" type="text/css" href="style.css"/>'
      end
      print %[
  </head>
  <body class="#{name}-body">
]
      super(name, description)
    end
    def end()
      super()
      puts "  </body>\n"
      puts "</html>\n"
    end
  end
end


$options = { :verbose => false, :type => nil, :inline => false }

opts = OptionParser.new do |x|
  x.banner = "Usage: #$0 [options] <YAML-file>"
  x.on("-v", "--verbose", "Emit verbose commentary") {
    $options[:verbose] = true
  }
  x.on("-p", "--profile", "Generate a Torus profile") {
    $options[:type] = :torus
  }
  x.on("-m", "--mkadmin", "Generate an MKAdmin config fragment") {
    $options[:type] = :mkadmin
  }
  x.on("-h", "--html", "Generate HTML output for manual") {
    $options[:type] = :html
  }
  x.on("-H", "--full-html", "Generate complete HTML page") {
    $options[:type] = :fullhtml
  }
  x.on("-i", "--inline-style", "Embed CSS stylesheet inline (implies -H)") {
    $options[:type] = :fullhtml
    $options[:inline] = true
  }
end

begin
  opts.parse!(ARGV)
rescue
  opt_error = $!
end

if ARGV.length != 1 or !$options[:type]
  $stderr.puts opt_error if opt_error
  $stderr.puts opts
  $stderr.puts "One of -p, -m, -h, -H or -i must be specified"
  $stderr.puts "e.g. #$0 -p ../config/system/profile/target-profile.yaml"
  exit 1
end

begin
  hash = YAML::load_file(ARGV[0])
rescue => e
  # Just to make the errors less ugly
  $stderr.puts "#{$0}: #{e}"
  exit 2
end

if $options[:type] == :torus
  driver = Driver::TorusProfile.new
elsif $options[:type] == :mkadmin
  driver = Driver::MKAdmin.new
elsif $options[:type] == :html
  driver = Driver::HTML.new
elsif $options[:type] == :fullhtml
  driver = Driver::FullHTML.new
else
  raise "How did that happen?"
end

profile = hash['profile'] or raise "no profile"
driver.profile(profile['name'], profile['description'])
if $options[:verbose]
  $stderr.puts "Name: #{profile['name']}"
  $stderr.puts "Description: #{profile['description']}"
end

# I just LOVE Ruby ...
profile['sections'].map { |key, value|
  [ key, value ]
}.sort { |a, b|
  a[1]['order'] <=> b[1]['order']
}.each do |ref|
  sectionName = ref[0]
  section = ref[1]
  fieldList = section['fields'].map { |key, value|
    [ key, value ]
  }.sort { |a, b|
    ao = a[1]['order']
    bo = b[1]['order']
    (!ao && !bo ? a[0] <=> b[0] :
     !ao ? 1 :
     !bo ? -1 :
     ao <=> bo)
  }

  driver.section(sectionName, section['order'], fieldList)

  fieldList.each do |ref|
    fieldName = ref[0]
    field = ref[1]
    driver.field(fieldName, field)
  end
end

driver.end()
