#ifdef MOZ_WIDGET_ANDROID

#include "AndroidNativeWindow.h"
#include "prlink.h"

// #define ANDROID_NATIVE_WINDOW_DEBUG

#if defined(ANDROID_NATIVE_WINDOW_DEBUG) || defined(DEBUG)
#define ALOG(args...)  __android_log_print(ANDROID_LOG_INFO, "AndroidNativeWindow" , ## args)
#else
#define ALOG(args...) ((void)0)
#endif

using namespace mozilla::gfx;
using namespace mozilla::gl;
using namespace mozilla;

class NativeWindowLibrary
{
public:

  NativeWindowLibrary()
    : fANativeWindow_fromSurface(nullptr)
    , fANativeWindow_release(nullptr)
    , fANativeWindow_setBuffersGeometry(nullptr)
    , fANativeWindow_lock(nullptr)
    , fANativeWindow_unlockAndPost(nullptr)
    , fANativeWindow_getFormat(nullptr)
    , fANativeWindow_getWidth(nullptr)
    , fANativeWindow_getHeight(nullptr)
  {
    PRLibrary* lib = PR_LoadLibrary("libandroid.so");

    fANativeWindow_fromSurface = (pfnANativeWindow_fromSurface)PR_FindSymbol(lib, "ANativeWindow_fromSurface");
    fANativeWindow_release = (pfnANativeWindow_release)PR_FindSymbol(lib, "ANativeWindow_release");
    fANativeWindow_setBuffersGeometry = (pfnANativeWindow_setBuffersGeometry)PR_FindSymbol(lib, "ANativeWindow_setBuffersGeometry");
    fANativeWindow_lock = (pfnANativeWindow_lock)PR_FindSymbol(lib, "ANativeWindow_lock");
    fANativeWindow_unlockAndPost = (pfnANativeWindow_unlockAndPost)PR_FindSymbol(lib, "ANativeWindow_unlockAndPost");
    fANativeWindow_getFormat = (pfnANativeWindow_getFormat)PR_FindSymbol(lib, "ANativeWindow_getFormat");
    fANativeWindow_getWidth = (pfnANativeWindow_getWidth)PR_FindSymbol(lib, "ANativeWindow_getWidth");
    fANativeWindow_getHeight = (pfnANativeWindow_getHeight)PR_FindSymbol(lib, "ANativeWindow_getHeight");
  }

  void* ANativeWindow_fromSurface(JNIEnv* aEnv, jobject aSurface) {
    ALOG("%s: env=%p, surface=%p\n", __PRETTY_FUNCTION__, aEnv, aSurface);
    if (!Initialized()) {
      return nullptr;
    }

    return fANativeWindow_fromSurface(aEnv, aSurface);
  }

  void ANativeWindow_release(void* aWindow) {
    ALOG("%s: window=%p\n", __PRETTY_FUNCTION__, aWindow);
    if (!Initialized()) {
      return;
    }

    fANativeWindow_release(aWindow);
  }

  bool ANativeWindow_setBuffersGeometry(void* aWindow, int32_t aWidth, int32_t aHeight, int32_t aFormat) {
    ALOG("%s: window=%p, width=%d, height=%d, format=%d\n", __PRETTY_FUNCTION__, aWindow, aWidth, aHeight, aFormat);
    if (!Initialized()) {
      return false;
    }

    return fANativeWindow_setBuffersGeometry(aWindow, aWidth, aHeight, (int32_t)aFormat) == 0;
  }

  bool ANativeWindow_lock(void* aWindow, void* out_buffer, void*in_out_dirtyBounds) {
    ALOG("%s: window=%p, out_buffer=%p, in_out_dirtyBounds=%p\n", __PRETTY_FUNCTION__,
         aWindow, out_buffer, in_out_dirtyBounds);
    if (!Initialized()) {
      return false;
    }

    return fANativeWindow_lock(aWindow, out_buffer, in_out_dirtyBounds) == 0;
  }

  bool ANativeWindow_unlockAndPost(void* aWindow) {
    ALOG("%s: window=%p\n", __PRETTY_FUNCTION__, aWindow);
    if (!Initialized()) {
      return false;
    }

    return fANativeWindow_unlockAndPost(aWindow) == 0;
  }

  AndroidWindowFormat ANativeWindow_getFormat(void* aWindow) {
    ALOG("%s: window=%p\n", __PRETTY_FUNCTION__, aWindow);
    if (!Initialized()) {
      return AndroidWindowFormat::Unknown;
    }

    return (AndroidWindowFormat)fANativeWindow_getFormat(aWindow);
  }

  int32_t ANativeWindow_getWidth(void* aWindow) {
    ALOG("%s: window=%p\n", __PRETTY_FUNCTION__, aWindow);
    if (!Initialized()) {
      return -1;
    }

    return fANativeWindow_getWidth(aWindow);
  }

  int32_t ANativeWindow_getHeight(void* aWindow) {
    ALOG("%s: window=%p\n", __PRETTY_FUNCTION__, aWindow);
    if (!Initialized()) {
      return -1;
    }

    return fANativeWindow_getHeight(aWindow);
  }

  bool Initialized() {
    return fANativeWindow_fromSurface && fANativeWindow_release && fANativeWindow_setBuffersGeometry
      && fANativeWindow_lock && fANativeWindow_unlockAndPost && fANativeWindow_getFormat && fANativeWindow_getWidth
      && fANativeWindow_getHeight;
  }

private:

  typedef void* (*pfnANativeWindow_fromSurface)(JNIEnv* env, jobject surface);
  pfnANativeWindow_fromSurface fANativeWindow_fromSurface;

  typedef void (*pfnANativeWindow_release)(void* window);
  pfnANativeWindow_release fANativeWindow_release;

  typedef int32_t (*pfnANativeWindow_setBuffersGeometry)(void* window, int32_t width, int32_t height, int32_t format);
  pfnANativeWindow_setBuffersGeometry fANativeWindow_setBuffersGeometry;

  typedef int32_t (*pfnANativeWindow_lock)(void *window, void *out_buffer, void *in_out_dirtyBounds);
  pfnANativeWindow_lock fANativeWindow_lock;

  typedef int32_t (*pfnANativeWindow_unlockAndPost)(void *window);
  pfnANativeWindow_unlockAndPost fANativeWindow_unlockAndPost;

  typedef AndroidWindowFormat (*pfnANativeWindow_getFormat)(void* window);
  pfnANativeWindow_getFormat fANativeWindow_getFormat;

  typedef int32_t (*pfnANativeWindow_getWidth)(void* window);
  pfnANativeWindow_getWidth fANativeWindow_getWidth;

  typedef int32_t (*pfnANativeWindow_getHeight)(void* window);
  pfnANativeWindow_getHeight fANativeWindow_getHeight;
};

static NativeWindowLibrary* sLibrary = nullptr;

static bool
EnsureInit()
{
  static bool initialized = false;
  if (!initialized) {
    if (!sLibrary) {
      sLibrary = new NativeWindowLibrary();
    }
    initialized = sLibrary->Initialized();
  }

  return initialized;
}


namespace mozilla {

/* static */ AndroidNativeWindow*
AndroidNativeWindow::CreateFromSurface(JNIEnv* aEnv, jobject aSurface)
{
  if (!EnsureInit()) {
    ALOG("Not initialized");
    return nullptr;
  }

  void* window = sLibrary->ANativeWindow_fromSurface(aEnv, aSurface);
  if (!window) {
    ALOG("Failed to create window from surface");
    return nullptr;
  }

  return new AndroidNativeWindow(window);
}

AndroidNativeWindow::~AndroidNativeWindow()
{
  if (EnsureInit() && mWindow) {
    sLibrary->ANativeWindow_release(mWindow);
    mWindow = nullptr;
  }
}

IntSize
AndroidNativeWindow::Size()
{
  MOZ_ASSERT(mWindow);
  if (!EnsureInit()) {
    return IntSize(0, 0);
  }

  return IntSize(sLibrary->ANativeWindow_getWidth(mWindow), sLibrary->ANativeWindow_getHeight(mWindow));
}

AndroidWindowFormat
AndroidNativeWindow::Format()
{
  MOZ_ASSERT(mWindow);
  if (!EnsureInit()) {
    return AndroidWindowFormat::Unknown;
  }

  return sLibrary->ANativeWindow_getFormat(mWindow);
}

bool
AndroidNativeWindow::SetBuffersGeometry(int32_t aWidth, int32_t aHeight, AndroidWindowFormat aFormat)
{
  MOZ_ASSERT(mWindow);
  if (!EnsureInit())
    return false;

  return sLibrary->ANativeWindow_setBuffersGeometry(mWindow, aWidth, aHeight, (int32_t)aFormat);
}

bool
AndroidNativeWindow::Lock(void** out_bits,int32_t* out_width, int32_t* out_height,
                          int32_t* out_stride, AndroidWindowFormat* out_format)
{
  /* Copied from native_window.h in Android NDK (platform-9) */
  typedef struct ANativeWindow_Buffer {
      // The number of pixels that are show horizontally.
      int32_t width;

      // The number of pixels that are shown vertically.
      int32_t height;

      // The number of *pixels* that a line in the buffer takes in
      // memory.  This may be >= width.
      int32_t stride;

      // The format of the buffer.  One of WINDOW_FORMAT_*
      int32_t format;

      // The actual bits.
      void* bits;

      // Do not touch.
      uint32_t reserved[6];
  } ANativeWindow_Buffer; 


  ANativeWindow_Buffer buffer;

  if (!sLibrary->ANativeWindow_lock(mWindow, &buffer, nullptr)) {
    ALOG("Failed to lock");
    return false;
  }

  *out_bits = buffer.bits;
  *out_width = buffer.width;
  *out_height = buffer.height;
  *out_stride = buffer.stride;
  *out_format = (AndroidWindowFormat)buffer.format;
  return true;
}

bool
AndroidNativeWindow::UnlockAndPost()
{
  if (!EnsureInit()) {
    ALOG("Not initialized");
    return false;
  }

  return sLibrary->ANativeWindow_unlockAndPost(mWindow);
}

}

#endif // MOZ_WIDGET_ANDROID
