/* bzflag
 * Copyright (c) 1993-2018 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "SGIDisplay.h"

#if defined(USE_XSGIVC_EXT)

#include <stdlib.h>

typedef int     (*XErrorHandler_t)(Display*, XErrorEvent*);
static bool     errorHappened;
static int      errorHandler(Display*, XErrorEvent*)
{
    errorHappened = true;
    return 0;
}

//
// Resolution
//

class Resolution
{
public:
    class Config
    {
    public:
        int     width;
        int     height;
        int     refresh;
    };

public:
    Resolution(XSGIvcVideoFormatInfo*);
    Resolution(const char* comboName, int numFormats,
               XSGIvcVideoFormatInfo* formats);
    ~Resolution();

    bool        isValid() const;
    const char*     getName() const;
    int         getNumChannels() const;
    const Config*   getConfigs() const;
    bool        setFormat(Display*, int screen) const;

private:
    void        setConfig(int index, const XSGIvcVideoFormatInfo*);

private:
    bool        valid;
    bool        combination;
    char*       name;
    int         numChannels;
    void*       data;
    Config*     config;
};

Resolution::Resolution(XSGIvcVideoFormatInfo* format) :
    valid(true), data(NULL)
{
    name = new char[strlen(format->name) + 1];
    strcpy(name, format->name);

    numChannels = 1;
    config = new Config[numChannels];
    setConfig(0, format);

    combination = false;
    XSGIvcVideoFormatInfo* _data = new XSGIvcVideoFormatInfo;
    *_data = *format;
    data = _data;
}

Resolution::Resolution(const char* comboName, int numFormats,
                       XSGIvcVideoFormatInfo* formats) :
    valid(true), data(NULL)
{
    name = new char[strlen(comboName) + 1];
    strcpy(name, comboName);

    numChannels = numFormats;
    config = new Config[numChannels];
    for (int i = 0; i < numChannels; i++)
        setConfig(i, formats + i);

    combination = true;
    char* _data = new char[strlen(comboName) + 1];
    strcpy(_data, comboName);
    data = _data;
}

Resolution::~Resolution()
{
    delete[] name;
    delete[] config;
    if (combination) delete[] (char*)data;
    else delete (XSGIvcVideoFormatInfo*)data;
}

void            Resolution::setConfig(int index,
                                      const XSGIvcVideoFormatInfo* format)
{
    config[index].width = format->width;
    config[index].height = format->height;
    config[index].refresh = (int)(format->verticalRetraceRate + 0.5f);
}

bool            Resolution::isValid() const
{
    return valid;
}

const char*     Resolution::getName() const
{
    return name;
}

int         Resolution::getNumChannels() const
{
    return numChannels;
}

const Resolution::Config* Resolution::getConfigs() const
{
    return config;
}

bool            Resolution::setFormat(Display* dpy, int screen) const
{
    errorHappened = false;
    XErrorHandler_t prevErrorHandler = XSetErrorHandler(&errorHandler);

    Status status;
    if (combination)
        status = XSGIvcLoadVideoFormatCombination(dpy, screen, (const char*)data);
    else
    {
        status = XSGIvcLoadVideoFormat(dpy, screen, 0,
                                       (XSGIvcVideoFormatInfo*)data,
                                       XSGIVC_QVFHeight |
                                       XSGIVC_QVFWidth |
                                       XSGIVC_QVFSwapbufferRate,
                                       false);
    }

    XSync(dpy, false);
    XSetErrorHandler(prevErrorHandler);
    if (!status || errorHappened) ((Resolution*)this)->valid = false;
    return valid;
}

//
// comparision of resolutions function for qsort
//

static int      resolutionCompare(const void* _a, const void* _b)
{
    const Resolution* a = *((const Resolution* const *)_a);
    const int aNumChannels = a->getNumChannels();
    const Resolution::Config* aConfig = a->getConfigs();

    const Resolution* b = *((const Resolution* const *)_b);
    const int bNumChannels = b->getNumChannels();
    const Resolution::Config* bConfig = b->getConfigs();

    if (aNumChannels < bNumChannels) return -1;
    if (aNumChannels > bNumChannels) return 1;
    for (int i = 0; i < aNumChannels; i++)
    {
        if (aConfig[i].width < bConfig[i].width) return -1;
        if (aConfig[i].width > bConfig[i].width) return 1;
        if (aConfig[i].height < bConfig[i].height) return -1;
        if (aConfig[i].height > bConfig[i].height) return 1;
        if (aConfig[i].refresh < bConfig[i].refresh) return -1;
        if (aConfig[i].refresh > bConfig[i].refresh) return 1;
    }
    return 0;
}

//
// SGIDisplay
//

SGIDisplayMode::SGIDisplayMode() : display(NULL)
{
    numResolutions = 0;
    lastResolution = -1;
    numVideoChannels = 0;
    defaultChannel = -1;
    defaultVideoFormats = NULL;
    defaultVideoCombo = NULL;
    numVideoFormats = NULL;
    videoFormats = NULL;
    numVideoCombos = 0;
    videoCombos = NULL;
    resolutions = NULL;
}

SGIDisplayMode::~SGIDisplayMode()
{
    int i, j;
    for (i = 0; i < numVideoChannels; i++)
    {
        for (j = 0; j < numVideoFormats[i]; j++)
            delete videoFormats[i][j];
        delete[] videoFormats[i];
    }
    delete[] videoFormats;
    for (i = 0; i < numVideoCombos; i++)
        delete videoCombos[i];
    delete[] videoCombos;
    delete[] defaultVideoFormats;
    delete[] defaultVideoCombo;
    delete[] numVideoFormats;
    delete[] resolutions;
}

XDisplayMode::ResInfo** SGIDisplayMode::init(XDisplay* _display,
        int& numModes, int& currentMode)
{
    // save display for later
    display = _display;
    XDisplay::Rep* rep = display->getRep();

    // get video screen info
    int i, major, minor;
    XSGIvcScreenInfo screenInfo;
    if (!XSGIvcQueryVersion(rep->getDisplay(), &major, &minor))
        return NULL;
    if (!XSGIvcQueryVideoScreenInfo(rep->getDisplay(),
                                    rep->getScreen(), &screenInfo))
        return NULL;
    if (screenInfo.numChannels < 1) return NULL;
    numVideoChannels = screenInfo.numChannels;

    errorHappened = false;
    XErrorHandler_t prevErrorHandler = XSetErrorHandler(&errorHandler);

    // get the current format on each channel
    Resolution** channelFormat = new Resolution*[numVideoChannels];
    for (i = 0; i < numVideoChannels; i++)
    {
        XSGIvcChannelInfo* channelInfo;
        channelFormat[i] = NULL;
        if (XSGIvcQueryChannelInfo(rep->getDisplay(),
                                   rep->getScreen(), i, &channelInfo))
        {
            if (channelInfo->active)
                channelFormat[i] = new Resolution(&channelInfo->vfinfo);
            XFree(channelInfo);
        }
    }

    // get the individually loadable formats available on each channel
    if (screenInfo.flags & XSGIVC_SIFFormatPerChannel)
    {
        defaultVideoFormats = new int[numVideoChannels];
        numVideoFormats = new int[numVideoChannels];
        videoFormats = new Resolution**[numVideoChannels];

        XSGIvcVideoFormatInfo pattern;
        for (i = 0; i < numVideoChannels; i++)
        {
            errorHappened = false;
            int numFormats = 0;
            XSGIvcVideoFormatInfo* formats = XSGIvcListVideoFormats(
                                                 rep->getDisplay(), rep->getScreen(), i,
                                                 &pattern, 0, false, 4096, &numFormats);
            if (!formats || errorHappened) numFormats = 0;

            videoFormats[i] = new Resolution*[numFormats];
            for (int j = 0; j < numFormats; j++)
                videoFormats[i][j] = new Resolution(formats + j);
            numVideoFormats[i] = numFormats;

            if (formats) XSGIvcFreeVideoFormatInfo(formats);
        }

        // figure out the current format on each channel by comparing the
        // current format against each format available on that channel.
        // also set the default channel (first active is as good as another).
        for (i = 0; i < numVideoChannels; i++)
        {
            defaultVideoFormats[i] = -1;
            for (int j = 0; j < numVideoFormats[i]; j++)
            {
                const char* name1 = videoFormats[i][j]->getName();
                const char* name2 = channelFormat[i]->getName();
                if ((name1[0] && name2[0] && strcmp(name1, name2) == 0) ||
                        resolutionCompare(videoFormats[i] + j, channelFormat + i) == 0)
                {
                    defaultVideoFormats[i] = j;
                    break;
                }
            }
            if (defaultVideoFormats[i] == -1)
            {
                // uh, Houston, we have a problem -- can't find current format!
                // disable format changing on this channel.
                for (int j = 0; j < numVideoFormats[i]; j++)
                    delete videoFormats[i][j];
                numVideoFormats[i] = 0;
            }

            else if (defaultChannel == -1)
                defaultChannel = i;
        }
    }

    // now get the combo formats
    if (screenInfo.flags & XSGIVC_SIFFormatCombination)
    {
        errorHappened = false;
        int numCombos;
        char** comboNames = XSGIvcListVideoFormatsCombinations(
                                rep->getDisplay(), rep->getScreen(),
                                "*", 4096, &numCombos);
        if (!comboNames || errorHappened) numCombos = 0;

        videoCombos = new Resolution*[numCombos];
        for (i = 0; i < numCombos; i++)
        {
            errorHappened = false;
            int numFormats, bestMatch = 0;
            XSGIvcVideoFormatInfo* formats =
                XSGIvcListVideoFormatsInCombination(
                    rep->getDisplay(), rep->getScreen(),
                    comboNames[i], &numFormats);
            if (formats && numFormats != 0 && !errorHappened)
            {
                // make format for combo
                videoCombos[numVideoCombos++] =
                    new Resolution(comboNames[i], numFormats, formats);

                // compare the format in each channel with the current format
                // in each channel.  if they all match then we've found a
                // suitable default video combo name.  note that inactive
                // channels have a NULL channelFormat[] and an empty
                // formats[].name.
                if (!defaultVideoCombo || bestMatch != numVideoChannels)
                {
                    int match = 0;
                    for (int j = 0; j < numVideoChannels; j++)
                    {
                        const bool hasFormat = (j < numFormats && formats[j].name[0]);
                        const bool isActive = (channelFormat[j] != NULL);

                        // no match if channel is inactive and there's a format or
                        // channel is active and there's no format.
                        if (hasFormat != isActive) continue;

                        // match if channel is inactive and no format on that channel.
                        if (!hasFormat && !isActive) continue;

                        // compare formats for equality
                        const Resolution::Config* c = channelFormat[j]->getConfigs();
                        if (formats[j].width != c[0].width)
                            continue;
                        if (formats[j].height != c[0].height)
                            continue;
                        if ((int)(formats[j].verticalRetraceRate + 0.5f) != c[0].refresh)
                            continue;
                        match++;
                    }

                    // if all channels matched then we've got it
                    if (match > bestMatch)
                    {
                        bestMatch = match;
                        delete[] defaultVideoCombo;
                        defaultVideoCombo = new char[strlen(comboNames[i]) + 1];
                        strcpy(defaultVideoCombo, comboNames[i]);
                    }
                }

                XSGIvcFreeVideoFormatInfo(formats);
            }
        }

        XFree(comboNames);
    }

    // done with current channel info
    for (i = 0; i < numVideoChannels; i++)
        delete channelFormat[i];
    delete[] channelFormat;

    // now combine formats and combinations for interface.  save a
    // pointer to the one that's the currently loaded format.
    int numFormats = (defaultChannel == -1) ? 0 : numVideoFormats[defaultChannel];
    numFormats += numVideoCombos;
    resolutions = new Resolution*[numFormats];
    Resolution* currentFormat = NULL;
    if (defaultChannel != -1)
    {
        for (i = 0; i < numVideoFormats[defaultChannel]; i++)
            resolutions[numResolutions++] = videoFormats[defaultChannel][i];

        const int defaultFormat = defaultVideoFormats[defaultChannel];
        if (defaultFormat != -1)
            currentFormat = videoFormats[defaultChannel][defaultFormat];
    }
    for (i = 0; i < numVideoCombos; i++)
    {
        resolutions[numResolutions++] = videoCombos[i];

        if (defaultVideoCombo &&
                strcmp(defaultVideoCombo, videoCombos[i]->getName()) == 0)
            currentFormat = videoCombos[i];
    }

    // sort resolutions
    qsort(resolutions, numResolutions, sizeof(resolutions[0]), resolutionCompare);

    XSetErrorHandler(prevErrorHandler);

    // find current format in sorted list
    int current;
    for (current = 0; current < numResolutions; current++)
        if (resolutions[current] == currentFormat)
            break;

    // if current format found then allow format switching
    if (current < numResolutions)
    {
        ResInfo** resInfo = new ResInfo*[numResolutions];
        for (int i = 0; i < numResolutions; i++)
        {
            const Resolution* r = resolutions[i];
            const Resolution::Config* c = r->getConfigs();
            resInfo[i] = new ResInfo(r->getName(), c->width, c->height, c->refresh);
        }

        numModes = numResolutions;
        currentMode = current;
        lastResolution = current;
        return resInfo;
    }

    return NULL;
}

bool            SGIDisplayMode::set(int index)
{
    // ignore attempts to set video format to current format.
    // normally this only happens when restoring the default
    // format, when BzfDisplay deliberately forces the change.
    // that's useful for win32 where the OS knows the right
    // format and will ignore calls to switch the current
    // format.  however, irix isn't so clever and may cause
    // the display to flicker even when the format isn't
    // really changing.
    if (index == lastResolution)
        return true;

    if (resolutions[index]->setFormat(display->getRep()->getDisplay(),
                                      display->getRep()->getScreen()))
    {
        lastResolution = index;
        return true;
    }
    return false;
}

#endif

// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
