require 'barby/outputter'
require 'cairo'
require 'stringio'

module Barby

  # Uses Cairo to render a barcode to a number of formats: PNG, PS, EPS, PDF and SVG
  #
  # Registers methods render_to_cairo_context, to_png, to_ps, to_eps, to_pdf and to_svg
  #
  # Options:
  #
  #   * xdim
  #   * ydim             - (2D only)
  #   * height           - (1D only)
  #   * foreground       - Cairo::Color::Base or Cairo::Color.parse(value)
  #   * background       - ^
  class CairoOutputter < Outputter

    register :render_to_cairo_context
    register :to_png

    if Cairo.const_defined?(:PSSurface)
      register :to_ps
      register :to_eps if Cairo::PSSurface.method_defined?(:eps=)
    end

    register :to_pdf if Cairo.const_defined?(:PDFSurface)
    register :to_svg if Cairo.const_defined?(:SVGSurface)

    attr_writer :x, :y, :xdim, :height, :margin


    def initialize(*)
      super
      @x, @y, @xdim, @height, @margin = nil
    end

    #Render the barcode onto a Cairo context
    def render_to_cairo_context(context, options={})
      if context.respond_to?(:have_current_point?) and
          context.have_current_point?
        current_x, current_y = context.current_point
      else
        current_x = x(options) || margin(options)
        current_y = y(options) || margin(options)
      end

      _xdim = xdim(options)
      _height = height(options)
      original_current_x = current_x
      context.save do
        context.set_source_color(options[:foreground] || :black)
        context.fill do
          if barcode.two_dimensional?
            boolean_groups.each do |groups|
              groups.each do |bar,amount|
                current_width = _xdim * amount
                if bar
                  context.rectangle(current_x, current_y, current_width, _xdim)
                end
                current_x += current_width
              end
              current_x = original_current_x
              current_y += _xdim
            end
          else
            boolean_groups.each do |bar,amount|
              current_width = _xdim * amount
              if bar
                context.rectangle(current_x, current_y, current_width, _height)
              end
              current_x += current_width
            end
          end
        end
      end

      context
    end


    #Render the barcode to a PNG image
    def to_png(options={})
      output_to_string_io do |io|
        Cairo::ImageSurface.new(options[:format],
                                full_width(options),
                                full_height(options)) do |surface|
          render(surface, options)
          surface.write_to_png(io)
        end
      end
    end


    #Render the barcode to a PS document
    def to_ps(options={})
      output_to_string_io do |io|
        Cairo::PSSurface.new(io,
                             full_width(options),
                             full_height(options)) do |surface|
          surface.eps = options[:eps] if surface.respond_to?(:eps=)
          render(surface, options)
        end
      end
    end


    #Render the barcode to an EPS document
    def to_eps(options={})
      to_ps(options.merge(:eps => true))
    end


    #Render the barcode to a PDF document
    def to_pdf(options={})
      output_to_string_io do |io|
        Cairo::PDFSurface.new(io,
                              full_width(options),
                              full_height(options)) do |surface|
          render(surface, options)
        end
      end
    end


    #Render the barcode to an SVG document
    def to_svg(options={})
      output_to_string_io do |io|
        Cairo::SVGSurface.new(io,
                              full_width(options),
                              full_height(options)) do |surface|
          render(surface, options)
        end
      end
    end


    def x(options={})
      @x || options[:x]
    end

    def y(options={})
      @y || options[:y]
    end

    def width(options={})
      (barcode.two_dimensional? ? encoding.first.length : encoding.length) * xdim(options)
    end

    def height(options={})
      if barcode.two_dimensional?
        encoding.size * xdim(options)
      else
        @height || options[:height] || 50
      end
    end

    def full_width(options={})
      width(options) + (margin(options) * 2)
    end

    def full_height(options={})
      height(options) + (margin(options) * 2)
    end

    def xdim(options={})
      @xdim || options[:xdim] || 1
    end

    def margin(options={})
      @margin || options[:margin] || 10
    end


  private

    def output_to_string_io
      io = StringIO.new
      yield(io)
      io.rewind
      io.read
    end


    def render(surface, options)
      context = Cairo::Context.new(surface)
      yield(context) if block_given?
      context.set_source_color(options[:background] || :white)
      context.paint
      render_to_cairo_context(context, options)
      context
    end


  end

end
