#!/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)
        %!#{src}!
      when 'v'
      # html_escape(src)
        %!''#{src}''!
      when 's'
      # html_escape(src)
        %!'''#{src}'''!
      when 'href'
      # html_escape(src)
        href, acap = space_split(src)
        if acap == '.' then acap = File.basename(href) end
        if %r%^\.\/% =~ href
          href = 'http://www.moriq.com/apollo/tutorial/'<<href.sub(/^\.\//,'')
        end
        if %r%^img/% =~ href
          href = 'http://www.moriq.com/apollo/tutorial/'<<href
        end
        %![[#{acap}|#{href}]]!
      when 'name'
      # html_escape(src)
        name, acap = space_split(src)
        %![[#{acap}|#{name}]]!
      when 'i'
        dirname = File.dirname(@srcname)
        path = File.join(dirname, src.strip)
        begin
          File.open(path).read
        rescue Exception
          "(fail to eval #{src})"
        end
      when 'e'
        begin
          eval(src.strip)
        rescue Exception
          "(fail to eval #{src})"
        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)
          %![[#{c}]]##{m}!
        end
      else
        modules = c.split(/::/)
        
      # dirname = File.dirname(@srcname)
      # dirs = dirname.split(/\//)
      # dirslev = dirs.size-1
        
        modline = modules.join('-')
      # 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?
          %![[#{modline}]]!
        else
        # name = Opname.op2name(m)
        # html_escape(m)
        # html_escape(name)
          %![[#{modline}]]##{m}!
        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"
      @text<<"*"*lev<<" #{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"
      @text<<"#"*lev<<" #{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!
        @text<<"!!! "<<%!#{cap.split(/::/).join('-')}\n!
      else
      # @text<<'  '*lev<<%!<dt>#{cap}\n!
        @text<<": "<<%!#{cap}!
        @text<<": "
      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<<"\n" if [:item, :enum, :text].include? @line_env
        when 1
      #   @text<<"<dd>"
        else
          @text<<"\n" 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
      marker = '!'*sec
      cap = $'.chomp.strip
      levdown 0
      @text<<"#{marker} #{cap.split(/::/).join('-')}\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<<"\t"<<text
    else
      @text<<"\t"<<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
    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<<"\n" if [:item, :enum, :text].include? @line_env
        when 1
      #   @text<<"<dd>"
        else
          @text<<"\n" if lev > 0
        end
        eval "parse_#{kind}"
        @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, 'hiki').sub(/^(\.\/)+/, '')
    parse
  # nav_apply
    f = open(@outpath, 'w')
    if @title.empty? then @title = @capc end
  # f.print ...
  # f.print header
    f.print @text
    if ! @author.empty? || ! @mailto.empty?
      if @author.empty? then @author = @mailto end
  #   f.print ...
    end
  # f.print footer
  # f.print ...
    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
