module Win32
  module Screenshot
    # @private
    # This is an internal class for taking the actual screenshots and not part of a public API.
    class BitmapMaker
      class << self
        extend FFI::Library

        ffi_lib 'user32', 'gdi32'
        ffi_convention :stdcall

        # user32.dll
        attach_function :window_dc, :GetWindowDC,
                        [:long], :long
        attach_function :client_dc, :GetDC,
                        [:long], :long
        attach_function :client_rect, :GetClientRect,
                        [:long, :pointer], :bool
        attach_function :window_rect, :GetWindowRect,
                        [:long, :pointer], :bool
        attach_function :foreground_window, :GetForegroundWindow,
                        [], :long
        attach_function :desktop_window, :GetDesktopWindow,
                        [], :long

        # gdi32.dll
        attach_function :create_compatible_dc, :CreateCompatibleDC,
                        [:long], :long
        attach_function :create_compatible_bitmap, :CreateCompatibleBitmap,
                        [:long, :int, :int], :long
        attach_function :select_object, :SelectObject,
                        [:long, :long], :long
        attach_function :bit_blt, :BitBlt,
                        [:long, :int, :int, :int, :int, :long, :int, :int, :long], :bool
        attach_function :di_bits, :GetDIBits,
                        [:long, :long, :int, :int, :pointer, :pointer, :int], :int
        attach_function :delete_object, :DeleteObject,
                        [:long], :bool
        attach_function :delete_dc, :DeleteDC,
                        [:long], :bool
        attach_function :release_dc, :ReleaseDC,
                        [:long, :long], :int

        def capture_all(hwnd, context)
          width, height = dimensions_for(hwnd, context)
          capture_area(hwnd, context, 0, 0, width, height)
        end

        SRCCOPY = 0x00CC0020
        DIB_RGB_COLORS = 0

        def capture_area(hwnd, context, x1, y1, x2, y2)
          hScreenDC = send("#{context}_dc", hwnd)
          w = x2-x1
          h = y2-y1

          hmemDC = create_compatible_dc(hScreenDC)
          hmemBM = create_compatible_bitmap(hScreenDC, w, h)
          select_object(hmemDC, hmemBM)
          bit_blt(hmemDC, 0, 0, w, h, hScreenDC, x1, y1, SRCCOPY)
          bitmap_size = w * h * 3 + w % 4 * h
          lpvpxldata = FFI::MemoryPointer.new(bitmap_size)

          # Bitmap header
          # http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html
          bmInfo = [40, w, h, 1, 24, 0, 0, 0, 0, 0, 0, 0].pack('L3S2L6')
          di_bits(hmemDC, hmemBM, 0, h, lpvpxldata, bmInfo, DIB_RGB_COLORS)

          bmFileHeader = [
                  19778,
                  bitmap_size + 40 + 14,
                  0,
                  0,
                  54
          ].pack('SLSSL')

          Image.new(bmFileHeader + bmInfo + lpvpxldata.read_string(bitmap_size), w, h)
        ensure
          lpvpxldata.free
          delete_object(hmemBM)
          delete_dc(hmemDC)
          release_dc(0, hScreenDC)
        end

        def dimensions_for(hwnd, context)
          rect = [0, 0, 0, 0].pack('l4')
          BitmapMaker.send("#{context}_rect", hwnd.to_i, rect)
          left, top, width, height = rect.unpack('l4')

          if context == :window
            [width + 1 - left, height + 1 - top]
          else
            [width, height]
          end
        end

      end
    end
  end
end