module Gosu
  class Image
    def self.from_blob(width, height, rgba = "\0\0\0\0" * (width * height), retro: false, tileable: false)
      @struct ||= Struct.new(:columns, :rows, :to_blob)
      self.new(@struct.new(width, height, rgba), retro: retro, tileable: tileable)
    end

    def self.from_text(markup, line_height, font: Gosu.default_font_name, width: -1, spacing: 0, align: :left,
                                            bold: false, italic: false, underline: false, retro: false)
      __image = GosuFFI.Gosu_Image_create_from_text(markup, font, line_height, width, spacing,
                                                    GosuFFI.font_alignment_flags(align),
                                                    GosuFFI.font_flags(bold, italic, underline),
                                                    GosuFFI.image_flags(retro: retro))
      GosuFFI.check_last_error
      Gosu::Image.new(__image)
    end

    def self.from_markup(markup, line_height, font: Gosu.default_font_name, width: -1, spacing: 0, align: :left,
                                              bold: false, italic: false, underline: false, retro: false)
      __image = GosuFFI.Gosu_Image_create_from_markup(markup, font, line_height, width, spacing,
                                                      GosuFFI.font_alignment_flags(align),
                                                      GosuFFI.font_flags(bold, italic, underline),
                                                      GosuFFI.image_flags(retro: retro))
      GosuFFI.check_last_error
      Gosu::Image.new(__image)
    end

    def self.load_tiles(filename_or_image, tile_width, tile_height, retro: false, tileable: false)
      flags = GosuFFI.image_flags(retro: retro, tileable: tileable)

      images = []
      callback = proc { |data, image| images << Gosu::Image.new(image) }

      if filename_or_image.is_a? String
        GosuFFI.Gosu_Image_create_from_tiles(filename_or_image, tile_width, tile_height, callback, nil, flags)
      else
        if filename_or_image.is_a? Gosu::Image
          GosuFFI.Gosu_Image_create_tiles_from_image(filename_or_image.__pointer, tile_width, tile_height, callback, nil, flags)
        else
          image = Gosu::Image.new(filename_or_image, retro: retro, tileable: tileable)
          GosuFFI.Gosu_Image_create_tiles_from_image(image.__pointer, tile_width, tile_height, callback, nil, flags)
        end
      end

      GosuFFI.check_last_error

      return images
    end

    def initialize(object, retro: false, tileable: false, rect: nil)
      if rect and rect.size != 4
        raise ArgumentError, "Expected 4-element array as rect"
      end

      flags = GosuFFI.image_flags(retro: retro, tileable: tileable)

      if object.is_a? String
        if rect
          __image = GosuFFI.Gosu_Image_create_from_rect(object, *rect, flags)
        else
          __image = GosuFFI.Gosu_Image_create(object, flags)
        end
        GosuFFI.check_last_error
      elsif object.is_a?(FFI::Pointer)
        __image = object
      elsif object.respond_to?(:to_blob) and
            object.respond_to?(:columns) and
            object.respond_to?(:rows)
      
        blob = object.to_blob { self.format = "RGBA"; self.depth = 8 }

        # This creates a copy of the Ruby string data, which shouldn't be necessary with CRuby.
        ptr = FFI::MemoryPointer.from_string(blob)
        rect ||= [0, 0, object.columns, object.rows]
        # Do not consider the terminating null byte part of the ptr length.
        __image = GosuFFI.Gosu_Image_create_from_blob(ptr, ptr.size - 1, object.columns, object.rows, *rect, flags)
        ptr.free

        GosuFFI.check_last_error
      else
        raise ArgumentError, "Expected String or RMagick::Image (or a type compatible with it)"
      end

      @managed_pointer = FFI::AutoPointer.new(__image, GosuFFI.method(:Gosu_Image_destroy))
    end

    def __pointer
      @managed_pointer
    end

    def width
      GosuFFI.check_last_error(GosuFFI.Gosu_Image_width(__pointer))
    end

    def height
      GosuFFI.check_last_error(GosuFFI.Gosu_Image_height(__pointer))
    end

    def draw(x, y, z, scale_x = 1, scale_y = 1, color = Gosu::Color::WHITE, flags = :default)
      GosuFFI.Gosu_Image_draw(__pointer, x, y, z, scale_x, scale_y, GosuFFI.color_to_uint32(color), GosuFFI.blend_mode(flags))
      GosuFFI.check_last_error
    end

    def draw_rot(x, y, z, angle = 0, center_x = 0.5, center_y = 0.5, scale_x = 1, scale_y = 1, color = Gosu::Color::WHITE, flags = :default)
      GosuFFI.Gosu_Image_draw_rot(__pointer, x, y, z, angle, center_x, center_y, scale_x, scale_y, GosuFFI.color_to_uint32(color), GosuFFI.blend_mode(flags))
      GosuFFI.check_last_error
    end

    def draw_as_quad(x1, y1, color1, x2, y2, color2, x3, y3, color3, x4, y4, color4, z = 0, mode = :default)
      GosuFFI.Gosu_Image_draw_as_quad(x1, y1, GosuFFI.color_to_uint32(color1), x2, y2, GosuFFI.color_to_uint32(color2),
                                      x3, y3, GosuFFI.color_to_uint32(color3), x4, y4, GosuFFI.color_to_uint32(color4),
                                      z, GosuFFI.blend_mode(mode))
      GosuFFI.check_last_error
    end

    def save(filename)
      GosuFFI.Gosu_Image_save(__pointer, filename)
      GosuFFI.check_last_error
    end

    def to_blob
      blob = GosuFFI.check_last_error(GosuFFI.Gosu_Image_to_blob(__pointer))
      blob.read_string(width * height * Gosu::Color::SIZEOF)
    end

    def subimage(left, top, width, height)
      __subimage = GosuFFI.Gosu_Image_create_from_subimage(__pointer, left, top, width, height)
      GosuFFI.check_last_error
      Gosu::Image.new(__subimage)
    end

    def insert(image, x, y)
      image_ = nil
      if image.is_a?(Gosu::Image)
        image_ = image.__pointer
      elsif image.respond_to?(:to_blob) and
            image.respond_to?(:rows) and
            image.respond_to?(:columns)
        image_ = Gosu::Image.new(image).__pointer
      else
        raise "Unable to insert image!"
      end

      GosuFFI.Gosu_Image_insert(__pointer, image_, x, y)
      GosuFFI.check_last_error
    end

    def gl_tex_info
      tex_info = GosuFFI.check_last_error(GosuFFI.Gosu_Image_gl_tex_info_create(__pointer))
      tex_info ? GLTexInfo.new(tex_info) : nil
    end
  end
end