#include "ScreenResolution.h"
#include "settings.h"
#include "trace.h"
#ifdef _WIN32
#include <Windows.h>
#endif
#ifdef ANDROID
#include <Common/StdString.h>
#include <vector>
#endif

struct ResolutionInfo
{
    ResolutionInfo(const char * name = nullptr, uint32_t width = 0, uint32_t height = 0, uint32_t frequency = 0, bool default_res = false) :
        m_name(name ? name : ""),
        m_width(width),
        m_height(height),
        m_frequency(frequency),
        m_default_res(default_res)
    {
    }

    const char * Name(void) const { return m_name.c_str(); }
    uint32_t width(void) const { return m_width; }
    uint32_t height(void) const { return m_height; }
    uint32_t frequency(void) const { return m_frequency; }
    bool DefaultRes(void) const { return m_default_res; }

    bool operator == (const ResolutionInfo& rRes) const
    {
        return m_width == rRes.m_width && m_height == rRes.m_height && m_frequency == rRes.m_frequency;
    }
    bool operator != (const ResolutionInfo& rRes) const
    {
        return !(*this == rRes);
    }
private:
    uint32_t m_width, m_height, m_frequency;
    std::string m_name;
    bool m_default_res;
};

#ifdef ANDROID
std::vector<ResolutionInfo> g_resolutions;
#else
static ResolutionInfo g_resolutions[] =
{
    { "320x200", 320, 200, 0, false },
    { "320x240", 320, 240, 0, false },
    { "400x256", 400, 256, 0, false },
    { "512x384", 512, 384, 0, false },
    { "640x200", 640, 200, 0, false },
    { "640x350", 640, 350, 0, false },
    { "640x400", 640, 400, 0, false },
    { "640x480", 640, 480, 0, true },
    { "800x600", 800, 600, 0, false },
    { "960x720", 960, 720, 0, false },
    { "856x480", 856, 480, 0, false },
    { "512x256", 512, 256, 0, false },
    { "1024x768", 1024, 768, 0, false },
    { "1280x1024", 1280, 1024, 0, false },
    { "1600x1200", 1600, 1200, 0, false },
    { "400x300", 400, 300, 0, false },
    { "1152x864", 1152, 864, 0, false },
    { "1280x960", 1280, 960, 0, false },
    { "1600x1024", 1600, 1024, 0, false },
    { "1792x1344", 1792, 1344, 0, false },
    { "1856x1392", 1856, 1392, 0, false },
    { "1920x1440", 1920, 1440, 0, false },
    { "2048x1536", 2048, 1536, 0, false },
    { "2048x2048", 2048, 2048, 0, false },
};
#endif

#ifdef ANDROID
void UpdateScreenResolution(int ScreenWidth, int ScreenHeight)
{
    WriteTrace(TraceResolution, TraceError, "aspectmode: %d", g_settings->aspectmode());
    g_resolutions.clear();
    switch (g_settings->aspectmode())
    {
    case CSettings::Aspect_4x3:
        g_resolutions.push_back(ResolutionInfo(stdstr_f("%dx%d", ScreenHeight * 4 / 3, ScreenHeight).c_str(), ScreenHeight * 4 / 3, ScreenHeight, 0, true));
        g_resolutions.push_back(ResolutionInfo("960x720", 960, 720, 0, false));
        g_resolutions.push_back(ResolutionInfo("800x600", 800, 600, 0, false));
        g_resolutions.push_back(ResolutionInfo("640x480", 640, 480, 0, false));
        g_resolutions.push_back(ResolutionInfo("480x360", 480, 360, 0, false));
        g_resolutions.push_back(ResolutionInfo("320x240", 320, 240, 0, false));
        break;
    case CSettings::Aspect_16x9:
        g_resolutions.push_back(ResolutionInfo(stdstr_f("%dx%d", ScreenHeight * 16 / 9, ScreenHeight).c_str(), ScreenHeight * 16 / 9, ScreenHeight, 0, true));
        g_resolutions.push_back(ResolutionInfo("1280x720", 1280, 720, 0, false));
        g_resolutions.push_back(ResolutionInfo("1067x600", 1067, 600, 0, false));
        g_resolutions.push_back(ResolutionInfo("854x480", 854, 480, 0, false));
        g_resolutions.push_back(ResolutionInfo("640x360", 640, 360, 0, false));
        g_resolutions.push_back(ResolutionInfo("426x240", 426, 240, 0, false));
        break;
    case CSettings::Aspect_Original:
        g_resolutions.push_back(ResolutionInfo("Original", ScreenWidth, ScreenHeight, 0, true));
        break;
    case CSettings::Aspect_Stretch:
    default: //stretch
        g_resolutions.push_back(ResolutionInfo(stdstr_f("%dx%d", ScreenWidth, ScreenHeight).c_str(), ScreenWidth, ScreenHeight, 0, true));
        break;
    }
}
#endif

uint32_t GetScreenResolutionCount()
{
#ifdef ANDROID
    return g_resolutions.size();
#else
    return sizeof(g_resolutions) / sizeof(g_resolutions[0]);
#endif
}

const char * GetScreenResolutionName(uint32_t index)
{
    if (index < GetScreenResolutionCount())
    {
        return g_resolutions[index].Name();
    }
    return "unknown";
}

uint32_t GetDefaultScreenRes()
{
    for (uint32_t i = 0, n = GetScreenResolutionCount(); i < n; i++)
    {
        if (g_resolutions[i].DefaultRes())
        {
            return i;
        }
    }
    return 0;
}

uint32_t GetScreenResWidth(uint32_t index)
{
    if (index < GetScreenResolutionCount())
    {
        return g_resolutions[index].width();
    }
    return 0;
}

uint32_t GetScreenResHeight(uint32_t index)
{
    if (index < GetScreenResolutionCount())
    {
        return g_resolutions[index].height();
    }
    return 0;
}

class FullScreenResolutions
{
public:
    FullScreenResolutions() :
        m_dwNumResolutions(0),
        m_aResolutions(0),
        m_aResolutionsStr(0)
    {
    }
    ~FullScreenResolutions();

    void getResolution(uint32_t _idx, uint32_t * _width, uint32_t * _height, uint32_t * _frequency = 0)
    {
        WriteTrace(TraceResolution, TraceDebug, "_idx: %d", _idx);
        if (m_dwNumResolutions == 0)
        {
            init();
        }
        if (_idx >= m_dwNumResolutions)
        {
            WriteTrace(TraceGlitch, TraceError, "NumResolutions = %d", m_dwNumResolutions);
            _idx = 0;
        }
        *_width = (uint32_t)m_aResolutions[_idx].width();
        *_height = (uint32_t)m_aResolutions[_idx].height();
        if (_frequency != 0)
        {
            *_frequency = (uint32_t)m_aResolutions[_idx].frequency();
        }
    }

    int getCurrentResolutions(void)
    {
        if (m_dwNumResolutions == 0)
        {
            init();
        }
        return m_currentResolutions;
    }

    const char ** getResolutionsList(int32_t * Size)
    {
        if (m_dwNumResolutions == 0)
        {
            init();
        }
        *Size = (int32_t)m_dwNumResolutions;
        return (const char **)m_aResolutionsStr;
    }

    bool changeDisplaySettings(uint32_t _resolution);

private:
    void init();
    unsigned int m_dwNumResolutions;
    ResolutionInfo * m_aResolutions;
    char ** m_aResolutionsStr;
    int m_currentResolutions;
};

FullScreenResolutions::~FullScreenResolutions()
{
    for (unsigned int i = 0; i < m_dwNumResolutions; i++)
    {
        delete[] m_aResolutionsStr[i];
        m_aResolutionsStr[i] = nullptr;
    }
    if (m_aResolutionsStr)
    {
        delete[] m_aResolutionsStr;
        m_aResolutionsStr = nullptr;
    }
    if (m_aResolutions)
    {
        delete[] m_aResolutions;
        m_aResolutions = nullptr;
    }
}

void FullScreenResolutions::init()
{
#ifdef _WIN32
    m_currentResolutions = -1;
    DEVMODE enumMode, currentMode;
    int iModeNum = 0;
    memset(&enumMode, 0, sizeof(DEVMODE));

    EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &currentMode);

    ResolutionInfo prevInfo;
    while (EnumDisplaySettings(nullptr, iModeNum++, &enumMode) != 0)
    {
        ResolutionInfo curInfo("", enumMode.dmPelsWidth, enumMode.dmPelsHeight, enumMode.dmDisplayFrequency);
        if (enumMode.dmBitsPerPel == 32 && curInfo != prevInfo)
        {
            m_dwNumResolutions++;
            prevInfo = curInfo;
        }
    }

    m_aResolutions = new ResolutionInfo[m_dwNumResolutions];
    m_aResolutionsStr = new char*[m_dwNumResolutions];
    iModeNum = 0;
    int current = 0;
    char smode[256];
    memset(&enumMode, 0, sizeof(DEVMODE));
    prevInfo = ResolutionInfo();
    while (EnumDisplaySettings(nullptr, iModeNum++, &enumMode) != 0)
    {
        ResolutionInfo curInfo(nullptr, enumMode.dmPelsWidth, enumMode.dmPelsHeight, enumMode.dmDisplayFrequency);
        if (enumMode.dmBitsPerPel == 32 && curInfo != prevInfo)
        {
            if (enumMode.dmPelsHeight == currentMode.dmPelsHeight && enumMode.dmPelsWidth == currentMode.dmPelsWidth)
            {
                m_currentResolutions = current;
            }
            m_aResolutions[current] = curInfo;
            sprintf(smode, curInfo.frequency() > 0 ? "%ix%i 32bpp %iHz" : "%ix%i 32bpp", curInfo.width(), curInfo.height(), curInfo.frequency());
            m_aResolutionsStr[current] = new char[strlen(smode) + 1];
            strcpy(m_aResolutionsStr[current], smode);
            prevInfo = curInfo;
            current++;
        }
    }
#endif
}

bool FullScreenResolutions::changeDisplaySettings(uint32_t _resolution)
{
#ifdef _WIN32
    uint32_t width, height, frequency;
    getResolution(_resolution, &width, &height, &frequency);
    ResolutionInfo info(nullptr, width, height, frequency);
    DEVMODE enumMode;
    int iModeNum = 0;
    memset(&enumMode, 0, sizeof(DEVMODE));
    while (EnumDisplaySettings(nullptr, iModeNum++, &enumMode) != 0)
    {
        ResolutionInfo curInfo(nullptr, enumMode.dmPelsWidth, enumMode.dmPelsHeight, enumMode.dmDisplayFrequency);
        if (enumMode.dmBitsPerPel == 32 && curInfo == info) {
            bool bRes = ChangeDisplaySettings(&enumMode, CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL;
            WriteTrace(TraceGlitch, TraceDebug, "width=%d, height=%d, freq=%d %s\r\n", enumMode.dmPelsWidth, enumMode.dmPelsHeight, enumMode.dmDisplayFrequency, bRes ? "Success" : "Failed");
            return bRes;
        }
    }
    return false;
#else // _WIN32
    return false;
#endif // _WIN32
}

FullScreenResolutions g_FullScreenResolutions;

uint32_t GetFullScreenResWidth(uint32_t index)
{
    uint32_t _width, _height;
    g_FullScreenResolutions.getResolution(index, &_width, &_height);
    return _width;
}

uint32_t GetFullScreenResHeight(uint32_t index)
{
    uint32_t _width, _height;
    g_FullScreenResolutions.getResolution(index, &_width, &_height);
    return _height;
}

bool EnterFullScreen(uint32_t index)
{
    return g_FullScreenResolutions.changeDisplaySettings(index);
}

int GetCurrentResIndex(void)
{
    return g_FullScreenResolutions.getCurrentResolutions();
}

#ifndef ANDROID
const char ** getFullScreenResList(int32_t * Size)
{
    WriteTrace(TraceGlitch, TraceDebug, "-");
    return g_FullScreenResolutions.getResolutionsList(Size);
}

uint32_t getFullScreenRes(uint32_t * width, uint32_t * height)
{
    WriteTrace(TraceGlitch, TraceDebug, "-");
    g_FullScreenResolutions.getResolution(g_settings->FullScreenRes(), width, height);
    return g_settings->FullScreenRes();
}
#endif