#!/usr/bin/env ruby
# -*- ruby -*-

GC.disable
require "rbconfig"

require "rabbit/console"
require "rabbit/source"
require "rabbit/renderer"
require "rabbit/front"

def parse_args(args=ARGV, logger=nil)
  Rabbit::Console.parse!(args, logger) do |opts, options|
    options.theme = "default"
    options.theme_specified = false
    options.base = nil
    options.source_type = Rabbit::Source::File
    options.full_screen = false
    options.index_mode = false
    options.geometry = nil
    options.width = 800
    options.height = 600
    options.paper_width = nil
    options.paper_height = nil
    options.save_as_image = false
    options.saved_image_base_name = nil
    options.saved_image_type = "png"
    options.output_html = false
    options.output_index_html = false
    options.rss_base_uri = nil
    options.encoding = nil
    options.print = false
    options.print_out_filename = nil
    options.slides_per_page = 1
    options.draw_scaled_image = true
    options.margin_left = nil
    options.margin_right = nil
    options.margin_top = nil
    options.margin_bottom = nil
    options.page_margin_left = nil
    options.page_margin_right = nil
    options.page_margin_top = nil
    options.page_margin_bottom = nil
    options.use_druby = true
    options.druby_uri = "druby://:10101"
    options.output_druby_uri = false
    options.use_soap = false
    options.soap_host = "0.0.0.0"
    options.soap_port = 10103
    options.use_xmlrpc = false
    options.xmlrpc_host = "0.0.0.0"
    options.xmlrpc_port = 10104
    options.server = false
    options.default_public_level = "strict"
    options.public_level = nil
    options.comment_source = nil
    options.comment_encoding = nil
    options.migemo_dictionary_search_path = [
      File.join(Config::CONFIG["prefix"], "share"),
      File.join("", "usr", "local", "share"),
      File.join("", "usr", "share"),
    ].uniq
    options.migemo_dictionary_name = "migemo-dict"
    options.use_gl = false
    options.show_native_window_id = false
    options.keep_above = false
    options.source_filename = nil


    opts.banner = "#{opts.banner} [SOURCE_INFOS]"

    opts.category _("Theme")

    opts.on("-I", "--include=PATH",
            _("Add [PATH] to load path.")) do |path|
      $LOAD_PATH.unshift(path)
    end
    
    opts.on("-t", "--theme=THEME",
            _("Use [THEME] as theme."),
            "(#{options.theme})") do |theme|
      options.theme = theme
      options.theme_specified = true
    end


    opts.category _("Source")

    source_type_names = Rabbit::Source.types.collect do |x|
      Rabbit::Console.get_last_name(x).downcase
    end
    source_type_descs = Rabbit::Source.types.collect do |x|
      message = _("When select %s\nspecify %s\nas [SOURCE_INFOS].")
      type = Rabbit::Console.get_last_name(x)
      message = message % [type, _(x.initial_args_description)]
      message.split(/\n/) + [" "]
    end.flatten
    opts.on("-T", "--type=TYPE",
            source_type_names,
            _("Specify source type as [TYPE]."),
            _("Select from [%s].") % source_type_names.join(', '),
            _("Note: case insensitive."),
            "(#{Rabbit::Console.get_last_name(options.source_type)})",
            _("(ARGV: if no filename is given)"),
            " ",
            *source_type_descs) do |source_type|
      options.source_type = Rabbit::Source.types.find do |t|
        Rabbit::Console.get_last_name(t).downcase == source_type.downcase
      end
    end

    opts.on("-e", "--encoding=ENCODING",
            _("Specify source encoding as [ENCODING]."),
            _("(auto)")) do |encoding|
      options.encoding = encoding
    end

    opts.on("-B", "--base=BASE",
            _("Specify base URI or path of source as [BASE]."),
            _("(auto)")) do |base|
      options.base = base
    end


    opts.category _("Initial state")

    opts.on("-f", "--[no-]full-screen",
            _("Toggle full screen mode."),
            "(#{options.full_screen ? 'on' : 'off'})") do |bool|
      options.full_screen = bool
    end

    opts.on("--[no-]index-mode",
            _("Toggle index mode."),
            "(#{options.index_mode ? 'on' : 'off'})") do |bool|
      options.index_mode = bool
    end


    opts.category _("Size")

    opts.on("-g", "--geometry=GEOMETRY",
            _("Set window geometry [GEOMETRY]."),
            _("Format: WIDTHxHEIGHT+X+Y"),
            "(#{options.geometry.inspect})") do |geometry|
      options.geometry = geometry
    end

    opts.on("-w", "--width=WIDTH",
            Integer,
            _("Set window width to [WIDTH]."),
            "(#{options.width})") do |width|
      options.width = width
    end

    opts.on("-h", "--height=HEIGHT",
            Integer,
            _("Set window height to [HEIGHT]."),
            "(#{options.height})") do |height|
      options.height = height
    end

    message = _("Set window width and height to\n" \
                "[WIDTH] and [HEIGHT].")
    message = message.split(/\n/) + ["(#{options.width},#{options.height})"]
    opts.on("-S", "--size=WIDTH,HEIGHT",
            Array,
            *message) do |size|
      width, height = size.collect{|x| Integer(x)}
      options.width = width
      options.height = height
    end


    opts.category _("Save")

    opts.on("-s", "--save-as-image",
            _("Save as image and exit.")) do
      options.save_as_image = true
    end

    opts.on("-i", "--saved-image-type=TYPE",
            _("Specify saved image type as [TYPE]."),
            "(#{options.saved_image_type})") do |t|
      options.saved_image_type = t
    end

    opts.on("-b", "--saved-image-base-name=BASE_NAME",
            "--saved-image-basename=BASE_NAME",
            _("Specify saved image base name as [BASE_NAME]."),
            "(" + _("Title of slide") + ")") do |b|
      options.saved_image_base_name = b
    end

    opts.on("--[no-]output-html",
            _("Output HTML for viewing saved images."),
            "(#{options.output_html})") do |bool|
      options.output_html = bool
    end

    opts.on("--[no-]output-index-html",
            _("Output index HTML for navigating slides."),
            "(#{options.output_index_html})") do |bool|
      options.output_index_html = bool
    end

    opts.on("--rss-base-uri=URI",
            _("Specify base URI of RSS as [URI]."),
            _("RSS is generated only when HTML is output."),
            "(#{options.rss_base_uri})") do |uri|
      options.rss_base_uri = uri
    end

    opts.on("--source-filename=FILENAME",
            _("Specify source filenam as [FILENAME]."),
            "(#{options.source_filename})") do |filename|
      options.source_filename = filename
    end

    opts.category _("Print")

    opts.on("-p", "--print",
            _("Print and exit.")) do
      options.print = true
    end

    opts.on("-o", "--output-filename=FILENAME",
            _("Specify printed out filename as [FILENAME]."),
            "(\#{%s}.pdf)" % _("Title of slide")) do |f|
      options.print_out_filename = f
    end

    opts.on("--slides-per-page=SLIDES",
            Integer,
            _("Set slides per page."),
            "(1)") do |slides|
      options.slides_per_page = slides
    end

    opts.on("--[no-]draw-scaled-image",
            _("Draw scaled image."),
            _("Better look for displaying but lesser look for printing."),
            "(#{options.draw_scaled_image})") do |boolean|
      options.draw_scaled_image = boolean
    end

    opts.category _("Paper")

    opts.on("--paper-width=WIDTH",
            Integer,
            _("Set paper width to [WIDTH] Pt."),
            _("(landscape A4 width)")) do |width|
      options.paper_width = width
    end

    opts.on("--paper-height=HEIGHT",
            Integer,
            _("Set paper height to [HEIGHT] Pt."),
            _("(landscape A4 height)")) do |height|
      options.paper_height = height
    end

    message = _("Set paper width and height to\n" \
                "[WIDTH] Pt and [HEIGHT] Pt.")
    message = message.split(/\n/) + [_("(landscape A4 size)")]
    opts.on("--paper-size=WIDTH,HEIGHT",
            Array,
            *message) do |size|
      width, height = size.collect{|x| Integer(x)}
      options.paper_width = width
      options.paper_height = height
    end


    opts.category _("Margin")

    opts.on("--margin-left=MARGIN",
            Integer,
            _("Set left margin for slides per page mode print."),
            _("(auto)")) do |margin|
      options.margin_left = margin
    end

    opts.on("--margin-right=MARGIN",
            Integer,
            _("Set right margin for slides per page mode print."),
            _("(auto)")) do |margin|
      options.margin_right = margin
    end

    opts.on("--margin-top=MARGIN",
            Integer,
            _("Set top margin for slides per page mode print."),
            _("(auto)")) do |margin|
      options.margin_top = margin
    end

    opts.on("--margin-bottom=MARGIN",
            Integer,
            _("Set bottom margin for slides per page mode print."),
            _("(auto)")) do |margin|
      options.margin_bottom = margin
    end

    margin1 = _("[ALL]")
    margin2 = _("[TOP_BOTTOM],[LEFT_RIGHT]")
    margin3 = _("[TOP],[LEFT_RIGHT],[BOTTOM]")
    margin4 = _("[TOP],[RIGHT],[BOTTOM],[LEFT]")
    opts.on("--margin={#{margin1}|#{margin2}|#{margin3}|#{margin4}}",
            Array,
            _("Set margin for slides per page mode print.")) do |margins|
      begin
        top, right, bottom, left = Rabbit::Utils.parse_four_way(margins)
        options.margin_top = top
        options.margin_right = right
        options.margin_bottom = bottom
        options.margin_left = left
      rescue ArgumentError
        raise OptionParser::InvalidArgument.new(margins)
      end
    end

    opts.on("--page-margin-left=MARGIN",
            Integer,
            _("Set left page margin."),
            _("(auto)")) do |margin|
      options.page_margin_left = margin
    end

    opts.on("--page-margin-right=MARGIN",
            Integer,
            _("Set right page margin."),
            _("(auto)")) do |margin|
      options.page_margin_right = margin
    end

    opts.on("--page-margin-top=MARGIN",
            Integer,
            _("Set top page margin."),
            _("(auto)")) do |margin|
      options.page_margin_top = margin
    end

    opts.on("--page-margin-bottom=MARGIN",
            Integer,
            _("Set bottom page margin."),
            _("(auto)")) do |margin|
      options.page_margin_bottom = margin
    end

    opts.on("--page-margin={#{margin1}|#{margin2}|#{margin3}|#{margin4}}",
            Array,
            _("Set page margin.")) do |margins|
      begin
        top, right, bottom, left = Utils.parse_four_dimensions(margins)
        options.page_margin_top = top
        options.page_margin_right = right
        options.page_margin_bottom = bottom
        options.page_margin_left = left
      rescue ArgumentError
        raise OptionParser::InvalidArgument.new(margins)
      end
    end

    opts.category _("dRuby")
    
    opts.on("--[no-]use-druby",
            _("Specify whether to use dRuby."),
            "(#{options.use_druby})") do |bool|
      options.use_druby = bool
    end
    
    opts.on("--druby-uri=URI",
            _("Specify dRuby URI."),
            "(#{options.druby_uri})") do |uri|
      options.druby_uri = uri if uri
    end
    
    opts.on("--[no-]output-druby-uri",
            _("Specify whether to output dRuby URI."),
            "(#{options.output_druby_uri})") do |bool|
      options.output_druby_uri = bool
    end
    
    opts.category _("SOAP")
    
    opts.on("--[no-]use-soap",
            _("Specify whether to use SOAP."),
            "(#{options.use_soap})") do |bool|
      options.use_soap = bool
    end
    
    opts.on("--soap-host=HOST",
            _("Specify SOAP host as [HOST]."),
            "(#{options.soap_host})") do |port|
      begin
        options.soap_host = host
      end
    end
    
    opts.on("--soap-port=PORT",
            _("Specify SOAP port as [PORT]."),
            "(#{options.soap_port})") do |port|
      begin
        options.soap_port = Integer(port) if port
      rescue ArgumentError
        raise OptionParser::InvalidArgument.new(port)
      end
    end
    
    opts.category _("XML-RPC")
    
    opts.on("--[no-]use-xmlrpc",
            _("Specify whether to use XML-RPC."),
            "(#{options.use_xmlrpc})") do |bool|
      options.use_xmlrpc = bool
    end
    
    opts.on("--xmlrpc-host=HOST",
            _("Specify XML-RPC host as [HOST]."),
            "(#{options.xmlrpc_host})") do |port|
      begin
        options.xmlrpc_host = host
      end
    end
    
    opts.on("--xmlrpc-port=PORT",
            _("Specify XML-RPC port as [PORT]."),
            "(#{options.xmlrpc_port})") do |port|
      begin
        options.xmlrpc_port = Integer(port) if port
      rescue ArgumentError
        raise OptionParser::InvalidArgument.new(port)
      end
    end
    
    opts.category _("Server")
    
    opts.on("--[no-]server",
            _("Specify whether to run as server."),
            "(#{options.server})") do |bool|
      options.server = bool
    end

    opts.category _("Public level")

    levels = Rabbit::Front::PublicLevel.constants.sort_by do |const|
      Rabbit::Front::PublicLevel.const_get(const)
    end.collect do |const|
      const.to_s.downcase.gsub(/_/, "-")
    end
    messages = [_("Specify public level.")]
    messages << _("Select from the following:")
    messages << "["
    messages << "  "
    levels[0..-2].each do |level|
      messages.last << "#{level}, "
      messages << "  " if messages.last.size > 30
    end
    messages.last << levels.last
    messages << "]"
    messages << _("(#{options.default_public_level})")
    opts.on("--public-level=LEVEL", levels, *messages) do |level|
      options.public_level = level
    end

    opts.category _("Comment")
    
    opts.on("--comment-source=FILE",
            _("Specify initial comment source."),
            _("(default source)")) do |name|
      options.comment_source = name
    end
    
    opts.on("--comment-encoding=ENCODING",
            _("Specify comment source encoding."),
            _("(auto)")) do |encoding|
      options.comment_encoding = encoding
    end

    opts.category _("Migemo")

    search_path = options.migemo_dictionary_search_path.join(', ')
    opts.on("--migemo-dictionary-search-path=PATH1,PATH2,...",
            Array,
            _("Specify search paths for Migemo static dictionary."),
            _("(#{search_path})")) do |path|
      options.migemo_dictionary_search_path = path
    end

    opts.on("--migemo-dictionary-name=NAME",
            Array,
            _("Specify static dictionary name for Migemo."),
            _("(#{options.migemo_dictionary_name})")) do |name|
      options.migemo_dictionary_name = name
    end

    opts.category _("3D")

    opts.on("--[no-]use-gl",
            _("Specify whether to use OpenGL if available."),
            "(#{options.use_gl})") do |bool|
      options.use_gl = bool
    end

    opts.category _("Display")

    opts.on("--[no-]keep-above",
            _("Specify whether to keep above window."),
            "(#{options.keep_above})") do |bool|
      options.keep_above = bool
    end

    opts.category _("Others")

    opts.on("--[no-]show-native-window-id",
            _("Show a native window ID of the Rabbit window if available."),
            _("e.g. The ID is the ID of X resource on X window system."),
            "(#{options.show_native_window_id})") do |bool|
      options.show_native_window_id = bool
    end
  end
end

def make_canvas(options, logger, renderer)
  args = [
    logger, renderer,
    options.comment_source, options.comment_encoding,
  ]
  Rabbit::Canvas.new(*args)
end

def make_source(options, argv, logger)
  if options.source_type == Rabbit::Source::File and argv.empty?
    options.source_type = Rabbit::Source::ARGF
  end
  if options.source_type == Rabbit::Source::ARGF
    infos = [ARGF]
  else
    infos = argv
  end
  options.source_type.new(options.encoding, logger, *infos)
end

def make_front(canvas, options)
  level = options.public_level
  if options.source_type == Rabbit::Source::Memory or options.server
    level ||= "all"
  end
  level ||= options.default_public_level
  level = level.gsub(/-/, "_").upcase
  canvas.front(Rabbit::Front::PublicLevel.const_get(level))
end

def apply_theme_if_need(target, options)
  target.apply_theme(options.theme) if options.theme_specified
end

def parse(target, options, logger, background=false)
  source = make_source(options, ARGV, logger)
  source.base = options.base if options.base
  if background
    callback = Rabbit::Utils.process_pending_events_proc
  else
    callback = nil
  end
  target.parse(source, callback)
end

def setup_image_info(target, options)
  target.saved_image_type = options.saved_image_type
  target.saved_image_base_name = options.saved_image_base_name
  target.output_html = options.output_html
  target.output_index_html = options.output_index_html
  target.rss_base_uri = options.rss_base_uri
  target.source_filename = options.source_filename
end

def setup_print_info(target, options)
  target.filename = options.print_out_filename
  target.slides_per_page = options.slides_per_page
  target.draw_scaled_image = options.draw_scaled_image
end

def setup_size(target, options)
  target.width = options.width
  target.height = options.height
end

def setup_paper_size(target, options)
  target.paper_width = options.paper_width
  target.paper_height = options.paper_height
  target.page_margin_left = options.page_margin_left
  target.page_margin_right = options.page_margin_right
  target.page_margin_top = options.page_margin_top
  target.page_margin_bottom = options.page_margin_bottom
  target.margin_left = options.margin_left
  target.margin_right = options.margin_right
  target.margin_top = options.margin_top
  target.margin_bottom = options.margin_bottom
end

def setup_migemo_info(target, options)
  target.migemo_dictionary_search_path = options.migemo_dictionary_search_path
  target.migemo_dictionary_name = options.migemo_dictionary_name
end

def setup_3d_info(target, options)
  target.use_gl = options.use_gl
end

def setup_druby(front, options, logger)
  require "drb/drb"
  begin
    DRb.start_service(options.druby_uri, front)
    logger.info(DRb.uri) if options.output_druby_uri
  rescue SocketError
    logger.error($!)
  rescue Errno::EADDRINUSE
    logger.error(_("dRuby URI <%s> is in use.") % options.druby_uri)
  end
end

def setup_soap(front, options, logger)
  require "rabbit/soap/server"
  thread = nil
  
  begin
    config = {
      :BindAddress => options.soap_host,
      :Port => options.soap_port,
      :AddressFamily => Socket::AF_INET,
      :Logger => logger,
    }
    server = Rabbit::SOAP::Server.new(front, config)
    prev = trap(:INT) {server.shutdown; trap(:INT, prev)}
    thread = Thread.new {server.start}
  rescue Errno::EADDRINUSE
    logger.error(_("port <%s> for SOAP is in use.") % options.soap_port)
  end

  thread
end

def setup_xmlrpc(front, options, logger)
  require "rabbit/xmlrpc/server"
  thread = nil
  
  begin
    config = {
      :BindAddress => options.xmlrpc_host,
      :Port => options.xmlrpc_port,
      :AddressFamily => Socket::AF_INET,
      :Logger => logger,
    }
    server = Rabbit::XMLRPC::Server.new(front, config)
    prev = trap(:INT) {server.shutdown; trap(:INT, prev)}
    thread = Thread.new {server.start}
  rescue Errno::EADDRINUSE
    logger.error(_("port <%s> for XML-RPC is in use.") % options.xmlrpc_port)
  end

  thread
end

def do_print(options, logger)
  renderer = Rabbit::Renderer.printable_renderer(options.slides_per_page)
  canvas = make_canvas(options, logger, renderer)
  setup_paper_size(canvas, options)
  setup_print_info(canvas, options)
  setup_3d_info(canvas, options)
  apply_theme_if_need(canvas, options)
  parse(canvas, options, logger)
  canvas.print
  canvas.quit
rescue Rabbit::NoPrintSupportError
  logger.error($!.message)
end

def do_save_as_image(options, logger)
  Rabbit::Renderer::Pixmap.init
  Rabbit.gui_init

  canvas = make_canvas(options, logger, Rabbit::Renderer::Pixmap)
  setup_size(canvas, options)
  setup_image_info(canvas, options)
  setup_print_info(canvas, options)
  setup_paper_size(canvas, options)
  setup_3d_info(canvas, options)
  apply_theme_if_need(canvas, options)
  parse(canvas, options, logger)
  canvas.activate("ToggleIndexMode") if options.index_mode
  canvas.save_as_image
  canvas.quit
end

def do_display(options, logger)
  Rabbit::Renderer::Display.init
  Rabbit.gui_init

  canvas = make_canvas(options, logger, Rabbit::Renderer::Display)
  frame = Rabbit::Frame.new(logger, canvas)
  frame.geometry = options.geometry
  frame.force_keep_above = options.keep_above
  setup_paper_size(canvas, options)
  setup_image_info(canvas, options)
  setup_print_info(canvas, options)
  setup_migemo_info(canvas, options)
  setup_3d_info(canvas, options)
  frame.init_gui(options.width, options.height, true)
  frame.fullscreen if options.full_screen
  native_window = frame.window.window
  if options.show_native_window_id and native_window.respond_to?(:xid)
    logger.info(_("Window ID: %d") % native_window.xid)
  end
  apply_theme_if_need(frame, options)
  parse(frame, options, logger, !Rabbit::Utils.windows?)
  canvas.activate("ToggleIndexMode") if options.index_mode

  front = make_front(canvas, options)
  setup_druby(front, options, logger) if options.use_druby
  setup_soap(front, options, logger) if options.use_soap
  setup_xmlrpc(front, options, logger) if options.use_xmlrpc
  
  Gtk.main
end

def do_server(options, logger)
  Rabbit.gui_init
  
  # GLib::Log.cancel_handler
  # GLib::Log.set_handler(nil, GLib::Log::LEVEL_ERROR)
  
  canvas = make_canvas(options, logger, Rabbit::Renderer::Pixmap)
  setup_size(canvas, options)
  setup_paper_size(canvas, options)
  setup_image_info(canvas, options)
  setup_print_info(canvas, options)
  setup_3d_info(canvas, options)
  apply_theme_if_need(canvas, options)
  parse(canvas, options, logger)

  soap_server_thread = nil
  xmlrpc_server_thread = nil
  
  front = make_front(canvas, options)
  setup_druby(front, options, logger) if options.use_druby
  if options.use_soap
    soap_server_thread = setup_soap(front, options, logger)
  end
  if options.use_xmlrpc
    xmlrpc_server_thread = setup_xmlrpc(front, options, logger)
  end

  soap_server_thread.join if soap_server_thread
  xmlrpc_server_thread.join if xmlrpc_server_thread
  if options.use_druby
    prev = trap(:INT) do
      logger.info(_("going to shutdown..."))
      DRb.thread.exit
      logger.info(_("DRb.thread done."))
      trap(:INT, prev)
    end
    DRb.thread.join
  end
end

def main
  options, logger = parse_args

  require "rabbit/canvas"
  GC.enable

  if options.save_as_image
    do_save_as_image(options, logger)
  elsif options.print
    do_print(options, logger)
  elsif options.server
    do_server(options, logger)
  else
    do_display(options, logger)
  end
end

main
