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

Thread.abort_on_exception = true

require "optparse"
require "ostruct"

require "rabbit/rabbit"
require "rabbit/canvas"
require "rabbit/source"
require "rabbit/logger"
require "rabbit/renderer"

Version = Rabbit::VERSION

def parse(args, logger)
  options = OpenStruct.new
  options.theme = "default"
  options.theme_specified = false
  options.base = nil
  options.source_type = Rabbit::Source::File
  options.full_screen = false
  options.width = 800
  options.height = 600
  options.paper_width = nil
  options.paper_height = nil
  options.save_as_image = false
  options.saved_image_basename = nil
  options.saved_image_type = "png"
  options.output_html = false
  options.encoding = "UTF-8"
  options.locale_dir = nil
  options.print = false
  options.print_out_filename = nil
  options.slides_per_page = 1
  options.margin_left = nil
  options.margin_right = nil
  options.margin_top = nil
  options.margin_bottom = nil
  options.margin_page_left = nil
  options.margin_page_right = nil
  options.margin_page_top = nil
  options.margin_page_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.public_level = Rabbit::Front::PublicLevel::STRICT

  default_logger = logger
  
  get_last_name = Proc.new do |klass|
    klass.name.split("::").last
  end
    
  opts = OptionParser.new do |opts|
    opts.banner = "#{opts.banner} SOURCE_INFOS"

    opts.separator ""

    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.separator ""

    source_type_names = Rabbit::Source.types.collect do |x|
      get_last_name[x].downcase
    end
    source_type_descs = Rabbit::Source.types.collect do |x|
      ["When select #{get_last_name[x]}",
       "specify #{x.initial_args_description}",
       "as SOURCE_INFOS.",
       " "]
    end.flatten
    opts.on("-T", "--type [TYPE]",
            source_type_names,
            "Specify source type as [TYPE].",
            "Select from [#{source_type_names.join(', ')}].",
            "Note: case insensitive.",
            "(#{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|
        get_last_name[t].downcase == source_type.downcase
      end
      if options.source_type == Rabbit::Source::Memory
        options.public_level = Rabbit::Front::PublicLevel::ALL
      end
    end

    opts.on("-e", "--encoding [ENCODING]",
            "Specify source encoding.",
            "(#{options.encoding})") 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.on("-f", "--full-screen",
            "Turn on full screen mode.",
            "(#{options.full_screen})") do
      options.full_screen = true
    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

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

    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-basename [BASENAME]",
            "Specify saved image basename as [BASENAME].",
            "(Title of slide)") do |b|
      options.saved_image_basename = b
    end

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

    if Rabbit::Renderer.printable?
      opts.separator ""

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

      opts.on("-o", "--output-filename [FILENAME]",
              "Specify printed out filename as [FILENAME].",
              "(\#{Title of slide}.ps)") do |f|
        options.print_out_filename = f
      end
      
      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

      opts.on("--paper-size [WIDTH],[HEIGHT]",
              Array,
              "Set paper width and height to",
              "[WIDTH] inch and [HEIGHT] Pt.",
              "(landscape A4 size)") do |size|
        width, height = size.collect{|x| Integer(x)}
        options.paper_width = width
        options.paper_height = height
      end

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

      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 = parse_margins(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("--margin-page-left [MARGIN]",
              Integer,
              "Set left page margin.",
              "(auto)") do |margin|
        options.margin_page_left = margin
      end

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

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

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

      opts.on("--page-margin {#{margin1}|#{margin2}|#{margin3}|#{margin4}}",
              Array,
              "Set page margin.") do |margins|
        begin
          top, right, bottom, left = parse_margins(margins)
          options.margin_page_top = top
          options.margin_page_right = right
          options.margin_page_bottom = bottom
          options.margin_page_left = left
        rescue ArgumentError
          raise OptionParser::InvalidArgument.new(margins)
        end
      end

    end

    opts.separator ""
    
    opts.on("--locale-dir [DIR]",
            "Specify locale dir as [DIR].",
            "(auto)") do |d|
      options.locale_dir = d
    end
    
    logger_type_names = Rabbit::Logger.types.collect do |x|
      get_last_name[x].downcase
    end
    opts.on("--logger-type [TYPE]",
            logger_type_names,
            "Specify logger type as [TYPE].",
            "Select from [#{logger_type_names.join(', ')}].",
            "Note: case insensitive.",
            "(#{get_last_name[logger.class]})") do |logger_type|
      logger_class = Rabbit::Logger.types.find do |t|
        get_last_name[t].downcase == logger_type.downcase
      end
      if logger_class.nil?
        logger = default_logger
        # logger.error("Unknown logger type: #{t}")
      else
        logger = logger_class.new
      end
    end
    
    opts.separator ""
    
    opts.on("--use-druby [BOOL]",
            "Specify whether to use dRuby.",
            "(#{options.use_druby})") do |bool|
      options.use_druby = bool != "false"
    end
    
    opts.on("--druby-uri [URI]",
            "Specify dRuby URI.",
            "(#{options.druby_uri})") do |uri|
      options.druby_uri = uri if uri
    end
    
    opts.on("--output-druby-uri [BOOL]",
            "Specify whether to output dRuby URI.",
            "(#{options.output_druby_uri})") do |bool|
      options.output_druby_uri = bool != "false"
    end
    
    opts.separator ""
    
    opts.on("--use-soap [BOOL]",
            "Specify whether to use SOAP.",
            "(#{options.use_soap})") do |bool|
      options.use_soap = bool != "false"
    end
    
    opts.on("--soap-host [HOST]",
            "Specify SOAP HOST.",
            "(#{options.soap_host})") do |port|
      begin
        options.soap_host = host
      end
    end
    
    opts.on("--soap-port [PORT]",
            "Specify SOAP PORT.",
            "(#{options.soap_port})") do |port|
      begin
        options.soap_port = Integer(port) if port
      rescue ArgumentError
        raise OptionParser::InvalidArgument.new(port)
      end
    end
    
    opts.separator ""
    
    opts.on("--use-xmlrpc [BOOL]",
            "Specify whether to use XML-RPC.",
            "(#{options.use_xmlrpc})") do |bool|
      options.use_xmlrpc = bool != "false"
    end
    
    opts.on("--xmlrpc-host [HOST]",
            "Specify XML-RPC HOST.",
            "(#{options.xmlrpc_host})") do |port|
      begin
        options.xmlrpc_host = host
      end
    end
    
    opts.on("--xmlrpc-port [PORT]",
            "Specify XML-RPC PORT.",
            "(#{options.xmlrpc_port})") do |port|
      begin
        options.xmlrpc_port = Integer(port) if port
      rescue ArgumentError
        raise OptionParser::InvalidArgument.new(port)
      end
    end
    
    opts.separator ""
    
    opts.on("--server [BOOL]",
            "Specify whether to run as server.",
            "(#{options.server})") do |bool|
      options.server = bool != "false"
      if options.server
        options.public_level = Rabbit::Front::PublicLevel::ALL
      end
    end
    
    opts.separator ""
    
    opts.on_tail("--help", "Show this message") do
      if logger.is_a?(Rabbit::Logger::STDERR) and
          default_logger == logger
        print(opts.to_s)
      else
        logger.info(opts.to_s)
      end
      exit
    end
  end

  begin
    opts.parse!(args)
  rescue
    logger.fatal($!.message)
  end

  [options, logger]
end

def parse_margins(margins)
  case margins.size
  when 1
    left = right = top = bottom = Integer(margins.first)
  when 2
    top, left = margins.collect{|x| Integer(x)}
    bottom = top
    right = left
  when 3
    top, left, bottom = margins.collect{|x| Integer(x)}
    right = left
  when 4
    top, right, bottom, left = margins.collect{|x| Integer(x)}
  else
    raise ArgumentError
  end
  [top, right, bottom, left]
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)
  canvas.front(options.public_level)
end

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

def parse_rd(target, options, logger)
  source = make_source(options, ARGV, logger)
  source.base = options.base if options.base
  target.parse_rd(source)
end

def setup_image_info(target, options)
  target.saved_image_type = options.saved_image_type
  target.saved_image_basename = options.saved_image_basename
  target.output_html = options.output_html
end

def setup_print_info(target, options)
  target.filename = options.print_out_filename
  target.slides_per_page = options.slides_per_page
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.margin_page_left = options.margin_page_left
  target.margin_page_right = options.margin_page_right
  target.margin_page_top = options.margin_page_top
  target.margin_page_bottom = options.margin_page_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_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 Errno::EADDRINUSE
    logger.error("#{options.druby_uri} is in use")
  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("#{options.soap_port} is in use")
  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("#{options.xmlrpc_port} is in use")
  end

  thread
end

def do_print(options, logger)
  renderer = Rabbit::Renderer.printable_renderer(options.slides_per_page)
  canvas = Rabbit::Canvas.new(logger, renderer)
  setup_paper_size(canvas, options)
  setup_print_info(canvas, options)
  apply_theme_if_need(canvas, options)
  parse_rd(canvas, options, logger)
  canvas.print
end

def do_save_as_image(options, logger)
  Rabbit.gui_init
  
  canvas = Rabbit::Canvas.new(logger, Rabbit::Renderer::Pixmap)
  setup_image_info(canvas, options)
  setup_size(canvas, options)
  setup_paper_size(canvas, options)
  apply_theme_if_need(canvas, options)
  parse_rd(canvas, options, logger)
  canvas.save_as_image
end

def do_display(options, logger)
  Rabbit.gui_init
  
  canvas = Rabbit::Canvas.new(logger, Rabbit::Renderer::DrawingArea)
  frame = Rabbit::Frame.new(logger, canvas)
  frame.init_gui(options.width, options.height, true)
  setup_paper_size(canvas, options)
  setup_image_info(frame, options)
  setup_print_info(canvas, options)
  apply_theme_if_need(frame, options)
  parse_rd(frame, options, logger)
  frame.fullscreen if options.full_screen

  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
  
  canvas = Rabbit::Canvas.new(logger, Rabbit::Renderer::Pixmap)
  setup_size(canvas, options)
  setup_paper_size(canvas, options)
  setup_image_info(canvas, options)
  setup_print_info(canvas, options)
  apply_theme_if_need(canvas, options)
  parse_rd(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
  Rabbit::GetText.bindtextdomain
  
  logger = Rabbit::Logger::STDERR.new
  
  options, logger = parse(ARGV, logger)
  
  Rabbit::GetText.bindtextdomain(options.locale_dir)
  
  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

if __FILE__ == $0
  main
end
