# We generate XHTML to display MathML chiefly.

require 'ftools'

require 'rdoc/options'
require 'rdoc/template'
require 'rdoc/generators/html_generator'
require 'rdoc/markup/simple_markup/mathml_wrapper'

module Generators


  #
  #<b>Note that Japanese and English are described in parallel.</b>
  #
  #== TeX の数式を MathML に変換
  #
  #TeX で記述された数式を MathML に変換します.
  #インラインで表示したい場合, TeX の数式を以下のように $ ... $ でくくって
  #記述してください. $ の
  #前後には半角空白を一文字以上入れて下さい.
  #
  #  インラインで表示する数式は $ f(x) = x^2 + 1 $ のように記述します.
  #  ($ の前後に空白をお忘れなく).
  #
  #ブロックで表示する場合, 以下のように \[ と
  #\] とでくくって記述してください. \[, \] は必ず行頭に記述してください.
  #
  #  \[
  #     \sum_{i=1}^nf_n(x)
  #  \]
  #
  #TeX の数式から MathML 変換には
  #<b>Ruby 用 MathML ライブラリのバージョン 0.5</b> を使用しています.
  #このライブラリは{ひらくの工房}[http://www.hinet.mydns.jp/~hiraku/]
  #にて公開されています. 使用できる TeX コマンドの詳細に関しても
  #こちらのサイトを参照してください.
  #
  #作成されたドキュメントを閲覧する際には MathML に対応した
  #ブラウザを使用する必要が
  #あります. {MathML 日本語情報}[http://washitake.com/MathML/] 
  #や {MathML Software - Browsers}[http://www.w3.org/Math/Software/mathml_software_cat_browsers.html]
  #などを参照してください.
  #
  #
  #== TeX is converted to MathML
  #
  #TeX formula is converted to MathML.
  #When inline display, TeX formula should be bundled by $ ... $
  #as follows. 
  #One or more normal-width blank is necessary before and behind "$".
  #
  #  Inline formula is $ f(x) = x^2 + 1 $ .
  #
  #When block display, TeX formula should be bundled by \[ ... \]
  #as follows. 
  #Describe \[ and \] at the head of line.
  #
  #  \[
  #     \sum_{i=1}^nf_n(x)
  #  \]
  #
  #<b>MathML library for Ruby version 0.5</b> is needed to
  #convert TeX formula to MathML.
  #This library is available from {Bottega of Hiraku (JAPANESE only)}[http://www.hinet.mydns.jp/~hiraku/].
  #See this site about available TeX commands.
  #
  #When you browse generated documents, you need to use 
  #browers that can handle MathML.
  #See {MathML Software - Browsers}[http://www.w3.org/Math/Software/mathml_software_cat_browsers.html], etc.
  #
  #
  class TexParser < HyperlinkHtml

    def initialize(*args)
      super(*args)
    end

    def file_location
      if @context.context.parent
        class_or_method = @context.context.name
      end
      context = @context.context
      while context.parent
        context = context.parent
      end
      file_name = context.file_relative_name

      location = file_name
      location += "#"+class_or_method if class_or_method
    end

    # TEXINLINE pattern $...$ is converted to MathML format
    # when --mathml option is given.
    #
    def handle_special_TEXINLINE(special)
      text = special.text
      return text unless Options.instance.mathml
      raw_text = text.scan(/^.*?\$/).to_s.sub(/\$$/, '')
      text.sub!(/^.*?\$/, '')
      text.sub!(/\$$/, '')
      tex = MathMLWrapper.new
      mathml, stat = tex.parse(text)
      if !stat.zero?
        $stderr.puts "Warning: in #{file_location}, following TeX commands can not be converted to MathML\n\n",
        "    #{text}\n\n"
      end
      return raw_text + mathml
    end

    # TEXINLINEDELIMITER pattern "\$" is converted to single dollar "$"
    # when --mathml option is given.
    #
    def handle_special_TEXINLINEDELIMITER(special)
      text = special.text
      return text unless Options.instance.mathml
      return text.gsub(/\\\$/, '$')
    end

    # TEXBLOCK pattern \[...\] is converted to MathML format
    # when --mathml option is given.
    #
    def handle_special_TEXBLOCK(special)
      text = special.text
      return text unless Options.instance.mathml
      text.sub!(/^\\\[/, '')
      text.sub!(/\\\]$/, '')
      tex = MathMLWrapper.new
      mathml, stat = tex.parse(text, true)
      if !stat.zero?
        $stderr.puts "Warning: in #{file_location}, following TeX commands can not be converted to MathML\n\n",
        "    #{text}\n\n"
      end
      return mathml
    end

  end

  #####################################################################
  #
  # Handle common markup tasks for the various Html classes
  #

  module MarkUp

    # This is almost a copy of the markup method in html_generator.
    # This method markup $ .... $ and \[ ... \] as tex format.

    def markup(str, remove_para=false)
      return '' unless str
      unless defined? @markup
        @markup = SM::SimpleMarkup.new

        # class names, variable names, or instance variables
        @markup.add_special(/(
                               \b\w+(::\w+)*[\.\#]\w+(\([\.\w+\*\/\+\-\=\<\>]+\))?  # A::B.meth(**) (for operator in Fortran95)
                             | \#\w+(\([.\w\*\/\+\-\=\<\>]+\))?  #  meth(**) (for operator in Fortran95)
                             | \b([A-Z]\w*(::\w+)*[.\#]\w+)  #    A::B.meth
                             | \b([A-Z]\w+(::\w+)*)       #    A::B..
                             | \#\w+[!?=]?                #    #meth_name 
                             | \b\w+([_\/\.]+\w+)*[!?=]?  #    meth_name
                             )/x, 
                            :CROSSREF)

        # file names
        @markup.add_special(/(
                               \b(\w[\w\#\/\.\-\~\:]*[!?=]?) # file_name
                             | \b(\w[\w\#\/\.\-\~\:]*(\([\.\w+\*\/\+\-\=\<\>]+\))?)
                             )/x, 
                            :CROSSREFFILE)

        # external hyperlinks
        @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)

        # and links of the form  <text>[<url>]
        @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
#        @markup.add_special(/\b(\S+?\[\S+?\.\S+?\])/, :TIDYLINK)

        if Options.instance.mathml
          # TeX inline form
          @markup.add_special(/(\$(.*?)[^\\]\$)/im, :TEXINLINE)

          # TeX inline delimiter
          @markup.add_special(/(\\\$)/im, :TEXINLINEDELIMITER)

          # TeX block form
          @markup.add_special(/(\\\[(.+?)\\\])/im, :TEXBLOCK)
        end

      end
      unless defined? @html_formatter
        @html_formatter = TexParser.new(self.path, self)
      end

      # Convert leading comment markers to spaces, but only
      # if all non-blank lines have them

      if str =~ /^(?>\s*)[^\#]/
        content = str
      else
        content = str.gsub(/^\s*(#+)/)  { $1.tr('#',' ') }
      end

      block_exceptions = []
      if Options.instance.mathml
        block_exceptions << {
          'name'     => :texblockform,
          'start'    => Regexp.new("^\\\\\\["),
          'end'      => Regexp.new("\\\\\\]$"),
          'replaces' => []
        }
        block_exceptions[0]['replaces'] << {
          'from' => Regexp.new("\\\\\\\\"),
          'to'   => "\\\\\\\\\\\\\\\\",
        }
      end

      res = @markup.convert(content, @html_formatter, block_exceptions)
      if remove_para
        res.sub!(/^<p>/, '')
        res.sub!(/<\/p>$/, '')
      end
      res
    end
  end

  class XHTMLGenerator < HTMLGenerator

    def XHTMLGenerator.gen_url(path, target)
      Generators::HTMLGenerator.gen_url(path, target)
    end
    def XHTMLGenerator.for(options)
      AllReferences::reset
      HtmlMethod::reset

      if options.all_one_file
        XHTMLGeneratorInOne.new(options)
      else
        XHTMLGenerator.new(options)
      end
    end

    def generate(toplevels)
      super(toplevels)
      copy_xsls
    end

    private

    def build_indices
      @toplevels.each do |toplevel|
        @files << XHtmlFile.new(toplevel, @options, FILE_DIR)
      end

      RDoc::TopLevel.all_classes_and_modules.each do |cls|
        build_class_list(cls, @files[0], CLASS_DIR)
      end
    end

    def build_class_list(from, html_file, class_dir)
      @classes << XHtmlClass.new(from, html_file, class_dir, @options)
      from.each_classmodule do |mod|
        build_class_list(mod, html_file, class_dir)
      end
    end

    def gen_method_index
      gen_an_index(XHtmlMethod.all_methods, 'Methods', 
                   RDoc::Page::METHOD_INDEX,
                   "fr_method_index.html")
    end

    def gen_an_index(collection, title, template, filename)
      template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template)
      res = []
      collection.sort.each do |f|
        if f.document_self
          res << { "href" => f.path, "name" => f.index_name }
        end
      end

      values = {
        "entries"         => res,
        'list_title'      => CGI.escapeHTML(title),
        'index_url'       => main_url,
        'charset'         => @options.charset,
        'style_url'       => style_url('', @options.css),
        'mathml_xsl_url'  => style_url('', "mathml.xsl"),
      }

      File.open(filename, "w") do |f|
        template.write_html_on(f, values)
      end
    end

    def copy_xsls
      xsl_files = ["mathml.xsl", "pmathmlcss.xsl", "ctop.xsl", "pmathml.xsl"]
      xsl_dir = "rdoc/generators/template/xhtml"
      hit = 0
      $LOAD_PATH.each{ |path|
        hit = 0
        xsl_files.each{ |file|
          hit += 1 if File.exist?(File.join(path, xsl_dir, file))
        }
        if hit >= 4
          xsl_files.each{ |file|
            File.copy(File.join(path, xsl_dir, file), "./")
          }
          break
        else
          hit = 0
        end
      }
      if hit < 4
        $stderr.puts "Couldn't find xsl files (#{xsl_files.join(', ')})\n"
        exit
      end
    end

  end

  class XHTMLGeneratorInOne < HTMLGeneratorInOne
    def build_class_list(from, html_file, class_dir)
      @classes << XHtmlClass.new(from, html_file, class_dir, @options)
      from.each_classmodule do |mod|
        build_class_list(mod, html_file, class_dir)
      end
    end

    def build_indices
      @toplevels.each do |toplevel|
        @files << XHtmlFile.new(toplevel, @options, FILE_DIR)
      end

      RDoc::TopLevel.all_classes_and_modules.each do |cls|
        build_class_list(cls, @files[0], CLASS_DIR)
      end
    end

    def gen_method_index
      gen_an_index(XHtmlMethod.all_methods, 'Methods')
    end
  end

  class XHtmlClass < HtmlClass
    def value_hash
      super
      @values["mathml_xsl_url"] = style_url(path, "mathml.xsl")
      @values
    end

    def collect_methods
      list = @context.method_list
      unless @options.show_all
        list = list.find_all {|m| m.visibility == :public || m.visibility == :protected || m.force_documentation }
      end
      @methods = list.collect {|m| XHtmlMethod.new(m, self, @options) }
    end

  end

  class XHtmlFile < HtmlFile
    def value_hash
      super
      @values["mathml_xsl_url"] = style_url(path, "mathml.xsl")
      @values
    end

    def collect_methods
      list = @context.method_list
      unless @options.show_all
        list = list.find_all {|m| m.visibility == :public || m.visibility == :protected || m.force_documentation }
      end
      @methods = list.collect {|m| XHtmlMethod.new(m, self, @options) }
    end

  end

  class XHtmlMethod < HtmlMethod
    def create_source_code_file(code_body)
      template_regexp = Regexp.new("\\." + @options.template + "$")
      meth_path = @html_class.path.sub(template_regexp, '.src')
      File.makedirs(meth_path)
      file_path = File.join(meth_path, @seq) + '.' + @options.template

      template = TemplatePage.new(RDoc::Page::SRC_PAGE)
      File.open(file_path, "w") do |f|
        values = {
          'title'     => CGI.escapeHTML(index_name),
          'code'      => code_body,
          'style_url' => style_url(file_path, @options.css),
          'mathml_xsl_url'  => style_url('', "mathml.xsl"),
          'charset'   => @options.charset
        }
        template.write_html_on(f, values)
      end
      XHTMLGenerator.gen_url(path, file_path)
    end
  end

end
