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

Thread.abort_on_exception = true

require "optparse"
require "ostruct"

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

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.encoding = "UTF-8"
  options.locale_dir = nil
  options.print = false
  options.print_out_filename = nil
  options.slides_per_page = 1
  options.left_margin = nil
  options.right_margin = nil
  options.top_margin = nil
  options.bottom_margin = nil
  options.left_page_margin = nil
  options.right_page_margin = nil
  options.top_page_margin = nil
  options.bottom_page_margin = nil

  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]})",
            " ",
            *source_type_descs) do |source_type|
      options.source_type = Rabbit::Source.types.find do |t|
        get_last_name[t].downcase == source_type.downcase
      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

    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("--left-margin [MARGIN]",
              Integer,
              "Set left margin for slides per page mode print.",
              "(auto)") do |margin|
        options.left_margin = margin
      end

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

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

      opts.on("--bottom-margin [MARGIN]",
              Integer,
              "Set bottom margin for slides per page mode print.",
              "(auto)") do |margin|
        options.bottom_margin = 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.top_margin = top
          options.right_margin = right
          options.bottom_margin = bottom
          options.left_margin = left
        rescue ArgumentError
          raise OptionParser::InvalidArgument.new(margins)
        end
      end

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

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

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

      opts.on("--bottom-page-margin [MARGIN]",
              Integer,
              "Set bottom page margin.",
              "(auto)") do |margin|
        options.bottom_page_margin = 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.top_page_margin = top
          options.right_page_margin = right
          options.bottom_page_margin = bottom
          options.left_page_margin = 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_tail("--help", "Show this message") do
      logger.info(opts.to_s)
      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::ARGF
    infos = [ARGF]
  else
    infos = argv
  end
  options.source_type.new(options.encoding, logger, *infos)
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
  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
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.left_page_margin = options.left_page_margin
  target.right_page_margin = options.right_page_margin
  target.top_page_margin = options.top_page_margin
  target.bottom_page_margin = options.bottom_page_margin
  target.left_margin = options.left_margin
  target.right_margin = options.right_margin
  target.top_margin = options.top_margin
  target.bottom_margin = options.bottom_margin
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)
  Gtk.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)
  Gtk.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
  Gtk.main
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)
  else
    do_display(options, logger)
  end
end

if __FILE__ == $0
  main
end
