bool cInterfaceWGL::PeekMessages()
{
  // TODO: peekmessage
  MSG msg;
  while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
  {
    if (msg.message == WM_QUIT)
      return FALSE;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return TRUE;
}

// Refer to the license.txt file included.

#include <windows.h>
#include <array>
#include <string>

#include "Common/GL/GLInterface/WGL.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"

// from wglext.h
#ifndef WGL_ARB_pbuffer
#define WGL_ARB_pbuffer 1
DECLARE_HANDLE(HPBUFFERARB);
#define WGL_DRAW_TO_PBUFFER_ARB 0x202D
#define WGL_MAX_PBUFFER_PIXELS_ARB 0x202E
#define WGL_MAX_PBUFFER_WIDTH_ARB 0x202F
#define WGL_MAX_PBUFFER_HEIGHT_ARB 0x2030
#define WGL_PBUFFER_LARGEST_ARB 0x2033
#define WGL_PBUFFER_WIDTH_ARB 0x2034
#define WGL_PBUFFER_HEIGHT_ARB 0x2035
#define WGL_PBUFFER_LOST_ARB 0x2036
typedef HPBUFFERARB(WINAPI* PFNWGLCREATEPBUFFERARBPROC)(HDC hDC, int iPixelFormat, int iWidth,
                                                        int iHeight, const int* piAttribList);
typedef HDC(WINAPI* PFNWGLGETPBUFFERDCARBPROC)(HPBUFFERARB hPbuffer);
typedef int(WINAPI* PFNWGLRELEASEPBUFFERDCARBPROC)(HPBUFFERARB hPbuffer, HDC hDC);
typedef BOOL(WINAPI* PFNWGLDESTROYPBUFFERARBPROC)(HPBUFFERARB hPbuffer);
typedef BOOL(WINAPI* PFNWGLQUERYPBUFFERARBPROC)(HPBUFFERARB hPbuffer, int iAttribute, int* piValue);
#endif /* WGL_ARB_pbuffer */

#ifndef WGL_ARB_pixel_format
#define WGL_ARB_pixel_format 1
#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
#define WGL_DRAW_TO_WINDOW_ARB 0x2001
#define WGL_DRAW_TO_BITMAP_ARB 0x2002
#define WGL_ACCELERATION_ARB 0x2003
#define WGL_NEED_PALETTE_ARB 0x2004
#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005
#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006
#define WGL_SWAP_METHOD_ARB 0x2007
#define WGL_NUMBER_OVERLAYS_ARB 0x2008
#define WGL_NUMBER_UNDERLAYS_ARB 0x2009
#define WGL_TRANSPARENT_ARB 0x200A
#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037
#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038
#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039
#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A
#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B
#define WGL_SHARE_DEPTH_ARB 0x200C
#define WGL_SHARE_STENCIL_ARB 0x200D
#define WGL_SHARE_ACCUM_ARB 0x200E
#define WGL_SUPPORT_GDI_ARB 0x200F
#define WGL_SUPPORT_OPENGL_ARB 0x2010
#define WGL_DOUBLE_BUFFER_ARB 0x2011
#define WGL_STEREO_ARB 0x2012
#define WGL_PIXEL_TYPE_ARB 0x2013
#define WGL_COLOR_BITS_ARB 0x2014
#define WGL_RED_BITS_ARB 0x2015
#define WGL_RED_SHIFT_ARB 0x2016
#define WGL_GREEN_BITS_ARB 0x2017
#define WGL_GREEN_SHIFT_ARB 0x2018
#define WGL_BLUE_BITS_ARB 0x2019
#define WGL_BLUE_SHIFT_ARB 0x201A
#define WGL_ALPHA_BITS_ARB 0x201B
#define WGL_ALPHA_SHIFT_ARB 0x201C
#define WGL_ACCUM_BITS_ARB 0x201D
#define WGL_ACCUM_RED_BITS_ARB 0x201E
#define WGL_ACCUM_GREEN_BITS_ARB 0x201F
#define WGL_ACCUM_BLUE_BITS_ARB 0x2020
#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021
#define WGL_DEPTH_BITS_ARB 0x2022
#define WGL_STENCIL_BITS_ARB 0x2023
#define WGL_AUX_BUFFERS_ARB 0x2024
#define WGL_NO_ACCELERATION_ARB 0x2025
#define WGL_GENERIC_ACCELERATION_ARB 0x2026
#define WGL_FULL_ACCELERATION_ARB 0x2027
#define WGL_SWAP_EXCHANGE_ARB 0x2028
#define WGL_SWAP_COPY_ARB 0x2029
#define WGL_SWAP_UNDEFINED_ARB 0x202A
#define WGL_TYPE_RGBA_ARB 0x202B
#define WGL_TYPE_COLORINDEX_ARB 0x202C
typedef BOOL(WINAPI* PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC hdc, int iPixelFormat,
                                                          int iLayerPlane, UINT nAttributes,
                                                          const int* piAttributes, int* piValues);
typedef BOOL(WINAPI* PFNWGLGETPIXELFORMATATTRIBFVARBPROC)(HDC hdc, int iPixelFormat,
                                                          int iLayerPlane, UINT nAttributes,
                                                          const int* piAttributes, FLOAT* pfValues);
typedef BOOL(WINAPI* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList,
                                                     const FLOAT* pfAttribFList, UINT nMaxFormats,
                                                     int* piFormats, UINT* nNumFormats);
#endif /* WGL_ARB_pixel_format */

#ifndef WGL_ARB_create_context
#define WGL_ARB_create_context 1
#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001
#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093
#define WGL_CONTEXT_FLAGS_ARB 0x2094
#define ERROR_INVALID_VERSION_ARB 0x2095
typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShareContext,
                                                         const int* attribList);
#endif /* WGL_ARB_create_context */

#ifndef WGL_ARB_create_context_profile
#define WGL_ARB_create_context_profile 1
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
#define ERROR_INVALID_PROFILE_ARB 0x2096
#endif /* WGL_ARB_create_context_profile */

#ifndef WGL_EXT_swap_control
#define WGL_EXT_swap_control 1
typedef BOOL(WINAPI* PFNWGLSWAPINTERVALEXTPROC)(int interval);
typedef int(WINAPI* PFNWGLGETSWAPINTERVALEXTPROC)(void);
#endif /* WGL_EXT_swap_control */

// Persistent pointers
static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = nullptr;
static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = nullptr;
static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = nullptr;
static PFNWGLCREATEPBUFFERARBPROC wglCreatePbufferARB = nullptr;
static PFNWGLGETPBUFFERDCARBPROC wglGetPbufferDCARB = nullptr;
static PFNWGLRELEASEPBUFFERDCARBPROC wglReleasePbufferDCARB = nullptr;
static PFNWGLDESTROYPBUFFERARBPROC wglDestroyPbufferARB = nullptr;

static void LoadWGLExtensions()
{
  wglSwapIntervalEXT = reinterpret_cast<PFNWGLSWAPINTERVALEXTPROC>(
      GLInterface->GetFuncAddress("wglSwapIntervalEXT"));
  wglCreateContextAttribsARB = reinterpret_cast<PFNWGLCREATECONTEXTATTRIBSARBPROC>(
      wglGetProcAddress("wglCreateContextAttribsARB"));
  wglChoosePixelFormatARB = reinterpret_cast<PFNWGLCHOOSEPIXELFORMATARBPROC>(
      wglGetProcAddress("wglChoosePixelFormatARB"));
  wglCreatePbufferARB =
      reinterpret_cast<PFNWGLCREATEPBUFFERARBPROC>(wglGetProcAddress("wglCreatePbufferARB"));
  wglGetPbufferDCARB =
      reinterpret_cast<PFNWGLGETPBUFFERDCARBPROC>(wglGetProcAddress("wglGetPbufferDCARB"));
  wglReleasePbufferDCARB =
      reinterpret_cast<PFNWGLRELEASEPBUFFERDCARBPROC>(wglGetProcAddress("wglReleasePbufferDCARB"));
  wglDestroyPbufferARB =
      reinterpret_cast<PFNWGLDESTROYPBUFFERARBPROC>(wglGetProcAddress("wglGetPbufferDCARB"));
}

static void ClearWGLExtensionPointers()
{
  wglSwapIntervalEXT = nullptr;
  wglCreateContextAttribsARB = nullptr;
  wglChoosePixelFormatARB = nullptr;
  wglCreatePbufferARB = nullptr;
  wglGetPbufferDCARB = nullptr;
  wglReleasePbufferDCARB = nullptr;
  wglDestroyPbufferARB = nullptr;
}

void cInterfaceWGL::SwapInterval(int Interval)
{
  if (wglSwapIntervalEXT)
    wglSwapIntervalEXT(Interval);
  else
    ERROR_LOG(VIDEO, "No support for SwapInterval (framerate clamped to monitor refresh rate).");
}
void cInterfaceWGL::Swap()
{
  SwapBuffers(m_dc);
}

void* cInterfaceWGL::GetFuncAddress(const std::string& name)
{
  FARPROC func = wglGetProcAddress(name.c_str());
  if (func == nullptr)
  {
    // Using GetModuleHandle here is okay, since we import functions from opengl32.dll, it's
    // guaranteed to be loaded.
    HMODULE opengl_module = GetModuleHandle(TEXT("opengl32.dll"));
    func = GetProcAddress(opengl_module, name.c_str());
  }

  return func;
}

// Draw messages on top of the screen

// Create rendering window.
bool cInterfaceWGL::Create(void* window_handle, bool stereo, bool core)
{
  if (!window_handle)
    return false;

  RECT window_rect = {};
  m_window_handle = reinterpret_cast<HWND>(window_handle);
  if (!GetClientRect(m_window_handle, &window_rect))
    return false;

  // Clear extension function pointers before creating the first context.
  ClearWGLExtensionPointers();

  // Control window size and picture scaling
  int twidth = window_rect.right - window_rect.left;
  int theight = window_rect.bottom - window_rect.top;
  s_backbuffer_width = twidth;
  s_backbuffer_height = theight;

  const DWORD stereo_flag = stereo ? PFD_STEREO : 0;
  static const PIXELFORMATDESCRIPTOR pfd = {
      sizeof(PIXELFORMATDESCRIPTOR),  // Size Of This Pixel Format Descriptor
      1,                              // Version Number
      PFD_DRAW_TO_WINDOW |            // Format Must Support Window
          PFD_SUPPORT_OPENGL |        // Format Must Support OpenGL
          PFD_DOUBLEBUFFER |          // Must Support Double Buffering
          stereo_flag,                // Could Support Quad Buffering
      PFD_TYPE_RGBA,                  // Request An RGBA Format
      32,                             // Select Our Color Depth
      0,
      0, 0, 0, 0, 0,   // Color Bits Ignored
      0,               // 8bit Alpha Buffer
      0,               // Shift Bit Ignored
      0,               // No Accumulation Buffer
      0, 0, 0, 0,      // Accumulation Bits Ignored
      0,               // 0Bit Z-Buffer (Depth Buffer)
      0,               // 0bit Stencil Buffer
      0,               // No Auxiliary Buffer
      PFD_MAIN_PLANE,  // Main Drawing Layer
      0,               // Reserved
      0, 0, 0          // Layer Masks Ignored
  };

  m_dc = GetDC(m_window_handle);
  if (!m_dc)
  {
    PanicAlert("(1) Can't create an OpenGL Device context. Fail.");
    return false;
  }

  int pixel_format = ChoosePixelFormat(m_dc, &pfd);
  if (!pixel_format)
  {
    PanicAlert("(2) Can't find a suitable PixelFormat.");
    return false;
  }

  if (!SetPixelFormat(m_dc, pixel_format, &pfd))
  {
    PanicAlert("(3) Can't set the PixelFormat.");
    return false;
  }

  m_rc = wglCreateContext(m_dc);
  if (!m_rc)
  {
    PanicAlert("(4) Can't create an OpenGL rendering context.");
    return false;
  }

  // WGL only supports desktop GL, for now.
  if (s_opengl_mode == GLInterfaceMode::MODE_DETECT)
    s_opengl_mode = GLInterfaceMode::MODE_OPENGL;

  if (core)
  {
    // Make the fallback context current, temporarily.
    // This is because we need an active context to use wglCreateContextAttribsARB.
    if (!wglMakeCurrent(m_dc, m_rc))
    {
      PanicAlert("(5) Can't make dummy WGL context current.");
      return false;
    }

    // Load WGL extension function pointers.
    LoadWGLExtensions();

    // Attempt creating a core context.
    HGLRC core_context = CreateCoreContext(m_dc, nullptr);

    // Switch out the temporary context before continuing, regardless of whether we got a core
    // context. If we didn't get a core context, the caller expects that the context is not current.
    if (!wglMakeCurrent(m_dc, nullptr))
    {
      PanicAlert("(6) Failed to switch out temporary context");
      return false;
    }

    // If we got a core context, destroy and replace the temporary context.
    if (core_context)
    {
      wglDeleteContext(m_rc);
      m_rc = core_context;
      m_core = true;
    }
    else
    {
      WARN_LOG(VIDEO, "Failed to create a core OpenGL context. Using fallback context.");
    }
  }

  return true;
}

bool cInterfaceWGL::Create(cInterfaceBase* main_context)
{
  cInterfaceWGL* wgl_main_context = static_cast<cInterfaceWGL*>(main_context);

  // WGL does not support surfaceless contexts, so we use a 1x1 pbuffer instead.
  if (!CreatePBuffer(wgl_main_context->m_dc, 1, 1, &m_pbuffer_handle, &m_dc))
    return false;

  m_rc = CreateCoreContext(m_dc, wgl_main_context->m_rc);
  if (!m_rc)
    return false;

  m_core = true;
  return true;
}

std::unique_ptr<cInterfaceBase> cInterfaceWGL::CreateSharedContext()
{
  std::unique_ptr<cInterfaceWGL> context = std::make_unique<cInterfaceWGL>();
  if (!context->Create(this))
  {
    context->Shutdown();
    return nullptr;
  }

  return std::move(context);
}

HGLRC cInterfaceWGL::CreateCoreContext(HDC dc, HGLRC share_context)
{
  if (!wglCreateContextAttribsARB)
  {
    WARN_LOG(VIDEO, "Missing WGL_ARB_create_context extension");
    return nullptr;
  }

  // List of versions to attempt context creation for. (4.5-3.2, geometry shaders is a minimum
  // requirement since we're using core profile)
  static constexpr std::array<std::pair<int, int>, 8> try_versions = {
      {{4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}, {3, 3}, {3, 2}}};

  for (const auto& version : try_versions)
  {
    // Construct list of attributes. Prefer a forward-compatible, core context.
    std::array<int, 5 * 2> attribs = {
        WGL_CONTEXT_PROFILE_MASK_ARB,
        WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
#ifdef _DEBUG
        WGL_CONTEXT_FLAGS_ARB,
        WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB,
#else
        WGL_CONTEXT_FLAGS_ARB,
        WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
#endif
        WGL_CONTEXT_MAJOR_VERSION_ARB,
        version.first,
        WGL_CONTEXT_MINOR_VERSION_ARB,
        version.second,
        0,
        0};

    // Attempt creating this context.
    HGLRC core_context = wglCreateContextAttribsARB(dc, nullptr, attribs.data());
    if (core_context)
    {
      // If we're creating a shared context, share the resources before the context is used.
      if (share_context)
      {
        if (!wglShareLists(share_context, core_context))
        {
          ERROR_LOG(VIDEO, "wglShareLists failed");
          wglDeleteContext(core_context);
          return nullptr;
        }
      }

      INFO_LOG(VIDEO, "WGL: Created a GL %d.%d core context", version.first, version.second);
      return core_context;
    }
  }

  WARN_LOG(VIDEO, "Unable to create a core OpenGL context of an acceptable version");
  return nullptr;
}

bool cInterfaceWGL::CreatePBuffer(HDC onscreen_dc, int width, int height, HANDLE* pbuffer_handle,
                                  HDC* pbuffer_dc)
{
  if (!wglChoosePixelFormatARB || !wglCreatePbufferARB || !wglGetPbufferDCARB ||
      !wglReleasePbufferDCARB || !wglDestroyPbufferARB)
  {
    ERROR_LOG(VIDEO, "Missing WGL_ARB_pbuffer extension");
    return false;
  }

  static constexpr std::array<int, 7 * 2> pf_iattribs = {
      WGL_DRAW_TO_PBUFFER_ARB,
      1,  // Pixel format compatible with pbuffer rendering
      WGL_RED_BITS_ARB,
      0,
      WGL_GREEN_BITS_ARB,
      0,
      WGL_BLUE_BITS_ARB,
      0,
      WGL_DEPTH_BITS_ARB,
      0,
      WGL_STENCIL_BITS_ARB,
      0,
      0,
      0};

  static constexpr std::array<float, 1 * 2> pf_fattribs = {};

  int pixel_format;
  UINT num_pixel_formats;
  if (!wglChoosePixelFormatARB(onscreen_dc, pf_iattribs.data(), pf_fattribs.data(), 1,
                               &pixel_format, &num_pixel_formats))
  {
    ERROR_LOG(VIDEO, "Failed to obtain a compatible pbuffer pixel format");
    return false;
  }

  static constexpr std::array<int, 1 * 2> pb_attribs = {};

  HPBUFFERARB pbuffer =
      wglCreatePbufferARB(onscreen_dc, pixel_format, width, height, pb_attribs.data());
  if (!pbuffer)
  {
    ERROR_LOG(VIDEO, "Failed to create pbuffer");
    return false;
  }

  HDC dc = wglGetPbufferDCARB(pbuffer);
  if (!dc)
  {
    ERROR_LOG(VIDEO, "Failed to get drawing context from pbuffer");
    wglDestroyPbufferARB(pbuffer);
    return false;
  }

  *pbuffer_handle = pbuffer;
  *pbuffer_dc = dc;
  return true;
}

bool cInterfaceWGL::MakeCurrent()
{
  return wglMakeCurrent(m_dc, m_rc) == TRUE;
}

bool cInterfaceWGL::ClearCurrent()
{
  return wglMakeCurrent(m_dc, nullptr) == TRUE;
}

// Update window width, size and etc. Called from Render.cpp
void cInterfaceWGL::Update()
{
  RECT rcWindow;
  GetClientRect(m_window_handle, &rcWindow);

  // Get the new window width and height
  s_backbuffer_width = rcWindow.right - rcWindow.left;
  s_backbuffer_height = rcWindow.bottom - rcWindow.top;
}

// Close backend
void cInterfaceWGL::Shutdown()
{
  if (m_rc)
  {
    if (!wglMakeCurrent(m_dc, nullptr))
      NOTICE_LOG(VIDEO, "Could not release drawing context.");

    if (!wglDeleteContext(m_rc))
      ERROR_LOG(VIDEO, "Attempt to release rendering context failed.");

    m_rc = nullptr;
  }

  if (m_dc)
  {
    if (m_pbuffer_handle)
    {
      wglReleasePbufferDCARB(static_cast<HPBUFFERARB>(m_pbuffer_handle), m_dc);
      m_dc = nullptr;

      wglDestroyPbufferARB(static_cast<HPBUFFERARB>(m_pbuffer_handle));
      m_pbuffer_handle = nullptr;
    }
    else
    {
      if (!ReleaseDC(m_window_handle, m_dc))
        ERROR_LOG(VIDEO, "Attempt to release device context failed.");

      m_dc = nullptr;
    }
  }
}


while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
  {
    if (msg.message == WM_QUIT)
      return FALSE;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

// Refer to the license.txt file included.

#include <windows.h>
#include <array>
#include <string>

#include "Common/GL/GLInterface/WGL.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"

// from wglext.h
#ifndef WGL_ARB_pbuffer
#define WGL_ARB_pbuffer 1
DECLARE_HANDLE(HPBUFFERARB);
#define WGL_DRAW_TO_PBUFFER_ARB 0x202D
#define WGL_MAX_PBUFFER_PIXELS_ARB 0x202E
#define WGL_MAX_PBUFFER_WIDTH_ARB 0x202F
#define WGL_MAX_PBUFFER_HEIGHT_ARB 0x2030
#define WGL_PBUFFER_LARGEST_ARB 0x2033
#define WGL_PBUFFER_WIDTH_ARB 0x2034
#define WGL_PBUFFER_HEIGHT_ARB 0x2035
#define WGL_PBUFFER_LOST_ARB 0x2036
typedef HPBUFFERARB(WINAPI* PFNWGLCREATEPBUFFERARBPROC)(HDC hDC, int iPixelFormat, int iWidth,
                                                        int iHeight, const int* piAttribList);
typedef HDC(WINAPI* PFNWGLGETPBUFFERDCARBPROC)(HPBUFFERARB hPbuffer);
typedef int(WINAPI* PFNWGLRELEASEPBUFFERDCARBPROC)(HPBUFFERARB hPbuffer, HDC hDC);
typedef BOOL(WINAPI* PFNWGLDESTROYPBUFFERARBPROC)(HPBUFFERARB hPbuffer);
typedef BOOL(WINAPI* PFNWGLQUERYPBUFFERARBPROC)(HPBUFFERARB hPbuffer, int iAttribute, int* piValue);
#endif /* WGL_ARB_pbuffer */

#ifndef WGL_ARB_pixel_format
#define WGL_ARB_pixel_format 1
#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
#define WGL_DRAW_TO_WINDOW_ARB 0x2001
#define WGL_DRAW_TO_BITMAP_ARB 0x2002
#define WGL_ACCELERATION_ARB 0x2003
#define WGL_NEED_PALETTE_ARB 0x2004
#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005
#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006
#define WGL_SWAP_METHOD_ARB 0x2007
#define WGL_NUMBER_OVERLAYS_ARB 0x2008
#define WGL_NUMBER_UNDERLAYS_ARB 0x2009
#define WGL_TRANSPARENT_ARB 0x200A
#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037
#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038
#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039
#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A
#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B
#define WGL_SHARE_DEPTH_ARB 0x200C
#define WGL_SHARE_STENCIL_ARB 0x200D
#define WGL_SHARE_ACCUM_ARB 0x200E
#define WGL_SUPPORT_GDI_ARB 0x200F
#define WGL_SUPPORT_OPENGL_ARB 0x2010
#define WGL_DOUBLE_BUFFER_ARB 0x2011
#define WGL_STEREO_ARB 0x2012
#define WGL_PIXEL_TYPE_ARB 0x2013
#define WGL_COLOR_BITS_ARB 0x2014
#define WGL_RED_BITS_ARB 0x2015
#define WGL_RED_SHIFT_ARB 0x2016
#define WGL_GREEN_BITS_ARB 0x2017
#define WGL_GREEN_SHIFT_ARB 0x2018
#define WGL_BLUE_BITS_ARB 0x2019
#define WGL_BLUE_SHIFT_ARB 0x201A
#define WGL_ALPHA_BITS_ARB 0x201B
#define WGL_ALPHA_SHIFT_ARB 0x201C
#define WGL_ACCUM_BITS_ARB 0x201D
#define WGL_ACCUM_RED_BITS_ARB 0x201E
#define WGL_ACCUM_GREEN_BITS_ARB 0x201F
#define WGL_ACCUM_BLUE_BITS_ARB 0x2020
#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021
#define WGL_DEPTH_BITS_ARB 0x2022
#define WGL_STENCIL_BITS_ARB 0x2023
#define WGL_AUX_BUFFERS_ARB 0x2024
#define WGL_NO_ACCELERATION_ARB 0x2025
#define WGL_GENERIC_ACCELERATION_ARB 0x2026
#define WGL_FULL_ACCELERATION_ARB 0x2027
#define WGL_SWAP_EXCHANGE_ARB 0x2028
#define WGL_SWAP_COPY_ARB 0x2029
#define WGL_SWAP_UNDEFINED_ARB 0x202A
#define WGL_TYPE_RGBA_ARB 0x202B
#define WGL_TYPE_COLORINDEX_ARB 0x202C
typedef BOOL(WINAPI* PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC hdc, int iPixelFormat,
                                                          int iLayerPlane, UINT nAttributes,
                                                          const int* piAttributes, int* piValues);
typedef BOOL(WINAPI* PFNWGLGETPIXELFORMATATTRIBFVARBPROC)(HDC hdc, int iPixelFormat,
                                                          int iLayerPlane, UINT nAttributes,
                                                          const int* piAttributes, FLOAT* pfValues);
typedef BOOL(WINAPI* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList,
                                                     const FLOAT* pfAttribFList, UINT nMaxFormats,
                                                     int* piFormats, UINT* nNumFormats);
#endif /* WGL_ARB_pixel_format */

#ifndef WGL_ARB_create_context
#define WGL_ARB_create_context 1
#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001
#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093
#define WGL_CONTEXT_FLAGS_ARB 0x2094
#define ERROR_INVALID_VERSION_ARB 0x2095
typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShareContext,
                                                         const int* attribList);
#endif /* WGL_ARB_create_context */

#ifndef WGL_ARB_create_context_profile
#define WGL_ARB_create_context_profile 1
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
#define ERROR_INVALID_PROFILE_ARB 0x2096
#endif /* WGL_ARB_create_context_profile */

#ifndef WGL_EXT_swap_control
#define WGL_EXT_swap_control 1
typedef BOOL(WINAPI* PFNWGLSWAPINTERVALEXTPROC)(int interval);
typedef int(WINAPI* PFNWGLGETSWAPINTERVALEXTPROC)(void);
#endif /* WGL_EXT_swap_control */

// Persistent pointers
static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = nullptr;
static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = nullptr;
static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = nullptr;
static PFNWGLCREATEPBUFFERARBPROC wglCreatePbufferARB = nullptr;
static PFNWGLGETPBUFFERDCARBPROC wglGetPbufferDCARB = nullptr;
static PFNWGLRELEASEPBUFFERDCARBPROC wglReleasePbufferDCARB = nullptr;
static PFNWGLDESTROYPBUFFERARBPROC wglDestroyPbufferARB = nullptr;

static void LoadWGLExtensions()
{
  wglSwapIntervalEXT = reinterpret_cast<PFNWGLSWAPINTERVALEXTPROC>(
      GLInterface->GetFuncAddress("wglSwapIntervalEXT"));
  wglCreateContextAttribsARB = reinterpret_cast<PFNWGLCREATECONTEXTATTRIBSARBPROC>(
      wglGetProcAddress("wglCreateContextAttribsARB"));
  wglChoosePixelFormatARB = reinterpret_cast<PFNWGLCHOOSEPIXELFORMATARBPROC>(
      wglGetProcAddress("wglChoosePixelFormatARB"));
  wglCreatePbufferARB =
      reinterpret_cast<PFNWGLCREATEPBUFFERARBPROC>(wglGetProcAddress("wglCreatePbufferARB"));
  wglGetPbufferDCARB =
      reinterpret_cast<PFNWGLGETPBUFFERDCARBPROC>(wglGetProcAddress("wglGetPbufferDCARB"));
  wglReleasePbufferDCARB =
      reinterpret_cast<PFNWGLRELEASEPBUFFERDCARBPROC>(wglGetProcAddress("wglReleasePbufferDCARB"));
  wglDestroyPbufferARB =
      reinterpret_cast<PFNWGLDESTROYPBUFFERARBPROC>(wglGetProcAddress("wglGetPbufferDCARB"));
}

static void ClearWGLExtensionPointers()
{
  wglSwapIntervalEXT = nullptr;
  wglCreateContextAttribsARB = nullptr;
  wglChoosePixelFormatARB = nullptr;
  wglCreatePbufferARB = nullptr;
  wglGetPbufferDCARB = nullptr;
  wglReleasePbufferDCARB = nullptr;
  wglDestroyPbufferARB = nullptr;
}

void cInterfaceWGL::SwapInterval(int Interval)
{
  if (wglSwapIntervalEXT)
    wglSwapIntervalEXT(Interval);
  else
    ERROR_LOG(VIDEO, "No support for SwapInterval (framerate clamped to monitor refresh rate).");
}
void cInterfaceWGL::Swap()
{
  SwapBuffers(m_dc);
}

void* cInterfaceWGL::GetFuncAddress(const std::string& name)
{
  FARPROC func = wglGetProcAddress(name.c_str());
  if (func == nullptr)
  {
    // Using GetModuleHandle here is okay, since we import functions from opengl32.dll, it's
    // guaranteed to be loaded.
    HMODULE opengl_module = GetModuleHandle(TEXT("opengl32.dll"));
    func = GetProcAddress(opengl_module, name.c_str());
  }

  return func;
}

// Draw messages on top of the screen
bool cInterfaceWGL::PeekMessages()
{
  // TODO: peekmessage
  MSG msg;

  return TRUE;
}

// Create rendering window.
bool cInterfaceWGL::Create(void* window_handle, bool stereo, bool core)
{
  if (!window_handle)
    return false;

  RECT window_rect = {};
  m_window_handle = reinterpret_cast<HWND>(window_handle);
  if (!GetClientRect(m_window_handle, &window_rect))
    return false;

  // Clear extension function pointers before creating the first context.
  ClearWGLExtensionPointers();

  // Control window size and picture scaling
  int twidth = window_rect.right - window_rect.left;
  int theight = window_rect.bottom - window_rect.top;
  s_backbuffer_width = twidth;
  s_backbuffer_height = theight;

  const DWORD stereo_flag = stereo ? PFD_STEREO : 0;
  static const PIXELFORMATDESCRIPTOR pfd = {
      sizeof(PIXELFORMATDESCRIPTOR),  // Size Of This Pixel Format Descriptor
      1,                              // Version Number
      PFD_DRAW_TO_WINDOW |            // Format Must Support Window
          PFD_SUPPORT_OPENGL |        // Format Must Support OpenGL
          PFD_DOUBLEBUFFER |          // Must Support Double Buffering
          stereo_flag,                // Could Support Quad Buffering
      PFD_TYPE_RGBA,                  // Request An RGBA Format
      32,                             // Select Our Color Depth
      0,
      0, 0, 0, 0, 0,   // Color Bits Ignored
      0,               // 8bit Alpha Buffer
      0,               // Shift Bit Ignored
      0,               // No Accumulation Buffer
      0, 0, 0, 0,      // Accumulation Bits Ignored
      0,               // 0Bit Z-Buffer (Depth Buffer)
      0,               // 0bit Stencil Buffer
      0,               // No Auxiliary Buffer
      PFD_MAIN_PLANE,  // Main Drawing Layer
      0,               // Reserved
      0, 0, 0          // Layer Masks Ignored
  };

  m_dc = GetDC(m_window_handle);
  if (!m_dc)
  {
    PanicAlert("(1) Can't create an OpenGL Device context. Fail.");
    return false;
  }

  int pixel_format = ChoosePixelFormat(m_dc, &pfd);
  if (!pixel_format)
  {
    PanicAlert("(2) Can't find a suitable PixelFormat.");
    return false;
  }

  if (!SetPixelFormat(m_dc, pixel_format, &pfd))
  {
    PanicAlert("(3) Can't set the PixelFormat.");
    return false;
  }

  m_rc = wglCreateContext(m_dc);
  if (!m_rc)
  {
    PanicAlert("(4) Can't create an OpenGL rendering context.");
    return false;
  }

  // WGL only supports desktop GL, for now.
  if (s_opengl_mode == GLInterfaceMode::MODE_DETECT)
    s_opengl_mode = GLInterfaceMode::MODE_OPENGL;

  if (core)
  {
    // Make the fallback context current, temporarily.
    // This is because we need an active context to use wglCreateContextAttribsARB.
    if (!wglMakeCurrent(m_dc, m_rc))
    {
      PanicAlert("(5) Can't make dummy WGL context current.");
      return false;
    }

    // Load WGL extension function pointers.
    LoadWGLExtensions();

    // Attempt creating a core context.
    HGLRC core_context = CreateCoreContext(m_dc, nullptr);

    // Switch out the temporary context before continuing, regardless of whether we got a core
    // context. If we didn't get a core context, the caller expects that the context is not current.
    if (!wglMakeCurrent(m_dc, nullptr))
    {
      PanicAlert("(6) Failed to switch out temporary context");
      return false;
    }

    // If we got a core context, destroy and replace the temporary context.
    if (core_context)
    {
      wglDeleteContext(m_rc);
      m_rc = core_context;
      m_core = true;
    }
    else
    {
      WARN_LOG(VIDEO, "Failed to create a core OpenGL context. Using fallback context.");
    }
  }

  return true;
}

bool cInterfaceWGL::Create(cInterfaceBase* main_context)
{
  cInterfaceWGL* wgl_main_context = static_cast<cInterfaceWGL*>(main_context);

  // WGL does not support surfaceless contexts, so we use a 1x1 pbuffer instead.
  if (!CreatePBuffer(wgl_main_context->m_dc, 1, 1, &m_pbuffer_handle, &m_dc))
    return false;

  m_rc = CreateCoreContext(m_dc, wgl_main_context->m_rc);
  if (!m_rc)
    return false;

  m_core = true;
  return true;
}

std::unique_ptr<cInterfaceBase> cInterfaceWGL::CreateSharedContext()
{
  std::unique_ptr<cInterfaceWGL> context = std::make_unique<cInterfaceWGL>();
  if (!context->Create(this))
  {
    context->Shutdown();
    return nullptr;
  }

  return std::move(context);
}

HGLRC cInterfaceWGL::CreateCoreContext(HDC dc, HGLRC share_context)
{
  if (!wglCreateContextAttribsARB)
  {
    WARN_LOG(VIDEO, "Missing WGL_ARB_create_context extension");
    return nullptr;
  }

  // List of versions to attempt context creation for. (4.5-3.2, geometry shaders is a minimum
  // requirement since we're using core profile)
  static constexpr std::array<std::pair<int, int>, 8> try_versions = {
      {{4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}, {3, 3}, {3, 2}}};

  for (const auto& version : try_versions)
  {
    // Construct list of attributes. Prefer a forward-compatible, core context.
    std::array<int, 5 * 2> attribs = {
        WGL_CONTEXT_PROFILE_MASK_ARB,
        WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
#ifdef _DEBUG
        WGL_CONTEXT_FLAGS_ARB,
        WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB,
#else
        WGL_CONTEXT_FLAGS_ARB,
        WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
#endif
        WGL_CONTEXT_MAJOR_VERSION_ARB,
        version.first,
        WGL_CONTEXT_MINOR_VERSION_ARB,
        version.second,
        0,
        0};

    // Attempt creating this context.
    HGLRC core_context = wglCreateContextAttribsARB(dc, nullptr, attribs.data());
    if (core_context)
    {
      // If we're creating a shared context, share the resources before the context is used.
      if (share_context)
      {
        if (!wglShareLists(share_context, core_context))
        {
          ERROR_LOG(VIDEO, "wglShareLists failed");
          wglDeleteContext(core_context);
          return nullptr;
        }
      }

      INFO_LOG(VIDEO, "WGL: Created a GL %d.%d core context", version.first, version.second);
      return core_context;
    }
  }

  WARN_LOG(VIDEO, "Unable to create a core OpenGL context of an acceptable version");
  return nullptr;
}

bool cInterfaceWGL::CreatePBuffer(HDC onscreen_dc, int width, int height, HANDLE* pbuffer_handle,
                                  HDC* pbuffer_dc)
{
  if (!wglChoosePixelFormatARB || !wglCreatePbufferARB || !wglGetPbufferDCARB ||
      !wglReleasePbufferDCARB || !wglDestroyPbufferARB)
  {
    ERROR_LOG(VIDEO, "Missing WGL_ARB_pbuffer extension");
    return false;
  }

  static constexpr std::array<int, 7 * 2> pf_iattribs = {
      WGL_DRAW_TO_PBUFFER_ARB,
      1,  // Pixel format compatible with pbuffer rendering
      WGL_RED_BITS_ARB,
      0,
      WGL_GREEN_BITS_ARB,
      0,
      WGL_BLUE_BITS_ARB,
      0,
      WGL_DEPTH_BITS_ARB,
      0,
      WGL_STENCIL_BITS_ARB,
      0,
      0,
      0};

  static constexpr std::array<float, 1 * 2> pf_fattribs = {};

  int pixel_format;
  UINT num_pixel_formats;
  if (!wglChoosePixelFormatARB(onscreen_dc, pf_iattribs.data(), pf_fattribs.data(), 1,
                               &pixel_format, &num_pixel_formats))
  {
    ERROR_LOG(VIDEO, "Failed to obtain a compatible pbuffer pixel format");
    return false;
  }

  static constexpr std::array<int, 1 * 2> pb_attribs = {};

  HPBUFFERARB pbuffer =
      wglCreatePbufferARB(onscreen_dc, pixel_format, width, height, pb_attribs.data());
  if (!pbuffer)
  {
    ERROR_LOG(VIDEO, "Failed to create pbuffer");
    return false;
  }

  HDC dc = wglGetPbufferDCARB(pbuffer);
  if (!dc)
  {
    ERROR_LOG(VIDEO, "Failed to get drawing context from pbuffer");
    wglDestroyPbufferARB(pbuffer);
    return false;
  }

  *pbuffer_handle = pbuffer;
  *pbuffer_dc = dc;
  return true;
}

bool cInterfaceWGL::MakeCurrent()
{
  return wglMakeCurrent(m_dc, m_rc) == TRUE;
}

bool cInterfaceWGL::ClearCurrent()
{
  return wglMakeCurrent(m_dc, nullptr) == TRUE;
}

// Update window width, size and etc. Called from Render.cpp
void cInterfaceWGL::Update()
{
  RECT rcWindow;
  GetClientRect(m_window_handle, &rcWindow);

  // Get the new window width and height
  s_backbuffer_width = rcWindow.right - rcWindow.left;
  s_backbuffer_height = rcWindow.bottom - rcWindow.top;
}

// Close backend
void cInterfaceWGL::Shutdown()
{
  if (m_rc)
  {
    if (!wglMakeCurrent(m_dc, nullptr))
      NOTICE_LOG(VIDEO, "Could not release drawing context.");

    if (!wglDeleteContext(m_rc))
      ERROR_LOG(VIDEO, "Attempt to release rendering context failed.");

    m_rc = nullptr;
  }

  if (m_dc)
  {
    if (m_pbuffer_handle)
    {
      wglReleasePbufferDCARB(static_cast<HPBUFFERARB>(m_pbuffer_handle), m_dc);
      m_dc = nullptr;

      wglDestroyPbufferARB(static_cast<HPBUFFERARB>(m_pbuffer_handle));
      m_pbuffer_handle = nullptr;
    }
    else
    {
      if (!ReleaseDC(m_window_handle, m_dc))
        ERROR_LOG(VIDEO, "Attempt to release device context failed.");

      m_dc = nullptr;
    }
  }
}


switch (_frontCrossTexType)
    {
        case TextureResType::LOCAL:
            _frontCrossRenderer->setTexture(cross);
            break;
        case TextureResType::PLIST:
            _frontCrossRenderer->setSpriteFrame(cross);
            break;
        default:
            break;
    }

/****************************************************************************

http://www.cocos2d-x.org

****************************************************************************/

#include "ui/UIAbstractCheckButton.h"
#include "2d/CCSprite.h"
#include "editor-support/cocostudio/CocosStudioExtension.h"
#include "renderer/ccShaders.h"

NS_CC_BEGIN

namespace ui {

static const int BACKGROUNDBOX_RENDERER_Z = (-1);
static const int BACKGROUNDSELECTEDBOX_RENDERER_Z = (-1);
static const int FRONTCROSS_RENDERER_Z = (-1);
static const int BACKGROUNDBOXDISABLED_RENDERER_Z = (-1);
static const int FRONTCROSSDISABLED_RENDERER_Z = (-1);

AbstractCheckButton::AbstractCheckButton():
_backGroundBoxRenderer(nullptr),
_backGroundSelectedBoxRenderer(nullptr),
_frontCrossRenderer(nullptr),
_backGroundBoxDisabledRenderer(nullptr),
_frontCrossDisabledRenderer(nullptr),
_isSelected(true),
_isBackgroundSelectedTextureLoaded(false),
_isBackgroundDisabledTextureLoaded(false),
_isFrontCrossDisabledTextureLoaded(false),
_backGroundTexType(TextureResType::LOCAL),
_backGroundSelectedTexType(TextureResType::LOCAL),
_frontCrossTexType(TextureResType::LOCAL),
_backGroundDisabledTexType(TextureResType::LOCAL),
_frontCrossDisabledTexType(TextureResType::LOCAL),
_zoomScale(0.1f),
_backgroundTextureScaleX(1.0),
_backgroundTextureScaleY(1.0),
_backGroundBoxRendererAdaptDirty(true),
_backGroundSelectedBoxRendererAdaptDirty(true),
_frontCrossRendererAdaptDirty(true),
_backGroundBoxDisabledRendererAdaptDirty(true),
_frontCrossDisabledRendererAdaptDirty(true),
_backGroundFileName(""),
_backGroundSelectedFileName(""),
_frontCrossFileName(""),
_backGroundDisabledFileName(""),
_frontCrossDisabledFileName("")
{
    setTouchEnabled(true);
}

AbstractCheckButton::~AbstractCheckButton()
{
}

bool AbstractCheckButton::init(const std::string& backGround,
                    const std::string& backGroundSelected,
                    const std::string& cross,
                    const std::string& backGroundDisabled,
                    const std::string& frontCrossDisabled,
                    TextureResType texType)
{
    bool ret = true;
    do
    {
        if (!Widget::init())
        {
            ret = false;
            break;
        }

        setSelected(false);
        loadTextures(backGround, backGroundSelected, cross, backGroundDisabled, frontCrossDisabled, texType);
    } while (0);
    return ret;
}

bool AbstractCheckButton::init()
{
    if (Widget::init())
    {
        setSelected(false);
        return true;
    }
    return false;
}

void AbstractCheckButton::initRenderer()
{
    _backGroundBoxRenderer = Sprite::create();
    _backGroundSelectedBoxRenderer = Sprite::create();
    _frontCrossRenderer = Sprite::create();
    _backGroundBoxDisabledRenderer = Sprite::create();
    _frontCrossDisabledRenderer = Sprite::create();

    addProtectedChild(_backGroundBoxRenderer, BACKGROUNDBOX_RENDERER_Z, -1);
    addProtectedChild(_backGroundSelectedBoxRenderer, BACKGROUNDSELECTEDBOX_RENDERER_Z, -1);
    addProtectedChild(_frontCrossRenderer, FRONTCROSS_RENDERER_Z, -1);
    addProtectedChild(_backGroundBoxDisabledRenderer, BACKGROUNDBOXDISABLED_RENDERER_Z, -1);
    addProtectedChild(_frontCrossDisabledRenderer, FRONTCROSSDISABLED_RENDERER_Z, -1);
}

void AbstractCheckButton::loadTextures(const std::string& backGround,
                            const std::string& backGroundSelected,
                            const std::string& cross,
                            const std::string& backGroundDisabled,
                            const std::string& frontCrossDisabled,
                            TextureResType texType)
{
    loadTextureBackGround(backGround,texType);
    loadTextureBackGroundSelected(backGroundSelected,texType);
    loadTextureFrontCross(cross,texType);
    loadTextureBackGroundDisabled(backGroundDisabled,texType);
    loadTextureFrontCrossDisabled(frontCrossDisabled,texType);
}

void AbstractCheckButton::loadTextureBackGround(const std::string& backGround,TextureResType texType)
{
    _backGroundFileName = backGround;

    _backGroundTexType = texType;
    switch (_backGroundTexType)
    {
        case TextureResType::LOCAL:
            _backGroundBoxRenderer->setTexture(backGround);
            break;
        case TextureResType::PLIST:
            _backGroundBoxRenderer->setSpriteFrame(backGround);
            break;
        default:
            break;
    }

    this->setupBackgroundTexture();
}

void AbstractCheckButton::setupBackgroundTexture()
{

    this->updateChildrenDisplayedRGBA();

    updateContentSizeWithTextureSize(_backGroundBoxRenderer->getContentSize());
    _backGroundBoxRendererAdaptDirty = true;
}

void AbstractCheckButton::loadTextureBackGround(SpriteFrame* spriteFrame)
{
    _backGroundBoxRenderer->setSpriteFrame(spriteFrame);
    this->setupBackgroundTexture();
}

void AbstractCheckButton::loadTextureBackGroundSelected(const std::string& backGroundSelected,TextureResType texType)
{
    _backGroundSelectedFileName = backGroundSelected;
    _isBackgroundSelectedTextureLoaded = !backGroundSelected.empty();
    if (!_isBackgroundSelectedTextureLoaded) return;

    _backGroundSelectedTexType = texType;
    switch (_backGroundSelectedTexType)
    {
        case TextureResType::LOCAL:
            _backGroundSelectedBoxRenderer->setTexture(backGroundSelected);
            break;
        case TextureResType::PLIST:
            _backGroundSelectedBoxRenderer->setSpriteFrame(backGroundSelected);
            break;
        default:
            break;
    }
    this->setupBackgroundSelectedTexture();
}

void AbstractCheckButton::loadTextureBackGroundSelected(SpriteFrame* spriteframe)
{
    this->_backGroundSelectedBoxRenderer->setSpriteFrame(spriteframe);
    this->setupBackgroundSelectedTexture();
}

void AbstractCheckButton::setupBackgroundSelectedTexture()
{
    this->updateChildrenDisplayedRGBA();
    _backGroundSelectedBoxRendererAdaptDirty = true;
}

void AbstractCheckButton::loadTextureFrontCross(const std::string& cross,TextureResType texType)
{
    _frontCrossFileName = cross;

    _frontCrossTexType = texType;

    this->setupFrontCrossTexture();
}

void AbstractCheckButton::loadTextureFrontCross(SpriteFrame* spriteFrame)
{
    this->_frontCrossRenderer->setSpriteFrame(spriteFrame);
    this->setupFrontCrossTexture();
}

void AbstractCheckButton::setupFrontCrossTexture()
{
    this->updateChildrenDisplayedRGBA();
    _frontCrossRendererAdaptDirty = true;
}

void AbstractCheckButton::loadTextureBackGroundDisabled(const std::string& backGroundDisabled,TextureResType texType)
{
    _backGroundDisabledFileName = backGroundDisabled;
    _isBackgroundDisabledTextureLoaded = !backGroundDisabled.empty();
    if (!_isBackgroundDisabledTextureLoaded) return;

    _backGroundDisabledTexType = texType;
    switch (_backGroundDisabledTexType)
    {
        case TextureResType::LOCAL:
            _backGroundBoxDisabledRenderer->setTexture(backGroundDisabled);
            break;
        case TextureResType::PLIST:
            _backGroundBoxDisabledRenderer->setSpriteFrame(backGroundDisabled);
            break;
        default:
            break;
    }
    this->setupBackgroundDisable();
}

void AbstractCheckButton::loadTextureBackGroundDisabled(SpriteFrame* spriteframe)
{
    this->_backGroundBoxDisabledRenderer->setSpriteFrame(spriteframe);
    this->setupBackgroundDisable();
}

void AbstractCheckButton::setupBackgroundDisable()
{
    this->updateChildrenDisplayedRGBA();

    _backGroundBoxDisabledRendererAdaptDirty = true;
}

void AbstractCheckButton::loadTextureFrontCrossDisabled(const std::string& frontCrossDisabled,TextureResType texType)
{
    _frontCrossDisabledFileName = frontCrossDisabled;
    _isFrontCrossDisabledTextureLoaded = !frontCrossDisabled.empty();
    if (!_isFrontCrossDisabledTextureLoaded) return;

    _frontCrossDisabledTexType = texType;
    switch (_frontCrossDisabledTexType)
    {
        case TextureResType::LOCAL:
            _frontCrossDisabledRenderer->setTexture(frontCrossDisabled);
            break;
        case TextureResType::PLIST:
            _frontCrossDisabledRenderer->setSpriteFrame(frontCrossDisabled);
            break;
        default:
            break;
    }
    this->setupFrontCrossDisableTexture();

}

void AbstractCheckButton::loadTextureFrontCrossDisabled(SpriteFrame* spriteframe)
{
    this->_frontCrossDisabledRenderer->setSpriteFrame(spriteframe);
    this->setupFrontCrossDisableTexture();
}

void AbstractCheckButton::setupFrontCrossDisableTexture()
{
    this->updateChildrenDisplayedRGBA();
    _frontCrossDisabledRendererAdaptDirty = true;
}

void AbstractCheckButton::onPressStateChangedToNormal()
{
    _backGroundBoxRenderer->setVisible(true);
    _backGroundSelectedBoxRenderer->setVisible(false);
    _backGroundBoxDisabledRenderer->setVisible(false);
    _frontCrossDisabledRenderer->setVisible(false);

    auto isETC1 = _backGroundBoxRenderer->getTexture() && _backGroundBoxRenderer->getTexture()->getAlphaTextureName();
    _backGroundBoxRenderer->setProgramState((isETC1) ? backend::ProgramType::ETC1 : backend::ProgramType::POSITION_TEXTURE_COLOR);

    isETC1 = _frontCrossRenderer->getTexture() && _frontCrossRenderer->getTexture()->getAlphaTextureName();
    _frontCrossRenderer->setProgramState((isETC1) ? backend::ProgramType::ETC1 : backend::ProgramType::POSITION_TEXTURE_COLOR);

    _backGroundBoxRenderer->setScale(_backgroundTextureScaleX, _backgroundTextureScaleY);
    _frontCrossRenderer->setScale(_backgroundTextureScaleX, _backgroundTextureScaleY);

    if (_isSelected)
    {
        _frontCrossRenderer->setVisible(true);
        _frontCrossRendererAdaptDirty = true;
    }
}

void AbstractCheckButton::onPressStateChangedToPressed()
{
    auto isETC1 = _backGroundBoxRenderer->getTexture() && _backGroundBoxRenderer->getTexture()->getAlphaTextureName();
    _backGroundBoxRenderer->setProgramState((isETC1) ? backend::ProgramType::ETC1 : backend::ProgramType::POSITION_TEXTURE_COLOR);

    isETC1 = _frontCrossRenderer->getTexture() && _frontCrossRenderer->getTexture()->getAlphaTextureName();
    _frontCrossRenderer->setProgramState((isETC1) ? backend::ProgramType::ETC1 : backend::ProgramType::POSITION_TEXTURE_COLOR);

    if (!_isBackgroundSelectedTextureLoaded)
    {
        _backGroundBoxRenderer->setScale(_backgroundTextureScaleX + _zoomScale,
                                         _backgroundTextureScaleY + _zoomScale);
        _frontCrossRenderer->setScale(_backgroundTextureScaleX + _zoomScale,
                                      _backgroundTextureScaleY + _zoomScale);
    }
    else
    {
        _backGroundBoxRenderer->setVisible(false);
        _backGroundSelectedBoxRenderer->setVisible(true);
        _backGroundBoxDisabledRenderer->setVisible(false);
        _frontCrossDisabledRenderer->setVisible(false);
    }
}

void AbstractCheckButton::onPressStateChangedToDisabled()
{
    if (!_isBackgroundDisabledTextureLoaded
        || !_isFrontCrossDisabledTextureLoaded)
    {
        auto isETC1 = _backGroundBoxRenderer->getTexture() && _backGroundBoxRenderer->getTexture()->getAlphaTextureName();
        _backGroundBoxRenderer->setProgramState((isETC1) ? backend::ProgramType::ETC1_GRAY : backend::ProgramType::GRAY_SCALE);

        isETC1 = _frontCrossRenderer->getTexture() && _frontCrossRenderer->getTexture()->getAlphaTextureName();
        _frontCrossRenderer->setProgramState((isETC1) ? backend::ProgramType::ETC1_GRAY : backend::ProgramType::GRAY_SCALE);
    }
    else
    {
        _backGroundBoxRenderer->setVisible(false);
        _backGroundBoxDisabledRenderer->setVisible(true);
    }

    _backGroundSelectedBoxRenderer->setVisible(false);
    _frontCrossRenderer->setVisible(false);
    _backGroundBoxRenderer->setScale(_backgroundTextureScaleX, _backgroundTextureScaleY);
    _frontCrossRenderer->setScale(_backgroundTextureScaleX, _backgroundTextureScaleY);

    if (_isSelected)
    {
        _frontCrossDisabledRenderer->setVisible(true);
        _frontCrossDisabledRendererAdaptDirty = true;
    }
}

void AbstractCheckButton::setZoomScale(float scale)
{
    _zoomScale = scale;
}

float AbstractCheckButton::getZoomScale()const
{
    return _zoomScale;
}

void AbstractCheckButton::setSelected(bool selected)
{
    if (selected == _isSelected)
    {
        return;
    }
    _isSelected = selected;
    _frontCrossRenderer->setVisible(_isSelected);
}

bool AbstractCheckButton::isSelected()const
{
    return _isSelected;
}

void AbstractCheckButton::onSizeChanged()
{
    Widget::onSizeChanged();
    _backGroundBoxRendererAdaptDirty = true;
    _backGroundSelectedBoxRendererAdaptDirty = true;
    _frontCrossRendererAdaptDirty = true;
    _backGroundBoxDisabledRendererAdaptDirty = true;
    _frontCrossDisabledRendererAdaptDirty = true;
}

void AbstractCheckButton::adaptRenderers()
{
    if (_backGroundBoxRendererAdaptDirty)
    {
        backGroundTextureScaleChangedWithSize();
        _backGroundBoxRendererAdaptDirty = false;
    }
    if (_backGroundSelectedBoxRendererAdaptDirty)
    {
        backGroundSelectedTextureScaleChangedWithSize();
        _backGroundSelectedBoxRendererAdaptDirty = false;
    }
    if (_frontCrossRendererAdaptDirty)
    {
        frontCrossTextureScaleChangedWithSize();
        _frontCrossRendererAdaptDirty = false;
    }
    if (_backGroundBoxDisabledRendererAdaptDirty)
    {
        backGroundDisabledTextureScaleChangedWithSize();
        _backGroundBoxDisabledRendererAdaptDirty = false;
    }
    if (_frontCrossDisabledRendererAdaptDirty)
    {
        frontCrossDisabledTextureScaleChangedWithSize();
        _frontCrossDisabledRendererAdaptDirty = false;
    }
}

Size AbstractCheckButton::getVirtualRendererSize() const
{
    return _backGroundBoxRenderer->getContentSize();
}

Node* AbstractCheckButton::getVirtualRenderer()
{
    return _backGroundBoxRenderer;
}

void AbstractCheckButton::backGroundTextureScaleChangedWithSize()
{
    if (_ignoreSize)
    {
        _backGroundBoxRenderer->setScale(1.0f);
        _backgroundTextureScaleX = _backgroundTextureScaleY = 1.0f;
    }
    else
    {
        Size textureSize = _backGroundBoxRenderer->getContentSize();
        if (textureSize.width <= 0.0f || textureSize.height <= 0.0f)
        {
            _backGroundBoxRenderer->setScale(1.0f);
            _backgroundTextureScaleX = _backgroundTextureScaleY = 1.0f;
            return;
        }
        float scaleX = _contentSize.width / textureSize.width;
        float scaleY = _contentSize.height / textureSize.height;
        _backgroundTextureScaleX = scaleX;
        _backgroundTextureScaleY = scaleY;
        _backGroundBoxRenderer->setScaleX(scaleX);
        _backGroundBoxRenderer->setScaleY(scaleY);
    }
    _backGroundBoxRenderer->setPosition(_contentSize.width / 2, _contentSize.height / 2);
}

void AbstractCheckButton::backGroundSelectedTextureScaleChangedWithSize()
{
    if (_ignoreSize)
    {
        _backGroundSelectedBoxRenderer->setScale(1.0f);
    }
    else
    {
        Size textureSize = _backGroundSelectedBoxRenderer->getContentSize();
        if (textureSize.width <= 0.0f || textureSize.height <= 0.0f)
        {
            _backGroundSelectedBoxRenderer->setScale(1.0f);
            return;
        }
        float scaleX = _contentSize.width / textureSize.width;
        float scaleY = _contentSize.height / textureSize.height;
        _backGroundSelectedBoxRenderer->setScaleX(scaleX);
        _backGroundSelectedBoxRenderer->setScaleY(scaleY);
    }
    _backGroundSelectedBoxRenderer->setPosition(_contentSize.width / 2, _contentSize.height / 2);
}

void AbstractCheckButton::frontCrossTextureScaleChangedWithSize()
{
    if (_ignoreSize)
    {
        _frontCrossRenderer->setScale(1.0f);
    }
    else
    {
        Size textureSize = _frontCrossRenderer->getContentSize();
        if (textureSize.width <= 0.0f || textureSize.height <= 0.0f)
        {
            _frontCrossRenderer->setScale(1.0f);
            return;
        }
        float scaleX = _contentSize.width / textureSize.width;
        float scaleY = _contentSize.height / textureSize.height;
        _frontCrossRenderer->setScaleX(scaleX);
        _frontCrossRenderer->setScaleY(scaleY);
    }
    _frontCrossRenderer->setPosition(_contentSize.width / 2, _contentSize.height / 2);
}

void AbstractCheckButton::backGroundDisabledTextureScaleChangedWithSize()
{
    if (_ignoreSize)
    {
        _backGroundBoxDisabledRenderer->setScale(1.0f);
    }
    else
    {
        Size textureSize = _backGroundBoxDisabledRenderer->getContentSize();
        if (textureSize.width <= 0.0f || textureSize.height <= 0.0f)
        {
            _backGroundBoxDisabledRenderer->setScale(1.0f);
            return;
        }
        float scaleX = _contentSize.width / textureSize.width;
        float scaleY = _contentSize.height / textureSize.height;
        _backGroundBoxDisabledRenderer->setScaleX(scaleX);
        _backGroundBoxDisabledRenderer->setScaleY(scaleY);
    }
    _backGroundBoxDisabledRenderer->setPosition(_contentSize.width / 2, _contentSize.height / 2);
}

void AbstractCheckButton::frontCrossDisabledTextureScaleChangedWithSize()
{
    if (_ignoreSize)
    {
        _frontCrossDisabledRenderer->setScale(1.0f);
    }
    else
    {
        Size textureSize = _frontCrossDisabledRenderer->getContentSize();
        if (textureSize.width <= 0.0f || textureSize.height <= 0.0f)
        {
            _frontCrossDisabledRenderer->setScale(1.0f);
            return;
        }
        float scaleX = _contentSize.width / textureSize.width;
        float scaleY = _contentSize.height / textureSize.height;
        _frontCrossDisabledRenderer->setScaleX(scaleX);
        _frontCrossDisabledRenderer->setScaleY(scaleY);
    }
    _frontCrossDisabledRenderer->setPosition(_contentSize.width / 2, _contentSize.height / 2);
}

void AbstractCheckButton::copySpecialProperties(Widget *widget)
{
    AbstractCheckButton* abstractCheckButton = dynamic_cast<AbstractCheckButton*>(widget);
    if (abstractCheckButton)
    {
        loadTextureBackGround(abstractCheckButton->_backGroundBoxRenderer->getSpriteFrame());
        loadTextureBackGroundSelected(abstractCheckButton->_backGroundSelectedBoxRenderer->getSpriteFrame());
        loadTextureFrontCross(abstractCheckButton->_frontCrossRenderer->getSpriteFrame());
        loadTextureBackGroundDisabled(abstractCheckButton->_backGroundBoxDisabledRenderer->getSpriteFrame());
        loadTextureFrontCrossDisabled(abstractCheckButton->_frontCrossDisabledRenderer->getSpriteFrame());
        setSelected(abstractCheckButton->_isSelected);
        _zoomScale = abstractCheckButton->_zoomScale;
        _backgroundTextureScaleX = abstractCheckButton->_backgroundTextureScaleX;
        _backgroundTextureScaleY = abstractCheckButton->_backgroundTextureScaleY;
        _isBackgroundSelectedTextureLoaded = abstractCheckButton->_isBackgroundSelectedTextureLoaded;
        _isBackgroundDisabledTextureLoaded = abstractCheckButton->_isBackgroundDisabledTextureLoaded;
        _isFrontCrossDisabledTextureLoaded = abstractCheckButton->_isFrontCrossDisabledTextureLoaded;
    }
}

ResourceData AbstractCheckButton::getBackNormalFile()
{
    ResourceData rData;
    rData.type = (int)_backGroundTexType;
    rData.file = _backGroundFileName;
    return rData;
}

ResourceData AbstractCheckButton::getBackPressedFile()
{
    ResourceData rData;
    rData.type = (int)_backGroundSelectedTexType;
    rData.file = _backGroundSelectedFileName;
    return rData;
}

ResourceData AbstractCheckButton::getBackDisabledFile()
{
    ResourceData rData;
    rData.type = (int)_backGroundDisabledTexType;
    rData.file = _backGroundDisabledFileName;
    return rData;
}

ResourceData AbstractCheckButton::getCrossNormalFile()
{
    ResourceData rData;
    rData.type = (int)_frontCrossTexType;
    rData.file = _frontCrossFileName;
    return rData;
}

ResourceData AbstractCheckButton::getCrossDisabledFile()
{
    ResourceData rData;
    rData.type = (int)_frontCrossDisabledTexType;
    rData.file = _frontCrossDisabledFileName;
    return rData;
}

}

NS_CC_END


switch (getFileFormat())
    {
    case FILEFORMAT_TMX:
        return ".scene";
    case FILEFORMAT_PNG:
    case FILEFORMAT_RAW:
        if (_normalMap)
            return ".png";

    default:
        return ".gpb";
    }

#include "Base.h"

#include "EncoderArguments.h"
#include "StringUtil.h"

#ifdef WIN32
    #define PATH_MAX    _MAX_PATH
    #define realpath(A,B)    _fullpath(B,A,PATH_MAX)
#endif

// The encoder version number should be incremented when a feature is added to the encoder.
// The encoder version is not the same as the GPB version.
#define ENCODER_VERSION "3.0.0"
#define HEIGHTMAP_SIZE_MAX 2049

namespace gplayencoder
{

static EncoderArguments* __instance;

extern int __logVerbosity = 1;

EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
    _normalMap(false),
    _parseError(false),
    _fontPreview(false),
    _fontFormat(Font::BITMAP),
    _textOutput(false),
    _optimizeAnimations(false),
    _animationGrouping(ANIMATIONGROUP_PROMPT),
    _outputMaterial(false),
    _generateTextureGutter(false)
{
    __instance = this;

    memset(_heightmapResolution, 0, sizeof(int) * 2);

    if (argc > 1)
    {
        // read the options
        std::vector<std::string> arguments;
        for (size_t i = 1; i < argc; ++i)
        {
            arguments.push_back(argv[i]);
        }
        size_t index = 0;
        for (size_t i = 0; i < arguments.size(); ++i)
        {
            if (arguments[i][0] == '-')
            {
                readOption(arguments, &i);
                index = i + 1;
            }
        }
        if (arguments.size() - index == 2)
        {
            setInputfilePath(arguments[index]);
            setOutputfilePath(arguments[index + 1]);
        }
        else if (arguments.size() - index == 1)
        {
            setInputfilePath(arguments[index]);
        }
    }
    else
    {
        _parseError = true;
    }
}

EncoderArguments::~EncoderArguments(void)
{
}

EncoderArguments* EncoderArguments::getInstance()
{
    return __instance;
}

const std::string& EncoderArguments::getFilePath() const
{
    return _filePath;
}

const std::string EncoderArguments::getFileDirPath() const
{
    int pos = _filePath.find_last_of('/');
    return (pos == -1 ? _filePath : _filePath.substr(0, pos));
}

const char* EncoderArguments::getFilePathPointer() const
{
    return _filePath.c_str();
}

const std::string EncoderArguments::getFileName() const
{
    int pos = _filePath.find_last_of('/');
    return (pos == -1 ? _filePath : _filePath.substr(pos + 1));
}

std::string EncoderArguments::getOutputDirPath() const
{
    if (_fileOutputPath.size() > 0)
    {
        int pos = _fileOutputPath.find_last_of('/');
        return (pos == -1 ? _fileOutputPath : _fileOutputPath.substr(0, pos));
    }
    else
    {
        int pos = _filePath.find_last_of('/');
        return (pos == -1 ? _filePath : _filePath.substr(0, pos));
    }
}

std::string EncoderArguments::getOutputFileExtension() const
{

}

std::string EncoderArguments::getOutputFilePath() const
{
    if (_fileOutputPath.size() > 0)
    {
        // Output file explicitly set
        return _fileOutputPath;
    }
    else
    {
        // Generate an output file path
        int pos = _filePath.find_last_of('.');
        std::string outputFilePath(pos > 0 ? _filePath.substr(0, pos) : _filePath);

        // Modify the original file name if the output extension can be the same as the input
        if (_normalMap)
        {
            outputFilePath.append("_normalmap");
        }

        outputFilePath.append(getOutputFileExtension());
        return outputFilePath;
    }
}

const std::vector<std::string>& EncoderArguments::getGroupAnimationNodeId() const
{
    return _groupAnimationNodeId;
}

const std::vector<std::string>& EncoderArguments::getGroupAnimationAnimationId() const
{
    return _groupAnimationAnimationId;
}

bool EncoderArguments::containsGroupNodeId(const std::string& nodeId) const
{
    return find(_groupAnimationNodeId.begin(), _groupAnimationNodeId.end(), nodeId) != _groupAnimationNodeId.end();
}

const std::string EncoderArguments::getAnimationId(const std::string& nodeId) const
{
    for (size_t i = 0, size = _groupAnimationNodeId.size(); i < size; ++i)
    {
        if (_groupAnimationNodeId[i].compare(nodeId) == 0)
        {
            return _groupAnimationAnimationId[i];
        }
    }
    return "";
}

EncoderArguments::AnimationGroupOption EncoderArguments::getAnimationGrouping() const
{
    return _animationGrouping;
}

const std::vector<EncoderArguments::HeightmapOption>& EncoderArguments::getHeightmapOptions() const
{
    return _heightmaps;
}

unsigned int EncoderArguments::tangentBinormalIdCount() const
{
    return _tangentBinormalId.size();
}

bool EncoderArguments::isGenerateTangentBinormalId(const std::string& id) const
{
    return _tangentBinormalId.find(id) != _tangentBinormalId.end();
}

bool EncoderArguments::normalMapGeneration() const
{
    return _normalMap;
}

void EncoderArguments::getHeightmapResolution(int* x, int* y) const
{
    *x = _heightmapResolution[0];
    *y = _heightmapResolution[1];
}

const Vector3& EncoderArguments::getHeightmapWorldSize() const
{
    return _heightmapWorldSize;
}

bool EncoderArguments::parseErrorOccured() const
{
    return _parseError;
}

bool EncoderArguments::fileExists() const
{
    if (_filePath.length() > 0)
    {
        struct stat buf;
        if (stat(_filePath.c_str(), &buf) != -1)
        {
            return true;
        }
    }
    return false;
}

void splitString(const char* str, std::vector<std::string>* tokens)
{
    // Split node id list into tokens
    unsigned int length = strlen(str);
    char* temp = new char[length + 1];
    strcpy(temp, str);
    char* tok = strtok(temp, ",");
    while (tok)
    {
        tokens->push_back(tok);
        tok = strtok(NULL, ",");
    }
    delete[] temp;
}

void EncoderArguments::printUsage() const
{
    LOG(1, "Usage: gameplay-encoder [options] <input filepath> <output filepath>\n\n" \
    "Encoder version: " ENCODER_VERSION "\n\n" \
    "Supported file extensions:\n" \
    "  .fbx\t(FBX scenes)\n" \
    "  .ttf\t(TrueType fonts)\n" \
    "\n" \
    "General options:\n" \
    "  -v <verbosity>\tVerbosity level (0-4).\n" \
    "\n" \
    "FBX file options:\n" \
    "  -i <id>\tFilter by node ID.\n" \
    "  -t\t\tWrite text/xml.\n" \
    "  -g:auto\tAutomatically group animation channels into a new animation.\n" \
    "  -g:none\tDo not prompt to group animations.\n" \
    "  -g <node id> <animation id>\n" \
        "\t\tGroup all animation channels targeting the nodes into a \n" \
        "\t\tnew animation.\n" \
    "  -m\t\tOutput material file for scene.\n" \
    "  -tb <node id>\n" \
        "\t\tGenerates tangents and binormals for the given node.\n" \
    "  -oa\n" \
        "\t\tOptimizes animations by analyzing animation channel data and\n" \
        "\t\tremoving any channels that contain default/identity values\n" \
        "\t\tand removing any duplicate contiguous keyframes, which are \n" \
        "\t\tcommon when exporting baked animation data.\n" \
    "  -h <size> \"<node ids>\" <filename>\n" \
        "\t\tGenerates a single heightmap image using meshes from the \n" \
        "\t\tspecified nodes. \n" \
        "\t\t<size> is two comma-separated numbers in the format \"X,Y\", \n" \
        "\t\tindicating the dimensions of the produced heightmap image.\n" \
        "\t\t<node ids> should be in quotes with a space between each id.\n" \
        "\t\tFilename is the name of the image (PNG) to be saved.\n" \
        "\t\tMultiple -h arguments can be supplied to generate more than one \n" \
        "\t\theightmap. For 24-bit packed height data use -hp instead of -h.\n" \
    "\n" \
    "TMX file options:\n" \
    "  -tg\tEnable texture gutter's around tiles. This will modify any referenced\n" \
    "  \ttile sets to add a 1px border around it to prevent seams.\n"
    "  -tg:none\tDo not priduce a texture gutter.\n"
    "\n" \
    "Normal map options:\n" \
        "  -n\t\tGenerate normal map (requires input file of type PNG or RAW)\n" \
        "  -s\t\tSize/resolution of the input heightmap image (required for RAW files)\n" \
        "  -w <size>\tSpecifies the size of an input terrain heightmap file in world\n" \
        "\t\tunits, along the X, Y and Z axes. <size> should be three \n" \
        "\t\tcomma-separated numbers in the format \"X,Y,Z\".  \n" \
        "\t\tThe Y value represents the maximum possible height value of a  \n" \
        "\t\tfull intensity heightmap pixel.\n" \
        "\n" \
        "  \t\tNormal map generation can be used to create object-space normal maps from \n" \
        "  \t\theightmap images. Heightmaps must be in either PNG format (where the \n" \
        "  \t\tintensity of each pixel represents a height value), or in RAW format \n" \
        "  \t\t(8 or 16-bit), which is a common headerless format supported by most \n" \
        "  \t\tterrain generation tools.\n" \
    "\n" \
    "TTF file options:\n" \
    "  -s <sizes>\tComma-separated list of font sizes (in pixels).\n" \
    "  -p\t\tOutput font preview.\n" \
    "  -f\t\tFormat of font. -f:b (BITMAP), -f:d (DISTANCE_FIELD).\n" \
    "\n");
    exit(8);
}

bool EncoderArguments::fontPreviewEnabled() const
{
    return _fontPreview;
}

Font::FontFormat EncoderArguments::getFontFormat() const
{
    return _fontFormat;
}

bool EncoderArguments::textOutputEnabled() const
{
    return _textOutput;
}

bool EncoderArguments::optimizeAnimationsEnabled() const
{
    return _optimizeAnimations;
}

bool EncoderArguments::outputMaterialEnabled() const
{
    return _outputMaterial;
}

bool EncoderArguments::generateTextureGutter() const
{
    return _generateTextureGutter;
}

const char* EncoderArguments::getNodeId() const
{
    if (_nodeId.length() == 0)
    {
        return NULL;
    }
    return _nodeId.c_str();
}

std::vector<unsigned int> EncoderArguments::getFontSizes() const
{
    return _fontSizes;
}

EncoderArguments::FileFormat EncoderArguments::getFileFormat() const
{
    if (_filePath.length() < 5)
    {
        return FILEFORMAT_UNKNOWN;
    }
    // Extract the extension
    std::string ext = "";
    size_t pos = _filePath.find_last_of(".");
    if (pos != std::string::npos)
    {
        ext = _filePath.substr(pos + 1);
    }
    for (size_t i = 0; i < ext.size(); ++i)
        ext[i] = (char)tolower(ext[i]);

    // Match every supported extension with its format constant
    if (ext.compare("fbx") == 0)
    {
        return FILEFORMAT_FBX;
    }
    if (ext.compare("tmx") == 0)
    {
        return FILEFORMAT_TMX;
    }
    if (ext.compare("ttf") == 0)
    {
        return FILEFORMAT_TTF;
    }
    if (ext.compare("otf") == 0)
    {
        return FILEFORMAT_OTF;
    }
    if (ext.compare("gpb") == 0)
    {
        return FILEFORMAT_GPB;
    }
    if (ext.compare("png") == 0)
    {
        return FILEFORMAT_PNG;
    }
    if (ext.compare("raw") == 0)
    {
        return FILEFORMAT_RAW;
    }

    return FILEFORMAT_UNKNOWN;
}

void EncoderArguments::readOption(const std::vector<std::string>& options, size_t* index)
{
    const std::string& str = options[*index];
    if (str.length() == 0 && str[0] != '-')
    {
        return;
    }
    switch (str[1])
    {
    case 'f':
        if (str.compare("-f:b") == 0)
        {
            _fontFormat = Font::BITMAP;
        }
       else  if (str.compare("-f:d") == 0)
        {
            _fontFormat = Font::DISTANCE_FIELD;
        }
        break;
    case 'g':
        if (str.compare("-groupAnimations:auto") == 0 || str.compare("-g:auto") == 0)
        {
            _animationGrouping = ANIMATIONGROUP_AUTO;
        }
        else if (str.compare("-groupAnimations:off") == 0 || str.compare("-g:off") == 0)
        {
            _animationGrouping = ANIMATIONGROUP_OFF;
        }
        else if (str.compare("-groupAnimations") == 0 || str.compare("-g") == 0)
        {
            // read two strings, make sure not to go out of bounds
            if ((*index + 2) >= options.size())
            {
                LOG(1, "Error: -g requires 2 arguments.\n");
                _parseError = true;
                return;
            }
            (*index)++;
            _groupAnimationNodeId.push_back(options[*index]);
            (*index)++;
            _groupAnimationAnimationId.push_back(options[*index]);
        }
        break;
    case 'i':
        // Node ID
        (*index)++;
        if (*index < options.size())
        {
            _nodeId.assign(options[*index]);
        }
        else
        {
            LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
            _parseError = true;
            return;
        }
        break;
    case 'o':
        // Optimization flag
        if (str == "-oa")
        {
            // Optimize animations
            _optimizeAnimations = true;
        }
        break;
    case 'h':
        {
            bool isHighPrecision = str.compare("-hp") == 0;
            if (str.compare("-heightmap") == 0 || str.compare("-h") == 0 || isHighPrecision)
            {
                (*index)++;
                if (*index < (options.size() + 2))
                {
                    _heightmaps.resize(_heightmaps.size() + 1);
                    HeightmapOption& heightmap = _heightmaps.back();

                    heightmap.isHighPrecision = isHighPrecision;

                    // Read heightmap size
                    std::vector<std::string> parts;
                    splitString(options[*index].c_str(), &parts);
                    if (parts.size() != 2)
                    {
                        LOG(1, "Error: invalid size argument for -h|-heightmap.\n");
                        _parseError = true;
                        return;
                    }
                    heightmap.width = atoi(parts[0].c_str());
                    heightmap.height = atoi(parts[1].c_str());

                    // Put some artificial bounds on heightmap dimensions
                    if (heightmap.width <= 0 || heightmap.height <= 0 || heightmap.width > HEIGHTMAP_SIZE_MAX || heightmap.height > HEIGHTMAP_SIZE_MAX)
                    {
                        LOG(1, "Error: size argument for -h|-heightmap must be between (1,1) and (%d,%d).\n", (int)HEIGHTMAP_SIZE_MAX, (int)HEIGHTMAP_SIZE_MAX);
                        _parseError = true;
                        return;
                    }

                    // Split node id list into tokens
                    (*index)++;
                    splitString(options[*index].c_str(), &heightmap.nodeIds);

                    // Store output filename
                    (*index)++;
                    heightmap.filename = options[*index];
                    if (heightmap.filename.empty())
                    {
                        LOG(1, "Error: missing filename argument for -h|-heightmap.\n");
                        _parseError = true;
                        return;
                    }

                    // Ensure the output filename has a .png extention
                    if (heightmap.filename.length() > 5)
                    {
                        const char* ext = heightmap.filename.c_str() + (heightmap.filename.length() - 4);
                        if (ext[0] != '.' || tolower(ext[1]) != 'p' || tolower(ext[2]) != 'n' || tolower(ext[3]) != 'g')
                            heightmap.filename += ".png";
                    }
                    else
                        heightmap.filename += ".png";
                }
                else
                {
                    LOG(1, "Error: missing argument for -h|-heightmap.\n");
                    _parseError = true;
                    return;
                }
            }
        }
        break;
    case 'm':
        if (str.compare("-m") == 0)
        {
            // generate a material file
            _outputMaterial = true;
        }
        break;
    case 'n':
        _normalMap = true;
        break;
    case 'w':
        {
            // Read world size
            (*index)++;
            if (*index >= options.size())
            {
                LOG(1, "Error: missing world size argument for -w.\n");
                _parseError = true;
                return;
            }
            std::vector<std::string> parts;
            splitString(options[*index].c_str(), &parts);
            if (parts.size() != 3)
            {
                LOG(1, "Error: invalid world size argument for -w.\n");
                _parseError = true;
                return;
            }
            _heightmapWorldSize.x = (float)atof(parts[0].c_str());
            _heightmapWorldSize.y = (float)atof(parts[1].c_str());
            _heightmapWorldSize.z = (float)atof(parts[2].c_str());
            if (_heightmapWorldSize.x == 0 || _heightmapWorldSize.y == 0 || _heightmapWorldSize.z == 0)
            {
                LOG(1, "Error: invalid world size argument for -w.\n");
                _parseError = true;
                return;
            }
        }
        break;
    case 'p':
        _fontPreview = true;
        break;
    case 's':
        if (_normalMap)
        {
            (*index)++;
            if (*index >= options.size())
            {
                LOG(1, "Error: missing argument for -s.\n");
                _parseError = true;
                return;
            }
            // Heightmap size
            std::vector<std::string> parts;
            splitString(options[*index].c_str(), &parts);
            if (parts.size() != 2 ||
                (_heightmapResolution[0] = atoi(parts[0].c_str())) <= 0 ||
                (_heightmapResolution[1] = atoi(parts[1].c_str())) <= 0)
            {
                LOG(1, "Error: invalid argument for -s.\n");
                _parseError = true;
                return;
            }
        }
        else
        {
            // Font Sizes
            const char* sizes = NULL;
            if (str.length() > 2)
            {
                char n = str[2];
                if (n > '0' && n <= '9')
                {
                    sizes = str.c_str() + 2;
                }
            }
            else
            {
                (*index)++;
                if (*index < options.size())
                {
                    sizes = options[*index].c_str();
                }
            }

            if (sizes == NULL)
            {
                LOG(1, "Error: invalid format for argument: -s");
                _parseError = true;
                return;
            }

            // Parse comma-separated list of font sizes
            char* ptr = const_cast<char*>(sizes);
            std::string sizeStr;
            while (ptr)
            {
                char* end = strchr(ptr, ',');
                if (end)
                {
                    sizeStr = std::string(ptr, end - ptr);
                    ptr = end + 1;
                }
                else
                {
                    sizeStr = ptr;
                    ptr = NULL;
                }
                if (sizeStr.length() > 0)
                {
                    int size = atoi(sizeStr.c_str());
                    if (size <= 0)
                    {
                        LOG(1, "Error: invalid font size provided: %s", sizeStr.c_str());
                        _parseError = true;
                        return;
                    }
                    _fontSizes.push_back((unsigned int)size);
                }
            }
        }
        break;
    case 't':
        if (str.compare("-t") == 0)
        {
            _textOutput = true;
        }
        else if (str.compare("-tb") == 0)
        {
            if ((*index + 1) >= options.size())
            {
                LOG(1, "Error: -tb requires 1 argument.\n");
                _parseError = true;
                return;
            }
            (*index)++;
            std::string nodeId = options[*index];
            if (nodeId.length() > 0)
            {
                _tangentBinormalId.insert(nodeId);
            }
        }
        else if (str.compare("-textureGutter:none") == 0 || str.compare("-tg:none") == 0)
        {
            _generateTextureGutter = false;
        }
        else if (str.compare("-textureGutter") == 0 || str.compare("-tg") == 0)
        {
            _generateTextureGutter = true;
        }
        break;
    case 'v':
        (*index)++;
        if (*index < options.size())
        {
            __logVerbosity = atoi(options[*index].c_str());
            if (__logVerbosity < 0)
                __logVerbosity = 0;
            else if (__logVerbosity > 4)
                __logVerbosity = 4;
        }
        break;
    default:
        break;
    }
}

void EncoderArguments::setInputfilePath(const std::string& inputPath)
{
    _filePath.assign(getRealPath(inputPath));
}

void EncoderArguments::setOutputfilePath(const std::string& outputPath)
{
    std::string ext = getOutputFileExtension();

    if (outputPath.size() > 0 && outputPath[0] != '\0')
    {
        std::string realPath = getRealPath(outputPath);
        if (endsWith(realPath.c_str(), ext.c_str()))
        {
            _fileOutputPath.assign(realPath);
        }
        else if (endsWith(outputPath.c_str(), "/"))
        {
            std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(_filePath));

            _fileOutputPath.assign(outputPath);
            _fileOutputPath.append(filenameNoExt);
            _fileOutputPath.append(ext);
        }
        else
        {
            std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(realPath));
            int pos = realPath.find_last_of("/");
            if (pos)
            {
                _fileOutputPath = realPath.substr(0, pos);
                _fileOutputPath.append("/");
                _fileOutputPath.append(filenameNoExt);
                _fileOutputPath.append(ext);
            }
        }
    }
}

std::string EncoderArguments::getRealPath(const std::string& filepath)
{
    char path[PATH_MAX + 1]; /* not sure about the "+ 1" */
    realpath(filepath.c_str(), path);
    replace_char(path, '\\', '/');
    return std::string(path);
}

void EncoderArguments::replace_char(char* str, char oldChar, char newChar)
{
    for (; *str != '\0'; ++str)
    {
        if (*str == oldChar)
        {
            *str = newChar;
        }
    }
}

std::string concat(const std::string& a, const char* b)
{
    std::string str(a);
    str.append(b);
    return str;
}

void unittestsEncoderArguments()
{
    std::string dir = EncoderArguments::getRealPath(".");
    std::string exePath = EncoderArguments::getRealPath(".");
    exePath.append("/gameplay-encoder.exe");
    const char* exe = exePath.c_str();
    {
        const char* argv[] = {exe, "-g", "root", "movements", "C:\\Git\\gaming\\GamePlay\\gameplay-encoder\\res\\duck.fbx"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getAnimationId("root"), ("movements")));
        assert(equals(args.getGroupAnimationNodeId()[0], ("root")));
        assert(equals(args.getOutputFilePath(), "C:/Git/gaming/GamePlay/gameplay-encoder/res/duck.gpb"));
    }
    {
        // Test with only input file name (relative)
        const char* argv[] = {exe, "input.fbx"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/input.gpb")));
        equals(args.getOutputDirPath(), dir);
    }
    {
        // Test specifying a relative output path
        const char* argv[] = {exe, "input.fbx", "output.gpb"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
    }
    {
        // Test specifying a relative output path
        const char* argv[] = {exe, "input.fbx", "output.gpb"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
    }
    {
        // Test specifying a relative output path to a directory
        const char* argv[] = {exe, "input.fbx", "stuff/output.gpb"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/stuff/output.gpb")));
    }
    {
        // Test parsing some arguments
        const char* argv[] = {exe, "test.fbx", "-t", "input.fbx", "output.gpb"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
        assert(args.textOutputEnabled());
    }
    {
        // Test output file with no file extension
        const char* argv[] = {exe, "input.fbx", "output"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
    }
    {
        // Test output file with wrong file extension
        const char* argv[] = {exe, "input.fbx", "output.fbx"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
    }
}

}