/*
 * Copyright © 2006 Keith Packard
 *
 * 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.
 */

#include "randrstr.h"

RESTYPE RROutputType;

/*
 * Notify the output of some change
 */
void
RROutputChanged(RROutputPtr output, Bool configChanged)
{
    ScreenPtr pScreen = output->pScreen;

    output->changed = TRUE;
    if (pScreen) {
        rrScrPriv(pScreen);
        pScrPriv->changed = TRUE;
        if (configChanged)
            pScrPriv->configChanged = TRUE;
    }
}

/*
 * Create an output
 */

RROutputPtr
RROutputCreate(const char *name, int nameLength, void *devPrivate)
{
    RROutputPtr output;

    if (!RRInit())
        return NULL;
    output = malloc(sizeof(RROutputRec) + nameLength + 1);
    if (!output)
        return NULL;
    output->id = FakeClientID(0);
    output->pScreen = NULL;
    output->name = (char *) (output + 1);
    output->nameLength = nameLength;
    memcpy(output->name, name, nameLength);
    output->name[nameLength] = '\0';
    output->connection = RR_UnknownConnection;
    output->subpixelOrder = SubPixelUnknown;
    output->mmWidth = 0;
    output->mmHeight = 0;
    output->crtc = NULL;
    output->numCrtcs = 0;
    output->crtcs = NULL;
    output->numClones = 0;
    output->clones = NULL;
    output->numModes = 0;
    output->numPreferred = 0;
    output->modes = NULL;
    output->properties = NULL;
    output->changed = FALSE;
    output->devPrivate = devPrivate;

    if (!AddResource(output->id, RROutputType, (pointer) output))
        return NULL;

    return output;
}

/*
 * Attach an Output to a screen. This is done as a separate step
 * so that an xf86-based driver can create Outputs in PreInit
 * before the Screen has been created
 */

Bool
RROutputAttachScreen(RROutputPtr output, ScreenPtr pScreen)
{
    rrScrPriv(pScreen);
    RROutputPtr *outputs;

    if (pScrPriv->numOutputs)
        outputs = realloc(pScrPriv->outputs,
                           (pScrPriv->numOutputs + 1) * sizeof(RROutputPtr));
    else
        outputs = malloc(sizeof(RROutputPtr));
    if (!outputs)
        return FALSE;
    output->pScreen = pScreen;
    pScrPriv->outputs = outputs;
    pScrPriv->outputs[pScrPriv->numOutputs++] = output;
    RROutputChanged(output, FALSE);
    return TRUE;
}

/*
 * Notify extension that output parameters have been changed
 */
Bool
RROutputSetClones(RROutputPtr output, RROutputPtr * clones, int numClones)
{
    RROutputPtr *newClones;

    int i;

    if (numClones == output->numClones) {
        for (i = 0; i < numClones; i++)
            if (output->clones[i] != clones[i])
                break;
        if (i == numClones)
            return TRUE;
    }
    if (numClones) {
        newClones = malloc(numClones * sizeof(RROutputPtr));
        if (!newClones)
            return FALSE;

        memcpy(newClones, clones, numClones * sizeof(RROutputPtr));
    }
    else
        newClones = NULL;
    if (output->clones)
        free(output->clones);
    output->clones = newClones;
    output->numClones = numClones;
    RROutputChanged(output, TRUE);
    return TRUE;
}

Bool
RROutputSetModes(RROutputPtr output,
                 RRModePtr * modes, int numModes, int numPreferred)
{
    RRModePtr *newModes;

    int i;

    if (numModes == output->numModes && numPreferred == output->numPreferred) {
        for (i = 0; i < numModes; i++)
            if (output->modes[i] != modes[i])
                break;
        if (i == numModes) {
            for (i = 0; i < numModes; i++)
                RRModeDestroy(modes[i]);
            return TRUE;
        }
    }

    if (numModes) {
        newModes = malloc(numModes * sizeof(RRModePtr));
        if (!newModes)
            return FALSE;

        memcpy(newModes, modes, numModes * sizeof(RRModePtr));
    }
    else
        newModes = NULL;
    if (output->modes) {
        for (i = 0; i < output->numModes; i++)
            RRModeDestroy(output->modes[i]);
        free(output->modes);
    }
    output->modes = newModes;
    output->numModes = numModes;
    output->numPreferred = numPreferred;
    RROutputChanged(output, TRUE);
    return TRUE;
}

Bool
RROutputSetCrtcs(RROutputPtr output, RRCrtcPtr * crtcs, int numCrtcs)
{
    RRCrtcPtr *newCrtcs;

    int i;

    if (numCrtcs == output->numCrtcs) {
        for (i = 0; i < numCrtcs; i++)
            if (output->crtcs[i] != crtcs[i])
                break;
        if (i == numCrtcs)
            return TRUE;
    }
    if (numCrtcs) {
        newCrtcs = malloc(numCrtcs * sizeof(RRCrtcPtr));
        if (!newCrtcs)
            return FALSE;

        memcpy(newCrtcs, crtcs, numCrtcs * sizeof(RRCrtcPtr));
    }
    else
        newCrtcs = NULL;

    free(output->crtcs);
    output->crtcs = newCrtcs;
    output->numCrtcs = numCrtcs;
    RROutputChanged(output, TRUE);
    return TRUE;
}

void
RROutputSetCrtc(RROutputPtr output, RRCrtcPtr crtc)
{
    if (output->crtc == crtc)
        return;
    output->crtc = crtc;
    RROutputChanged(output, FALSE);
}

Bool
RROutputSetConnection(RROutputPtr output, CARD8 connection)
{
    if (output->connection == connection)
        return TRUE;
    output->connection = connection;
    RROutputChanged(output, TRUE);
    return TRUE;
}

Bool
RROutputSetSubpixelOrder(RROutputPtr output, int subpixelOrder)
{
    if (output->subpixelOrder == subpixelOrder)
        return TRUE;

    output->subpixelOrder = subpixelOrder;
    RROutputChanged(output, FALSE);
    return TRUE;
}

Bool
RROutputSetPhysicalSize(RROutputPtr output, int mmWidth, int mmHeight)
{
    if (output->mmWidth == mmWidth && output->mmHeight == mmHeight)
        return TRUE;
    output->mmWidth = mmWidth;
    output->mmHeight = mmHeight;
    RROutputChanged(output, FALSE);
    return TRUE;
}

void
RRDeliverOutputEvent(ClientPtr client, WindowPtr pWin, RROutputPtr output)
{
    ScreenPtr pScreen = pWin->drawable.pScreen;

    rrScrPriv(pScreen);
    xRROutputChangeNotifyEvent oe;

    RRCrtcPtr crtc = output->crtc;

    RRModePtr mode = crtc ? crtc->mode : 0;

    oe.type = RRNotify + RREventBase;
    oe.subCode = RRNotify_OutputChange;
    oe.timestamp = pScrPriv->lastSetTime.milliseconds;
    oe.configTimestamp = pScrPriv->lastConfigTime.milliseconds;
    oe.window = pWin->drawable.id;
    oe.output = output->id;
    if (crtc) {
        oe.crtc = crtc->id;
        oe.mode = mode ? mode->mode.id : None;
        oe.rotation = crtc->rotation;
    }
    else {
        oe.crtc = None;
        oe.mode = None;
        oe.rotation = RR_Rotate_0;
    }
    oe.connection = output->connection;
    oe.subpixelOrder = output->subpixelOrder;
    WriteEventsToClient(client, 1, (xEvent *) &oe);
}

/*
 * Destroy a Output at shutdown
 */
void
RROutputDestroy(RROutputPtr crtc)
{
    FreeResource(crtc->id, 0);
}

static int
RROutputDestroyResource(pointer value, XID pid)
{
    RROutputPtr output = (RROutputPtr) value;

    ScreenPtr pScreen = output->pScreen;

    if (pScreen) {
        rrScrPriv(pScreen);
        int i;

        for (i = 0; i < pScrPriv->numOutputs; i++) {
            if (pScrPriv->outputs[i] == output) {
                memmove(pScrPriv->outputs + i, pScrPriv->outputs + i + 1,
                        (pScrPriv->numOutputs - (i + 1)) * sizeof(RROutputPtr));
                --pScrPriv->numOutputs;
                break;
            }
        }
    }
    if (output->modes)
        free(output->modes);
    if (output->crtcs)
        free(output->crtcs);
    if (output->clones)
        free(output->clones);
    RRDeleteAllOutputProperties(output);
    free(output);
    return 1;
}

/*
 * Initialize output type
 */
Bool
RROutputInit(void)
{
    RROutputType = CreateNewResourceType(RROutputDestroyResource);
    if (!RROutputType)
        return FALSE;
#ifdef XResExtension
    RegisterResourceName(RROutputType, "OUTPUT");
#endif
    return TRUE;
}

#define OutputInfoExtra	(SIZEOF(xRRGetOutputInfoReply) - 32)

int
ProcRRGetOutputInfo(ClientPtr client)
{
    REQUEST(xRRGetOutputInfoReq);;
    xRRGetOutputInfoReply rep;

    RROutputPtr output;

    CARD8 *extra;

    unsigned long extraLen;

    ScreenPtr pScreen;

    rrScrPrivPtr pScrPriv;

    RRCrtc *crtcs;

    RRMode *modes;

    RROutput *clones;

    char *name;

    int i;

    REQUEST_SIZE_MATCH(xRRGetOutputInfoReq);
    output = LookupOutput(client, stuff->output, SecurityReadAccess);

    if (!output)
        return RRErrorBase + BadRROutput;

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

    rep.type = X_Reply;
    rep.sequenceNumber = client->sequence;
    rep.length = OutputInfoExtra >> 2;
    rep.timestamp = pScrPriv->lastSetTime.milliseconds;
    rep.crtc = output->crtc ? output->crtc->id : None;
    rep.mmWidth = output->mmWidth;
    rep.mmHeight = output->mmHeight;
    rep.connection = output->connection;
    rep.subpixelOrder = output->subpixelOrder;
    rep.nCrtcs = output->numCrtcs;
    rep.nModes = output->numModes;
    rep.nPreferred = output->numPreferred;
    rep.nClones = output->numClones;
    rep.nameLength = output->nameLength;

    extraLen = ((output->numCrtcs +
                 output->numModes +
                 output->numClones + ((rep.nameLength + 3) >> 2)) << 2);

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

    crtcs = (RRCrtc *) extra;
    modes = (RRMode *) (crtcs + output->numCrtcs);
    clones = (RROutput *) (modes + output->numModes);
    name = (char *) (clones + output->numClones);

    for (i = 0; i < output->numCrtcs; i++) {
        crtcs[i] = output->crtcs[i]->id;
        if (client->swapped)
            swapl(&crtcs[i]);
    }
    for (i = 0; i < output->numModes; i++) {
        modes[i] = output->modes[i]->mode.id;
        if (client->swapped)
            swapl(&modes[i]);
    }
    for (i = 0; i < output->numClones; i++) {
        clones[i] = output->clones[i]->id;
        if (client->swapped)
            swapl(&clones[i]);
    }
    memcpy(name, output->name, output->nameLength);
    if (client->swapped) {
        swaps(&rep.sequenceNumber);
        swapl(&rep.length);
        swapl(&rep.timestamp);
        swapl(&rep.crtc);
        swapl(&rep.mmWidth);
        swapl(&rep.mmHeight);
        swaps(&rep.nCrtcs);
        swaps(&rep.nModes);
        swaps(&rep.nClones);
        swaps(&rep.nameLength);
    }
    WriteToClient(client, sizeof(xRRGetOutputInfoReply), (char *) &rep);
    if (extraLen) {
        WriteToClient(client, extraLen, (char *) extra);
        free(extra);
    }

    return client->noClientException;
}
