#!/usr/bin/ruby
# author: YOSHIDA Kazuhiro (moriq)
# mailto: moriq@moriq.com

# require 'opname'
require File.dirname(__FILE__.gsub(/\\/,'/')) + '/../lib/opname'

def html_escape(s)
  s.gsub!( /&/, "&amp;" )
  s.gsub!( /</, "&lt;" )
  s.gsub!( />/, "&gt;" )
  s.gsub!( /"/, "&quot;" )
  s
end

def html_anchor(src, cap)
  %!<a href="#{src}.html">#{cap}</a>!
end

def space_split(src)
  src.strip!
  pos = src.index(' ')
  if pos then [src[0...pos], src[pos+1..-1]] else [src, src] end
end

class Pi

  SURFIX = 'pi'
  UP = {
    :item => "<ul>\n",
    :enum => "<ol>\n",
    :defi => "<dl>\n",
  }
  DOWN = {
    :item => "</ul>\n",
    :enum => "</ol>\n",
    :defi => "</dl>\n",
  }
  def reset
    @lev = 0
    @stack = []
    @line_env = :section
    @title  = ''
    @author = ''
    @mailto = ''
    @header = ''
    @footer = ''
    @text = ''
  end

  def initialize
    reset
  end
  attr :title
  attr :author
  attr :mailto
  attr :header
  attr :footer

  def levdown(lev)
    (@lev-1).downto(lev){|i| @text<<'  '*i<<DOWN[@stack.pop]}
  rescue
  ensure
    @lev = lev
  end

  def levup(lev, kind)
    @lev.upto(lev-1){|i| @text<<'  '*i<<UP[kind]; @stack.push kind}
  rescue
  ensure
    @lev = lev
  end

  def levmove(lev, kind)
    if lev < @lev
      levdown lev
    end
    levdown lev-1 if lev == @lev && @stack[-1] != kind
    levup lev, kind
  end

  def trans_word(line)

    rep0 = []
    line.gsub!( /%%/, "\1" )
    line.gsub!( /%(\w+):(.*?)%/ ) {
      act = $1
      src = $2.gsub(/\001/, '%')
      rep0 <<
      case act
      when 'html'
        src
      when 'n'
        html_escape(src)
        src
      when 'c'
        html_escape(src)
        %!<code>#{src}</code>!
      when 'v'
        html_escape(src)
        %!<var>#{src}</var>!
      when 's'
        html_escape(src)
        %!<strong>#{src}</strong>!
      when 'href'
        html_escape(src)
        href, acap = space_split(src)
        if acap == '.' then acap = File.basename(href) end
        %!<a href="#{href}">#{acap}</a>!
      when 'name'
        html_escape(src)
        name, acap = space_split(src)
        %!<a name="#{name}">#{acap}</a>!
      when 'i'
        dirname = File.dirname(@srcname)
        path = File.join(dirname, src.strip)
        begin
          File.open(path).read
        rescue Exception
          "(fail to open #{path})"
        end
      when 'e'
        begin
          eval(src.strip)
        rescue Exception
          "(fail to eval #{src} in trans_word: #{$!})"
        end
      end
      "\0"
    }
    line.gsub!(/\001/, '%')

    rep1 = []
    line.gsub!( /([\w\d:]+?)?#(#{Opname::PATTERN_METHOD})?/o ) {
      c = $1
      m = $2
      rep1 <<
      if c.nil?
        c = @capc
        html_escape(c)
        if m.nil? || m.empty?
          '#'
        else
          name = Opname.op2name(m)
          html_escape(m)
          html_escape(name)
          %!<a href="#{c}.html##{name}">#{m}</a>!
        end
      else
        modules = c.split(/::/)
        
        dirname = File.dirname(@srcname)
        dirs = dirname.split(/\//)
        dirslev = dirs.size-1
        
        modpath = modules.join('/')
        currdir = '.'
        
        d = '../'*dirslev+'missing'
        0.upto(dirslev) do
          path = File.join(currdir, modpath)
          if test ?f, File.join(dirname, path)+'.'+SURFIX
            d = path
            break
          end
          currdir = '../'+currdir
        end
        
        html_escape(c)
        html_escape(d)
        if m.nil? || m.empty?
          %!<a href="#{d}.html">#{c}</a>!
        else
          name = Opname.op2name(m)
          html_escape(m)
          html_escape(name)
          %!<a href="#{d}.html##{name}">#{c}##{m}</a>!
        end
      end
      "\1"
    }

    html_escape(line)

    line.gsub!(/\001/) { rep1.shift }
    line.gsub!(/\000/) { rep0.shift }

  end

  def trans_line(line)

    /^(  )*/ =~ line
    indent = $&
    string = $'

    lev = (indent.length)/2

    case string
    when /^\* /
      if @line_env == :text
        @text<<'  '*@lev<<"<br>\n"
      end
      @line_env = :item
      lev+= 1
      levmove lev, @line_env
      cap = $'.chomp
      @text<<'  '*lev<<"<li>#{cap}\n"
      @lev = lev
    when /^\+ /
      if @line_env == :text
        @text<<'  '*@lev<<"<br>\n"
      end
      @line_env = :enum
      lev+= 1
      levmove lev, @line_env
      cap = $'.chomp
      @text<<'  '*lev<<"<li>#{cap}\n"
      @lev = lev
    when /^\: /
      if @line_env == :text
        @text<<'  '*@lev<<"<br>\n"
      end
      @line_env = :defi
      lev+= 1
      levmove lev, @line_env
      cap = $'.chomp
      if lev == 1
        name = Opname.op2name(Opname.method_str(cap.strip))
        @text<<'  '*lev<<%!<dt class="method"><a name="#{name}">#{cap}</a>\n!
      else
        @text<<'  '*lev<<%!<dt>#{cap}\n!
      end
      @text<<'  '*lev<<"<dd>\n" # for IE
      @lev = lev
    else
      if /\S/ =~ line
        cap = string.chomp
        cmp = lev <=> @lev
        if cmp == -1
          levdown lev
        else
          levup lev, :defi
        end
        @text<<'  '*lev
        case cmp
        when 0
          @text<<"<br>" if [:item, :enum, :text].include? @line_env
        when 1
          @text<<"<dd>"
        else
          @text<<"<br>" if lev > 0
        end
        @line_env = :text
        @text<<cap
        @lev = lev
      else
        if @line_env == :text
          @text<<'  '*@lev<<"<br>"
        end
      end
      @text<<"\n"
    end
  end

  def parse_line(line)

    if /^----$/ =~ line
      levdown 0
      @line_env = :section
      @text<<"<hr>"
      return
    end

    trans_word(line)

    case line
    when /^header:/
      levdown 0
      _text = @text
      @text = @header
      while line = @srcfile.gets
        break if ~ /^end\b/
        parse_line(line)
      end
      levdown 0
      @text = _text
    when /^footer:/
      levdown 0
      _text = @text
      @text = @footer
      while line = @srcfile.gets
        break if ~ /^end\b/
        parse_line(line)
      end
      levdown 0
      @text = _text
    when /^title:/
      @title = $'.strip
    when /^author:/
      @author = $'.strip
    when /^mailto:/
      @mailto = $'.strip
    when /^(\d+\.)+/
      @line_env = :section
      sec = ($&.length)/2
      cap = $'.chomp.strip
      levdown 0
      @text<<"<h#{sec}>#{cap}</h#{sec}>\n"
    else
      trans_line(line)
    end
  end

  def append_line(line)
    case line
    when /^include:/
      src, *opt = $'.strip.split(/,/)
      begin
        dirname = File.dirname(@srcname)
        text = open(File.join(dirname, src.strip)).read
      rescue
        text = "(fail to include #{src})"
      end
      text.gsub!(/.{76}/, "\\0\\\n") if opt.include? 'wrap'
      html_escape(text) if opt.include? 'escape'
      @text<<text
    else
      @text<<line
    end
  end

  def parse_plain(context)
    @text<<%%<pre class="#{context}">\n%
    while line = @srcfile.gets
      break if /^--/ =~ line
      html_escape(line)
      append_line(line)
    end
    @text<<"</pre>\n"
  end

  def parse_text
    parse_plain('text')
  end

  def parse_ruby
    parse_plain('ruby')
  end

  def parse_code
    parse_plain('code')
  end

  def parse_html
    while line = @srcfile.gets
      break if /^--/ =~ line
      append_line(line)
    end
  end

  def parse_eval
    src=''
    while line = @srcfile.gets
      break if /^--/ =~ line
      src<<line
    end
    begin
      @text<<"#{eval(src)}"
    rescue
      @text<<"(fail to eval #{html_escape(src)} in parse_eval: #{$!})"
    end
  end

  def parse
    while @srcfile.gets
      case $_
      when /^ *--([a-z_]+)/
        kind = $1
        ~ /^(  )*/
        indent = $&
        string = $'
        lev = (indent.length)/2
#       levdown lev
        cmp = lev <=> @lev
        if cmp == -1
          levdown lev
        else
          levup lev, :defi
        end
        @text<<'  '*lev
        case cmp
        when 0
          @text<<"<br>" if [:item, :enum, :text].include? @line_env
        when 1
          @text<<"<dd>"
        else
          @text<<"<br>" if lev > 0
        end
        begin
          eval "parse_#{kind}"
        rescue
          parse_plain(kind)
        end
        @line_env = :heredoc
      else
        parse_line($_)
      end
    end
    levdown 0
  end

  def nav_apply
    return if @srcname.nil?

    dirname = File.dirname(@srcname)
    tree_fn = dirname + '/nav.dat'
    return if !test ?e, tree_fn
    tree = IO.readlines tree_fn

    tlev = []
    tcap = []
    tcap_up = []
    cnt = 0

    tree.each { |i|
      i.chomp!
      /^ */ =~ i
      indent, string = $&, $'
      cap, cap_up = string.split
      lev = indent.length / 2
      tlev[cnt] = lev
      tcap[cnt] = cap
      tcap_up[cnt] = cap_up
      cnt+= 1
    }
    return if cnt == 0
    cur = tcap.rindex(@capc)

    if cur.nil?
      $stderr.print "not found node '#@capc' in nav.dat\n"
      cur = 0
    end

    if tcap_up[cur].nil?
      cnt = cur
      begin
        break if cnt == 0
        cnt-= 1
      end while tlev[cnt] >= tlev[cur]
    end

    top  = tcap.empty? ? 'top' : html_anchor(tcap[0], 'top')
    up   = html_anchor(tcap_up[cur] || tcap[cnt], 'up')
    prev = cur == 0 ? 'prev' : html_anchor(tcap[cur-1], 'prev')
    ncxt = cur == tcap.size-1 ? 'next' : html_anchor(tcap[cur+1], 'next')

    nav = <<EOT
[ #{top} ] [ #{prev} ] [ #{up} ] [ #{ncxt} ]
EOT
    @header[0, 0] = nav
    @footer << nav
  end

  def write(outdir, path)
    $stderr.print path, "\n"
    @srcname = path
    @capc = File.basename(path, '.'<<SURFIX)
    @srcfile = open(path)
    @outpath = File.join(outdir, File.basename(path)).
      sub(/#{SURFIX}$/o, 'html').sub(/^(\.\/)+/, '')
    parse
    nav_apply
    f = open(@outpath, 'w')
    if @title.empty? then @title = @capc end
    f.print <<EOT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html lang="ja">
<head>
<title>#{title}</title>
<link rel=stylesheet type="text/css" href="pi.css">
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis">
</head>

<body>
EOT
    f.print header
    f.print @text
    if ! @author.empty? || ! @mailto.empty?
      if @author.empty? then @author = @mailto end
      f.print <<EOT
<div align="right">
author: <a href="mailto:#{mailto}">#{author}</a>
</div>
EOT
    end
    f.print footer
    f.print <<EOT
</body>
</html>
EOT
    f.close
  end

  def run(outdir, path, entry)
    if ! test ?d, outdir then Dir.mkdir(outdir) end
    if test ?d, path
      outdir = File.join(outdir, entry)
      Dir.foreach(path) do |entry|
        next if /^\./ =~ entry
        run outdir, File.join(path, entry), entry
      end
    elsif /\.#{SURFIX}$/io =~ path
      reset
      write outdir, path
    end
  end

end

if __FILE__ == $0
  outdir, path = ARGV
  Pi.new.run outdir || '.', path || '.', '.'
end
