/*
 * Copyright © 2012 Red Hat Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of the copyright holders not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no representations
 * about the suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 *
 * Authors: Dave Airlie
 */

#include "randrstr.h"
#include "swaprep.h"

#include <X11/Xatom.h>

RESTYPE RRProviderType;

/*
 * Initialize provider type error value
 */
void
RRProviderInitErrorValue(void)
{
    SetResourceTypeErrorValue(RRProviderType, RRErrorBase + BadRRProvider);
}

#define ADD_PROVIDER(_pScreen) do {                                 \
    pScrPriv = rrGetScrPriv((_pScreen));                            \
    if (pScrPriv->provider) {                                   \
        providers[count_providers] = pScrPriv->provider->id;    \
        if (client->swapped)                                    \
            swapl(&providers[count_providers]);                 \
        count_providers++;                                      \
    }                                                           \
    } while(0)

int
ProcRRGetProviders (ClientPtr client)
{
    REQUEST(xRRGetProvidersReq);
    xRRGetProvidersReply rep;
    WindowPtr pWin;
    ScreenPtr pScreen;
    rrScrPrivPtr pScrPriv;
    int rc;
    CARD8 *extra;
    unsigned int extraLen;
    RRProvider *providers;
    int total_providers = 0, count_providers = 0;
    ScreenPtr iter;

    REQUEST_SIZE_MATCH(xRRGetProvidersReq);
    rc = dixLookupWindow(&pWin, stuff->window, client, DixGetAttrAccess);
    if (rc != Success)
        return rc;

    pScreen = pWin->drawable.pScreen;

    pScrPriv = rrGetScrPriv(pScreen);

    if (pScrPriv->provider)
        total_providers++;
    xorg_list_for_each_entry(iter, &pScreen->slave_list, slave_head) {
        pScrPriv = rrGetScrPriv(iter);
        total_providers += pScrPriv->provider ? 1 : 0;
    }

    pScrPriv = rrGetScrPriv(pScreen);

    if (!pScrPriv)
    {
        rep = (xRRGetProvidersReply) {
            .type = X_Reply,
            .sequenceNumber = client->sequence,
            .length = 0,
            .timestamp = currentTime.milliseconds,
            .nProviders = 0
        };
        extra = NULL;
        extraLen = 0;
    } else {
        rep = (xRRGetProvidersReply) {
            .type = X_Reply,
            .sequenceNumber = client->sequence,
            .timestamp = pScrPriv->lastSetTime.milliseconds,
            .nProviders = total_providers,
            .length = total_providers
        };
        extraLen = rep.length << 2;
        if (extraLen) {
            extra = malloc(extraLen);
            if (!extra)
                return BadAlloc;
        } else
            extra = NULL;

        providers = (RRProvider *)extra;
        ADD_PROVIDER(pScreen);
        xorg_list_for_each_entry(iter, &pScreen->slave_list, slave_head) {
            ADD_PROVIDER(iter);
        }
    }

    if (client->swapped) {
        swaps(&rep.sequenceNumber);
        swapl(&rep.length);
        swapl(&rep.timestamp);
        swaps(&rep.nProviders);
    }
    WriteToClient(client, sizeof(xRRGetProvidersReply), (char *)&rep);
    if (extraLen)
    {
        WriteToClient (client, extraLen, (char *) extra);
        free(extra);
    }
    return Success;
}

int
ProcRRGetProviderInfo (ClientPtr client)
{
    REQUEST(xRRGetProviderInfoReq);
    xRRGetProviderInfoReply rep;
    rrScrPrivPtr pScrPriv, pScrProvPriv;
    RRProviderPtr provider;
    ScreenPtr pScreen;
    CARD8 *extra;
    unsigned int extraLen = 0;
    RRCrtc *crtcs;
    RROutput *outputs;
    int i;
    char *name;
    ScreenPtr provscreen;
    RRProvider *providers;
    uint32_t *prov_cap;

    REQUEST_SIZE_MATCH(xRRGetProviderInfoReq);
    VERIFY_RR_PROVIDER(stuff->provider, provider, DixReadAccess);

    pScreen = provider->pScreen;
    pScrPriv = rrGetScrPriv(pScreen);

    rep = (xRRGetProviderInfoReply) {
        .type = X_Reply,
        .status = RRSetConfigSuccess,
        .sequenceNumber = client->sequence,
        .length = 0,
        .capabilities = provider->capabilities,
        .nameLength = provider->nameLength,
        .timestamp = pScrPriv->lastSetTime.milliseconds,
        .nCrtcs = pScrPriv->numCrtcs,
        .nOutputs = pScrPriv->numOutputs,
        .nAssociatedProviders = 0
    };

    /* count associated providers */
    if (provider->offload_sink)
        rep.nAssociatedProviders++;
    if (provider->output_source &&
            provider->output_source != provider->offload_sink)
        rep.nAssociatedProviders++;
    xorg_list_for_each_entry(provscreen, &pScreen->slave_list, slave_head) {
        if (provscreen->is_output_slave || provscreen->is_offload_slave)
            rep.nAssociatedProviders++;
    }

    rep.length = (pScrPriv->numCrtcs + pScrPriv->numOutputs +
                  (rep.nAssociatedProviders * 2) + bytes_to_int32(rep.nameLength));

    extraLen = rep.length << 2;
    if (extraLen) {
        extra = malloc(extraLen);
        if (!extra)
            return BadAlloc;
    }
    else
        extra = NULL;

    crtcs = (RRCrtc *)extra;
    outputs = (RROutput *)(crtcs + rep.nCrtcs);
    providers = (RRProvider *)(outputs + rep.nOutputs);
    prov_cap = (unsigned int *)(providers + rep.nAssociatedProviders);
    name = (char *)(prov_cap + rep.nAssociatedProviders);

    for (i = 0; i < pScrPriv->numCrtcs; i++) {
        crtcs[i] = pScrPriv->crtcs[i]->id;
        if (client->swapped)
            swapl(&crtcs[i]);
    }

    for (i = 0; i < pScrPriv->numOutputs; i++) {
        outputs[i] = pScrPriv->outputs[i]->id;
        if (client->swapped)
            swapl(&outputs[i]);
    }

    i = 0;
    if (provider->offload_sink) {
        providers[i] = provider->offload_sink->id;
        if (client->swapped)
            swapl(&providers[i]);
        prov_cap[i] = RR_Capability_SinkOffload;
        if (client->swapped)
            swapl(&prov_cap[i]);
        i++;
    }
    if (provider->output_source) {
        providers[i] = provider->output_source->id;
        if (client->swapped)
            swapl(&providers[i]);
        prov_cap[i] = RR_Capability_SourceOutput;
            swapl(&prov_cap[i]);
        i++;
    }
    xorg_list_for_each_entry(provscreen, &pScreen->slave_list, slave_head) {
        if (!provscreen->is_output_slave && !provscreen->is_offload_slave)
            continue;
        pScrProvPriv = rrGetScrPriv(provscreen);
        providers[i] = pScrProvPriv->provider->id;
        if (client->swapped)
            swapl(&providers[i]);
        prov_cap[i] = 0;
        if (provscreen->is_output_slave)
            prov_cap[i] |= RR_Capability_SinkOutput;
        if (provscreen->is_offload_slave)
            prov_cap[i] |= RR_Capability_SourceOffload;
        if (client->swapped)
            swapl(&prov_cap[i]);
        i++;
    }

    memcpy(name, provider->name, rep.nameLength);
    if (client->swapped) {
              swaps(&rep.sequenceNumber);
        swapl(&rep.length);
        swapl(&rep.capabilities);
        swaps(&rep.nCrtcs);
        swaps(&rep.nOutputs);
        swaps(&rep.nameLength);
    }
    WriteToClient(client, sizeof(xRRGetProviderInfoReply), (char *)&rep);
    if (extraLen)
    {
        WriteToClient (client, extraLen, (char *) extra);
        free(extra);
    }
    return Success;
}

static void
RRInitPrimeSyncProps(ScreenPtr pScreen)
{
    /*
     * TODO: When adding support for different sources for different outputs,
     * make sure this sets up the output properties only on outputs associated
     * with the correct source provider.
     */

    rrScrPrivPtr pScrPriv = rrGetScrPriv(pScreen);

    const char *syncStr = PRIME_SYNC_PROP;
    Atom syncProp = MakeAtom(syncStr, strlen(syncStr), TRUE);

    int defaultVal = TRUE;
    INT32 validVals[2] = {FALSE, TRUE};

    int i;
    for (i = 0; i < pScrPriv->numOutputs; i++) {
        if (!RRQueryOutputProperty(pScrPriv->outputs[i], syncProp)) {
            RRConfigureOutputProperty(pScrPriv->outputs[i], syncProp,
                                      TRUE, FALSE, FALSE,
                                      2, &validVals[0]);
            RRChangeOutputProperty(pScrPriv->outputs[i], syncProp, XA_INTEGER,
                                   8, PropModeReplace, 1, &defaultVal,
                                   FALSE, FALSE);
        }
    }
}

static void
RRFiniPrimeSyncProps(ScreenPtr pScreen)
{
    /*
     * TODO: When adding support for different sources for different outputs,
     * make sure this tears down the output properties only on outputs
     * associated with the correct source provider.
     */

    rrScrPrivPtr pScrPriv = rrGetScrPriv(pScreen);
    int i;

    const char *syncStr = PRIME_SYNC_PROP;
    Atom syncProp = MakeAtom(syncStr, strlen(syncStr), FALSE);
    if (syncProp == None)
        return;

    for (i = 0; i < pScrPriv->numOutputs; i++) {
        RRDeleteOutputProperty(pScrPriv->outputs[i], syncProp);
    }
}

int
ProcRRSetProviderOutputSource(ClientPtr client)
{
    REQUEST(xRRSetProviderOutputSourceReq);
    rrScrPrivPtr pScrPriv;
    RRProviderPtr provider, source_provider = NULL;
    ScreenPtr pScreen;

    REQUEST_SIZE_MATCH(xRRSetProviderOutputSourceReq);

    VERIFY_RR_PROVIDER(stuff->provider, provider, DixReadAccess);

    if (!(provider->capabilities & RR_Capability_SinkOutput))
        return BadValue;

    if (stuff->source_provider) {
        VERIFY_RR_PROVIDER(stuff->source_provider, source_provider, DixReadAccess);

        if (!(source_provider->capabilities & RR_Capability_SourceOutput))
            return BadValue;
    }

    pScreen = provider->pScreen;
    pScrPriv = rrGetScrPriv(pScreen);

    if (!pScreen->isGPU)
        return BadValue;

    pScrPriv->rrProviderSetOutputSource(pScreen, provider, source_provider);

    RRInitPrimeSyncProps(pScreen);

    provider->changed = TRUE;
    RRSetChanged(pScreen);

    RRTellChanged (pScreen);

    return Success;
}

int
ProcRRSetProviderOffloadSink(ClientPtr client)
{
    REQUEST(xRRSetProviderOffloadSinkReq);
    rrScrPrivPtr pScrPriv;
    RRProviderPtr provider, sink_provider = NULL;
    ScreenPtr pScreen;

    REQUEST_SIZE_MATCH(xRRSetProviderOffloadSinkReq);

    VERIFY_RR_PROVIDER(stuff->provider, provider, DixReadAccess);
    if (!(provider->capabilities & RR_Capability_SourceOffload))
        return BadValue;
    if (!provider->pScreen->isGPU)
        return BadValue;

    if (stuff->sink_provider) {
        VERIFY_RR_PROVIDER(stuff->sink_provider, sink_provider, DixReadAccess);
        if (!(sink_provider->capabilities & RR_Capability_SinkOffload))
            return BadValue;
    }
    pScreen = provider->pScreen;
    pScrPriv = rrGetScrPriv(pScreen);

    pScrPriv->rrProviderSetOffloadSink(pScreen, provider, sink_provider);

    provider->changed = TRUE;
    RRSetChanged(pScreen);

    RRTellChanged (pScreen);

    return Success;
}

RRProviderPtr
RRProviderCreate(ScreenPtr pScreen, const char *name,
                 int nameLength)
{
    RRProviderPtr provider;
    rrScrPrivPtr pScrPriv;

    pScrPriv = rrGetScrPriv(pScreen);

    provider = calloc(1, sizeof(RRProviderRec) + nameLength + 1);
    if (!provider)
        return NULL;

    provider->id = FakeClientID(0);
    provider->pScreen = pScreen;
    provider->name = (char *) (provider + 1);
    provider->nameLength = nameLength;
    memcpy(provider->name, name, nameLength);
    provider->name[nameLength] = '\0';
    provider->changed = FALSE;

    if (!AddResource (provider->id, RRProviderType, (void *) provider))
        return NULL;
    pScrPriv->provider = provider;
    return provider;
}

/*
 * Destroy a provider at shutdown
 */
void
RRProviderDestroy (RRProviderPtr provider)
{
    RRFiniPrimeSyncProps(provider->pScreen);
    FreeResource (provider->id, 0);
}

void
RRProviderSetCapabilities(RRProviderPtr provider, uint32_t capabilities)
{
    provider->capabilities = capabilities;
}

static int
RRProviderDestroyResource (void *value, XID pid)
{
    RRProviderPtr provider = (RRProviderPtr)value;
    ScreenPtr pScreen = provider->pScreen;

    if (pScreen)
    {
        rrScrPriv(pScreen);

        if (pScrPriv->rrProviderDestroy)
            (*pScrPriv->rrProviderDestroy)(pScreen, provider);
        pScrPriv->provider = NULL;
    }
    free(provider);
    return 1;
}

Bool
RRProviderInit(void)
{
    RRProviderType = CreateNewResourceType(RRProviderDestroyResource, "Provider");
    if (!RRProviderType)
        return FALSE;

    return TRUE;
}

extern _X_EXPORT Bool
RRProviderLookup(XID id, RRProviderPtr *provider_p)
{
    int rc = dixLookupResourceByType((void **)provider_p, id,
                                   RRProviderType, NullClient, DixReadAccess);
    if (rc == Success)
        return TRUE;
    return FALSE;
}

void
RRDeliverProviderEvent(ClientPtr client, WindowPtr pWin, RRProviderPtr provider)
{
    ScreenPtr pScreen = pWin->drawable.pScreen;

    rrScrPriv(pScreen);

    xRRProviderChangeNotifyEvent pe = {
        .type = RRNotify + RREventBase,
        .subCode = RRNotify_ProviderChange,
        .timestamp = pScrPriv->lastSetTime.milliseconds,
        .window = pWin->drawable.id,
        .provider = provider->id
    };

    WriteEventsToClient(client, 1, (xEvent *) &pe);
}
