module Magick
  class Image
    
    def self.from_blob(blob, &add)
      # TODO multiple images in file
      [Image.from_image(Magick4J.MagickImage.from_blob(blob.to_java_bytes), &add)]
    end

    def self.read(file, &add)
      info = Info.new(&add)
      image = Magick4J.ImageDatabase.createDefault(file.to_s, info._info) || Magick4J.MagickImage.new(java.io.File.new(file.to_s))
      [Image.from_image(image,&add)]
    end
    
    def self.from_image(image, &add)
      raise ArgumentError, 'First parameter must be a MagickImage instance.' unless image.is_a? Magick4J.MagickImage
      magick_image = Image.new(image.getWidth(), image.getHeight(), &add)
      magick_image._image = image
      magick_image
    end
    
    def self.allocate(*args, &add)
      info = Info.new(&add)
      if args.length == 1
        case args[0]
        when String then
          # TODO Respect Dir.getwd
          name = args[0]
          @image = Magick4J.ImageDatabase.createDefault(name, info._info) || Magick4J.MagickImage.new(java.io.File.new(name))
        when Magick4J.MagickImage then
          @image = args[0]
        when Image then
          @image = args[0]._image
        else
          raise ArgumentError, "The argument just can be a String, a MagickImage or an Image instance."
        end
      else
        @image = Magick4J.MagickImage.new(args[0], args[1], info._info)
        if args.length == 3
          args[2].fill(self)
        end
      end
    end

    def background_color
      @image.getBackgroundColor
    end
    
    def background_color=(value)
      raise TypeError, "argument must be color name or pixel (#{value.class} given)" unless value.is_a?(String) || value.is_a?(Pixel)
      value = Pixel.from_color(value) if value.is_a?(String)
      @image.setBackgroundColor(value)
    end
    
    def blur_image(radius=0.0, sigma=1.0)
      # Swap order on purpose. I wanted them the other way around in Magick4J.
      Image.from_image(@image.blurred(sigma, radius))
    end
    
    def change_geometry(geometry)
      geometry = Geometry.from_s(geometry.to_s) unless geometry.is_a? Geometry
      index = if geometry.flag.nil?
                0
              else
                geometry.flag._val
              end
      geometry = JGeometries[index].new( geometry.width, geometry.height,
                                         geometry.x, geometry.y)
      yield geometry.calculate_width(self._image),
            geometry.calculate_height(self._image),
            self
    end

    def columns
      @image.getWidth
    end

    def composite(*args)
      # image, x, y, composite_op
      args[0] = args[0]._image
      args.map! {|arg| arg.is_a?(Enum) ? arg._val : arg}
      Image.from_image(@image.composited(*args))
    end

    def copy
      Image.from_image(@image.clone)
    end

    def crop(*args)
      copy.crop!(*args)
    end

    def crop!(*args)
      # gravity, x, y, width, height, reset_offset
      # Defaults.
      gravity = nil
      x = y = -1
      reset_offset = false
      # Find available args.
      if args.first.is_a? GravityType
        gravity = args.shift._val
      end
      if [FalseClass, TrueClass].member? args.last.class
        reset = args.pop
      end
      if args.length == 4
        x, y = args[0..1]
      end
      width, height = args[-2..-1]
      # Call Java.
      # TODO Why wouldn't we reset offset information? Do we need to use that?
      @image =  unless gravity.nil?
                  if x == -1 || y == -1
                    @image.crop(gravity, width, height)
                  else
                    @image.crop(gravity, x, y, width, height)
                  end
                else
                  @image.crop(x,y,width,height)
                end
      self
    end

    def display
      @image.display
      self
    end
    
    def erase!
      @image.erase
    end

    def format
      @image.getFormat
    end

    def format= format
      @image.setFormat(format)
      self
    end

    def flip
      copy.flip
    end

    def flip!
      @image.flip
      self
    end

    def _image
      @image
    end
    
    def _image=(new_image)
      @image = new_image
    end
    
    def _info
      @info
    end
    
    def _info=(new_info)
      @info = new_info
    end

    def initialize(columns, rows, fill=nil, &info_block)
      info = Info.new(&info_block)
      @image = Magick4J.MagickImage.new(columns, rows, info._info)
      fill.fill(self) if fill.respond_to? :fill
    end

    def matte= matte
      @image.setMatte(matte)
    end

    def quantize(number_colors=256, colorspace=RGBColorspace, dither=true, tree_depth=0, measure_error=false)
      Image.from_image(@image.quantized(number_colors, colorspace._val, dither, tree_depth, measure_error))
    end

    def raise(width=6, height=6, raise=true)
      Image.from_image(@image.raised(width, height, raise))
    end

    def resize(*args)
      copy.resize!(*args)
    end

    def resize!(*args)
      @image =  if args.length == 1
                  @image.resized(args[0])
                elsif args.length == 2 # It must be 4 nor 2, but two of them are not yet implemented
                  # TODO  Implement the other two arguments.
                  # arg[0] --> new_width
                  # arg[1] --> new_height
                  # arg[2] --> filter=LanczosFilter
                  # arg[3] --> support=1.0
                  @image.resized(args[0],args[1])
                else
                  Kernel.raise ArgumentError, "wrong number of parameters(#{args.length} for 1 or 4)"
                end
      self
    end

    def rotate(amount, qualifier=nil)
      copy.rotate!(amount,qualifier)
    end

    def rotate!(amount, qualifier=nil)
      if qualifier == '<' && columns < rows
        @image.rotate(amount)
        self
      elsif qualifier == '>' && columns > rows
        @image.rotate(amount)
        self
      elsif qualifier.nil?
        @image.rotate(amount)
        self
      else
        nil
      end
    end

    def rows
      @image.getHeight
    end
    
    def store_pixels(x, y, columns, rows, pixels)
      ria_size = columns*rows
      raise IndexError, "not enough elements in array - expecting #{ria_size}, got #{pixels.size}" if pixels.size < ria_size
      @image.storePixels(x,y,columns,rows,pixels.to_java)
    end

    def to_blob(&add)
      info = Info.new(&add)
      @image.setFormat(info.format) if info.format
      String.from_java_bytes(@image.toBlob)
    end

    def watermark(mark, lightness=1.0, saturation=1.0, gravity=nil, x_offset=0, y_offset=0)
      if gravity.is_a? Numeric
        # gravity is technically an optional argument in the middle.
        gravity = nil
        y_offset = x_offset
        x_offset = gravity
      end
      # TODO Perform watermark.
      self
    end

    def write(file, &add)
      # TODO I'm having trouble finding out how this info is used, so I'll skip using it for now.
      info = Info.new(&add)
      # TODO Resolve pwd as needed
      @image.write(file)
      self
    end

    class Info

      # TODO Replace with call to Java, or is this the better way? Should it be converted to the Java version only later?
      def background_color= background_color
        @info.setBackgroundColor(Magick4J.ColorDatabase.queryDefault(background_color))
      end

      attr_accessor :format

      def _info
        @info
      end

      def initialize(&add)
        @info = Magick4J.ImageInfo.new
        instance_eval &add if add
      end

      def size= size
        size = Geometry.from_s(size) if size.is_a? String
        geometry = Magick4J.Geometry.new
        geometry.setWidth(size.width)
        geometry.setHeight(size.height)
        @info.setSize(geometry)
      end

    end

  end
end