module VIPS
  class Writer
    def write(path)
      write_internal path
    end
  end

  class CSVWriter < Writer
    attr_accessor :separator

    def initialize(image, options={})
      super image
      @separator = "\t"

      self.separator = options[:separator] if options.has_key?(:separator)
    end

    def write(path)
      write_internal "#{path}:sep:#{@separator}"
    end
  end

  class JPEGWriter < Writer
    attr_reader :quality

    def initialize(image, options={})
      super image

      @quality = 75

      self.quality = options[:quality] if options.has_key?(:quality)
    end

    def write(path)
      write_internal "#{path}:#{@quality}"
    end

    def to_memory
      buf_internal @quality
    end

    def quality=(quality_v)
      unless (0..100).include?(quality_v)
        raise ArgumentError, 'quality must be a numeric value between 0 and 100'
      end

      @quality = quality_v
    end
  end

  class PNGWriter < Writer
    attr_reader :compression
    attr_accessor :interlace

    def initialize(image, options={})
      super image

      @compression = 6
      @interlace = false

      self.compression = options[:compression] if options.has_key?(:compression)
      self.interlace = options[:interlace] if options.has_key?(:interlace)
    end

    def write(path)
      write_internal "#{path}:#{@compression},#{@interlace ? 1 : 0}"
    end

    def to_memory
      buf_internal @compression, (@interlace ? 1 : 0)
    end

    def compression=(compression_v)
      unless (0..9).include?(compression_v)
        raise ArgumentError, 'compression must be a numeric value between 0 and 9'
      end

      @compression = compression_v
    end
  end

  class PPMWriter < Writer
    attr_reader :format

    FORMAT = [:binary, :ascii]

    def initialize(image, options={})
      super image

      @format = :binary

      self.format = options[:format] if options.has_key?(:format)
    end

    def write(path)
      write_internal "#{path}:#{@format}"
    end

    def format=(format_v)
      unless FORMAT.include?(format_v)
        raise ArgumentError, "format must be one of #{FORMAT.join(', ')}"
      end

      @format = format_v
    end
  end

  class TIFFWriter < Writer
    attr_reader :compression, :layout, :multi_res, :format, :resolution_units,
      :resolution, :predictor, :quality, :tile_size

    COMPRESSION = [:none, :jpeg, :deflate, :packbits, :ccittfax4, :lzw]
    PREDICTOR = [:none, :horizontal_differencing, :floating_point]
    LAYOUT = [:strip, :tile]
    MULTI_RES = [:flat, :pyramid]
    FORMAT = [:manybit, :onebit]
    RESOLUTION_UNITS = [:cm, :inch]

    def initialize(image, options={})
      super image

      @compression = :none
      @quality = 75
      @predictor = :none
      @layout = :strip
      @tile_size = [128, 128]
      @multi_res = :flat
      @format = :manybit
      @resolution_units = :cm

      [ :compression, :layout, :multi_res, :format, :resolution_units,
        :resolution, :predictor, :quality, :tile_size ].each do |att|
        self.send "#{att}=".to_sym, options[att] if options.has_key?(att)
      end
    end

    def write(path)
      opts = [compression_str, layout_str, @multi_res, @format, resolution_str].join ','
      write_internal "#{path}:#{opts}"
    end

    def compression_str
      case @compression
      when :jpeg then "#{@compression}:#{@quality}"
      when :lzw, :deflate then "#{@compression}:#{@predictor}"
      else @compression
      end
    end

    def layout_str
      s = @layout
      s << ":#{@tile_size.join 'x'}" if @layout == :tile
      s
    end

    def resolution_str
      s = "res_#{@resolution_units}"
      s << ":#{@resolution.join 'x'}" if @resolution
      s
    end

    def compression=(compression_v)
      unless COMPRESSION.include?(compression_v)
        raise ArgumentError, "compression must be one of #{COMPRESSION.join(', ')}"
      end

      @compression = compression_v
    end

    def quality=(quality_v)
      unless (0..100).include?(quality_v)
        raise ArgumentError, 'quality must be a numeric value between 0 and 100'
      end

      @quality = quality_v
    end

    def predictor=(predictor_v)
      unless PREDICTOR.include?(predictor_v)
        raise ArgumentError, "predictor must be one of #{PREDICTOR.join(', ')}"
      end

      @predictor = predictor_v
    end

    def layout=(layout_v)
      unless LAYOUT.include?(layout_v)
        raise ArgumentError, "layout must be one of #{LAYOUT.join(', ')}"
      end

      @layout = layout_v
    end

    def tile_size=(tile_size_v)
      unless tile_size_v[0] > 1 && tile_size_v[1] > 1
        raise ArgumentError, "tile sizes must be larger than 1"
      end

      @tile_size = tile_size_v
    end

    def multi_res=(multi_res_v)
      unless MULTI_RES.include?(multi_res_v)
        raise ArgumentError, "multi res must be one of #{MULTI_RES.join(', ')}"
      end

      @multi_res = multi_res_v
    end

    def format=(format_v)
      unless FORMAT.include?(format_v)
        raise ArgumentError, "format must be one of #{FORMAT.join(', ')}"
      end

      @format = format_v
    end

    def resolution_units=(resolution_units_v)
      unless RESOLUTION_UNITS.include?(resolution_units_v)
        raise ArgumentError, "Resolution units must be one of #{RESOLUTION_UNITS.join(', ')}"
      end

      @resolution_units = resolution_units_v
    end

    def resolution=(resolution_v)
      unless resolution_v[0] > 0 && resolution_v[1] > 0
        raise ArgumentError, "Resolution must have a height and width larger than 0"
      end

      @resolution = resolution_v
    end
  end

  class Image
    def csv(*args)
      invoke_writer CSVWriter, *args
    end

    def jpeg(*args)
      invoke_writer JPEGWriter, *args
    end

    def png(*args)
      invoke_writer PNGWriter, *args
    end

    def ppm(*args)
      invoke_writer PPMWriter, *args
    end

    def tiff(*args)
      invoke_writer TIFFWriter, *args
    end

    def vips(*args)
      invoke_writer VIPSWriter, *args
    end

    def write(*args)
      invoke_writer Writer, *args
    end

    private

    def invoke_writer(writer_class, path=nil, options={})
      w = writer_class.new self, options
      if path
        w.write path
      else
        w
      end
    end
  end
end
