/* 
 * 
 * Copyright (c) 1997  Metro Link Incorporated
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of the Metro Link shall not be
 * used in advertising or otherwise to promote the sale, use or other dealings
 * in this Software without prior written authorization from Metro Link.
 * 
 */

#include "xf86Parser.h"
#include "xf86tokens.h"
#include "Configint.h"



/*
 * xconfigAddRemovedOptionComment() - Makes a note in the comment
 * string "existing_comments" that a particular option has been
 * removed.
 *
 */
static void xconfigAddRemovedOptionComment(char **existing_comments,
                                           XConfigOptionPtr option)
{
    int len;
    char *str;
    char *name, *value;

    if (!option || !existing_comments)
        return;

    name = xconfigOptionName(option);
    value = xconfigOptionValue(option);

    if (!name) return;

    if (value) {
        len = 32 + strlen(name) + strlen(value);
        str = malloc(len);
        if (!str) return;
        snprintf(str, len, "# Removed Option \"%s\" \"%s\"", name, value);
    } else {
        len = 32 + strlen(name);
        str = malloc(len);
        if (!str) return;
        snprintf(str, len, "# Removed Option \"%s\"", name);
    }

    *existing_comments = xconfigAddComment(*existing_comments, str);

} /* xconfigAddRemovedOptionComment() */



/*
 * xconfigRemoveNamedOption() - Removes the named option from an option
 * list and (if specified) adds a comment to an existing comments string
 *
 */
void xconfigRemoveNamedOption(XConfigOptionPtr *pHead, const char *name,
                              char **comments)
{
    XConfigOptionPtr option;

    option = xconfigFindOption(*pHead, name);
    if (option) {
        if (comments) {
            xconfigAddRemovedOptionComment(comments, option);
        }
        xconfigRemoveOption(pHead, option);
    }

} /* xconfigRemoveNamedOption() */



/*
 * xconfigOptionValuesDiffer() - return '1' if the option values for
 * option0 and option1 are different; return '0' if the option values
 * are the same.
 */

static int xconfigOptionValuesDiffer(XConfigOptionPtr option0,
                                     XConfigOptionPtr option1)
{
    char *value0, *value1;

    value0 = value1 = NULL;

    if (!option0 && !option1) return 0;
    if (!option0 &&  option1) return 1;
    if ( option0 && !option1) return 1;

    value0 = xconfigOptionValue(option0);
    value1 = xconfigOptionValue(option1);

    if (!value0 && !value1) return 0;
    if (!value0 &&  value1) return 1;
    if ( value0 && !value1) return 1;

    return (strcmp(value0, value1) != 0);

} /* xconfigOptionValuesDiffer() */



/*
 * xconfigMergeOption() - Merge option "name" from option source
 * list "srcHead" to option destination list "dstHead".
 *
 * Merging here means:
 *
 * If the option is not in the source config, do nothing to the
 * destination.  Otherwise, either add or update the option in
 * the dest.  If the option is modified, and a comment is given,
 * then the old option will be commented out instead of being
 * simply removed/replaced.
 */
static void xconfigMergeOption(XConfigOptionPtr *dstHead,
                               XConfigOptionPtr *srcHead,
                               const char *name, char **comments)
{
    XConfigOptionPtr srcOption = xconfigFindOption(*srcHead, name);
    XConfigOptionPtr dstOption = xconfigFindOption(*dstHead, name);

    char *srcValue = NULL;

    if (!srcOption) {
        /* Option does not exist in src, do nothing to dst. */
        return;
    }

    srcValue = xconfigOptionValue(srcOption);

    if (srcOption && !dstOption) {

        /* option exists in src but not in dst: add to dst */
        xconfigAddNewOption(dstHead, name, srcValue);

    } else if (srcOption && dstOption) {

        /*
         * option exists in src and in dst; if the option values are
         * different, replace the dst's option value with src's option
         * value; note that xconfigAddNewOption() will remove the old
         * option first, if necessary
         */

        if (xconfigOptionValuesDiffer(srcOption, dstOption)) {
            if (comments) {
                xconfigAddRemovedOptionComment(comments, dstOption);
            }
            xconfigAddNewOption(dstHead, name, srcValue);
        }
    }

} /* xconfigMergeOption() */



/*
 * xconfigMergeFlags() - Updates the destination's list of server flag
 * options with the options found in the source config.
 *
 * Optons in the destination are either added or updated.  Options that
 * are found in the destination config and not in the source config are
 * not modified.
 *
 * Returns 1 if the merge was successful and 0 if not.
 */
static int xconfigMergeFlags(XConfigPtr dstConfig, XConfigPtr srcConfig)
{
    if (srcConfig->flags) {
        XConfigOptionPtr option;
        
        /* Flag section was not found, create a new one */
        if (!dstConfig->flags) {
            dstConfig->flags =
                (XConfigFlagsPtr) calloc(1, sizeof(XConfigFlagsRec));
            if (!dstConfig->flags) return 0;
        }
        
        option = srcConfig->flags->options;
        while (option) {
            xconfigMergeOption(&(dstConfig->flags->options),
                               &(srcConfig->flags->options),
                               xconfigOptionName(option),
                               &(dstConfig->flags->comment));
            option = option->next;
        }
    }
    
    return 1;

} /* xconfigMergeFlags() */



/*
 * xconfigMergeMonitors() - Updates information in the destination monitor
 * with that of the source monitor.
 *
 */
static void xconfigMergeMonitors(XConfigMonitorPtr dstMonitor,
                                 XConfigMonitorPtr srcMonitor)
{
    int i;


    /* Update vendor */
    
    free(dstMonitor->vendor);
    dstMonitor->vendor = xconfigStrdup(srcMonitor->vendor);
    
    /* Update modelname */
    
    free(dstMonitor->modelname);
    dstMonitor->modelname = xconfigStrdup(srcMonitor->modelname);
    
    /* Update horizontal sync */
    
    dstMonitor->n_hsync = srcMonitor->n_hsync;
    for (i = 0; i < srcMonitor->n_hsync; i++) {
        dstMonitor->hsync[i].lo = srcMonitor->hsync[i].lo;
        dstMonitor->hsync[i].hi = srcMonitor->hsync[i].hi;
    }
    
    /* Update vertical sync */
    
    dstMonitor->n_vrefresh = srcMonitor->n_vrefresh;
    for (i = 0; i < srcMonitor->n_hsync; i++) {
        dstMonitor->vrefresh[i].lo = srcMonitor->vrefresh[i].lo;
        dstMonitor->vrefresh[i].hi = srcMonitor->vrefresh[i].hi;
    }
    
    /* XXX Remove the destination monitor's "UseModes" references to
     *     avoid having the wrong modelines tied to the new monitor.
     */
    xconfigFreeModesLinkList(&dstMonitor->modes_sections);

} /* xconfigMergeMonitors() */



/*
 * xconfigMergeAllMonitors() - This function ensures that all monitors in
 * the source config appear in the destination config by adding and/or
 * updating the "appropriate" destination monitor sections.
 *
 */
static int xconfigMergeAllMonitors(XConfigPtr dstConfig, XConfigPtr srcConfig)
{
    XConfigMonitorPtr dstMonitor;
    XConfigMonitorPtr srcMonitor;


    /* Make sure all monitors in the src config are also in the dst config */

    for (srcMonitor = srcConfig->monitors;
         srcMonitor;
         srcMonitor = srcMonitor->next) {

        dstMonitor =
            xconfigFindMonitor(srcMonitor->identifier, dstConfig->monitors);

        /* Monitor section was not found, create a new one and add it */
        if (!dstMonitor) {
            dstMonitor =
                (XConfigMonitorPtr) calloc(1, sizeof(XConfigMonitorRec));
            if (!dstMonitor) return 0;

            dstMonitor->identifier = xconfigStrdup(srcMonitor->identifier);

            xconfigAddListItem((GenericListPtr *)(&dstConfig->monitors),
                               (GenericListPtr)dstMonitor);
        }

        /* Do the merge */
        xconfigMergeMonitors(dstMonitor, srcMonitor);
    }

    return 1;

} /* xconfigMergeAllMonitors() */



/*
 * xconfigMergeDevices() - Updates information in the destination device
 * with that of the source device.
 *
 */
static void xconfigMergeDevices(XConfigDevicePtr dstDevice,
                                XConfigDevicePtr srcDevice)
{
    // XXX Zero out the device section?

    /* Update driver */
    
    free(dstDevice->driver);
    dstDevice->driver = xconfigStrdup(srcDevice->driver);
    
    /* Update vendor */
    
    free(dstDevice->vendor);
    dstDevice->vendor = xconfigStrdup(srcDevice->vendor);
    
    /* Update bus ID */
    
    free(dstDevice->busid);
    dstDevice->busid = xconfigStrdup(srcDevice->busid);
    
    /* Update board */
    
    free(dstDevice->board);
    dstDevice->board = xconfigStrdup(srcDevice->board);
    
    /* Update chip info */
    
    dstDevice->chipid = srcDevice->chipid;
    dstDevice->chiprev = srcDevice->chiprev;

    /* Update IRQ */

    dstDevice->irq = srcDevice->irq;
    
    /* Update screen */
    
    dstDevice->screen = srcDevice->screen;

} /* xconfigMergeDevices() */



/*
 * xconfigMergeAllDevices() - This function ensures that all devices in
 * the source config appear in the destination config by adding and/or
 * updating the "appropriate" destination device sections.
 *
 */
static int xconfigMergeAllDevices(XConfigPtr dstConfig, XConfigPtr srcConfig)
{
    XConfigDevicePtr dstDevice;
    XConfigDevicePtr srcDevice;


    /* Make sure all monitors in the src config are also in the dst config */

    for (srcDevice = srcConfig->devices;
         srcDevice;
         srcDevice = srcDevice->next) {

        dstDevice =
            xconfigFindDevice(srcDevice->identifier, dstConfig->devices);
        
        /* Device section was not found, create a new one and add it */
        if (!dstDevice) {
            dstDevice =
                (XConfigDevicePtr) calloc(1, sizeof(XConfigDeviceRec));
            if (!dstDevice) return 0;

            dstDevice->identifier = xconfigStrdup(srcDevice->identifier);

            xconfigAddListItem((GenericListPtr *)(&dstConfig->devices),
                               (GenericListPtr)dstDevice);
        }

        /* Do the merge */
        xconfigMergeDevices(dstDevice, srcDevice);
    }

    return 1;

} /* xconfigMergeAllDevices() */



/*
 * xconfigMergeDriverOptions() - Update the (Screen) driver options
 * of the destination config with information from the source config.
 *
 * - Assumes the source options are all found in the srcScreen->options.
 * - Updates only those options listed in the srcScreen->options.
 *
 */
static int xconfigMergeDriverOptions(XConfigScreenPtr dstScreen,
                                     XConfigScreenPtr srcScreen)
{
    XConfigOptionPtr option;
    XConfigDisplayPtr display;

    option = srcScreen->options;
    while (option) {
        char *name = xconfigOptionName(option);

        /* Remove the option from all non-screen option lists */
        
        if (dstScreen->device) {
            xconfigRemoveNamedOption(&(dstScreen->device->options), name,
                                     &(dstScreen->device->comment));
        }
        if (dstScreen->monitor) {
            xconfigRemoveNamedOption(&(dstScreen->monitor->options), name,
                                     &(dstScreen->monitor->comment));
        }       
        for (display = dstScreen->displays; display; display = display->next) {
            xconfigRemoveNamedOption(&(display->options), name,
                                     &(display->comment));
        }

        /* Update/Add the option to the screen's option list */
        {
            // XXX Only add a comment if the value changed.
            XConfigOptionPtr old =
                xconfigFindOption(dstScreen->options, name);

            if (old && xconfigOptionValuesDiffer(option, old)) {
                xconfigRemoveNamedOption(&(dstScreen->options), name,
                                         &(dstScreen->comment));
            } else {
                xconfigRemoveNamedOption(&(dstScreen->options), name,
                                         NULL);
            }
        }

        /* Add the option to the screen->options list */

        xconfigAddNewOption(&dstScreen->options,
                            name, xconfigOptionValue(option));
        
        option = option->next;
    }

    return 1;

} /* xconfigMergeDriverOptions() */



/*
 * xconfigMergeDisplays() - Duplicates display information from the
 * source screen to the destination screen.
 *
 */
static int xconfigMergeDisplays(XConfigScreenPtr dstScreen,
                                XConfigScreenPtr srcScreen)
{
    XConfigDisplayPtr dstDisplay;
    XConfigDisplayPtr srcDisplay;
    XConfigModePtr srcMode, dstMode, lastDstMode;

    /* Free all the displays in the destination screen */

    xconfigFreeDisplayList(&dstScreen->displays);

    /* Copy all te displays */
    
    for (srcDisplay = srcScreen->displays;
         srcDisplay;
         srcDisplay = srcDisplay->next) {

        /* Create a new display */

        dstDisplay = xconfigAlloc(sizeof(XConfigDisplayRec));
        if (!dstDisplay) return 0;

        /* Copy display fields */

        dstDisplay->frameX0 = srcDisplay->frameX0;
        dstDisplay->frameY0 = srcDisplay->frameY0;
        dstDisplay->virtualX = srcDisplay->virtualX;
        dstDisplay->virtualY = srcDisplay->virtualY;
        dstDisplay->depth = srcDisplay->depth;
        dstDisplay->bpp = srcDisplay->bpp;
        dstDisplay->visual = xconfigStrdup(srcDisplay->visual);
        dstDisplay->weight = srcDisplay->weight;
        dstDisplay->black = srcDisplay->black;
        dstDisplay->white = srcDisplay->white;
        dstDisplay->comment = xconfigStrdup(srcDisplay->comment);

        /* Copy options over */

        dstDisplay->options = xconfigOptionListDup(srcDisplay->options);

        /* Copy modes over */

        lastDstMode = NULL;
        srcMode = srcDisplay->modes;
        dstMode = NULL;
        while (srcMode) {

            /* Copy the mode */
            
            xconfigAddMode(&dstMode, srcMode->mode_name);

            /* Add mode at the end of the list */

            if ( !lastDstMode ) {
                dstDisplay->modes = dstMode;
            } else {
                lastDstMode->next = dstMode;
            }
            lastDstMode = dstMode;

            srcMode = srcMode->next;
        }

        xconfigAddListItem((GenericListPtr *)(&dstScreen->displays),
                           (GenericListPtr)dstDisplay);
    }

    return 1;

} /* xconfigMergeDisplays() */



/*
 * xconfigMergeScreens() - Updates information in the destination screen
 * with that of the source screen.
 *
 * NOTE: This assumes the Monitor and Device sections have already been
 *       merged.
 *
 */
static void xconfigMergeScreens(XConfigScreenPtr dstScreen,
                                XConfigPtr dstConfig,
                                XConfigScreenPtr srcScreen,
                                XConfigPtr srcConfig)
{
    /* Use the right device */
    
    free(dstScreen->device_name);
    dstScreen->device_name = xconfigStrdup(srcScreen->device_name);
    dstScreen->device =
        xconfigFindDevice(dstScreen->device_name, dstConfig->devices);
    

    /* Use the right monitor */
    
    free(dstScreen->monitor_name);
    dstScreen->monitor_name = xconfigStrdup(srcScreen->monitor_name);
    dstScreen->monitor =
        xconfigFindMonitor(dstScreen->monitor_name, dstConfig->monitors);
    

    /* Update the right default depth */
    
    dstScreen->defaultdepth = srcScreen->defaultdepth;
    

    /* Copy over the display section */
    
    xconfigMergeDisplays(dstScreen, srcScreen);
   

    /* Update the screen's driver options */

    xconfigMergeDriverOptions(dstScreen, srcScreen);

} /* xconfigMergeScreens() */



/*
 * xconfigMergeAllScreens() - This function ensures that all screens in
 * the source config appear in the destination config by adding and/or
 * updating the "appropriate" destination screen sections.
 *
 */
static int xconfigMergeAllScreens(XConfigPtr dstConfig, XConfigPtr srcConfig)
{
    XConfigScreenPtr srcScreen;
    XConfigScreenPtr dstScreen;


    /* Make sure all src screens are in the dst config */

    for (srcScreen = srcConfig->screens;
         srcScreen;
         srcScreen = srcScreen->next) {

        dstScreen =
            xconfigFindScreen(srcScreen->identifier, dstConfig->screens);

        /* Screen section was not found, create a new one and add it */
        if (!dstScreen) {
            dstScreen =
                (XConfigScreenPtr) calloc(1, sizeof(XConfigScreenRec));
            if (!dstScreen) return 0;

            dstScreen->identifier = xconfigStrdup(srcScreen->identifier);

            xconfigAddListItem((GenericListPtr *)(&dstConfig->screens),
                               (GenericListPtr)dstScreen);
        }

        /* Do the merge */
        xconfigMergeScreens(dstScreen, dstConfig, srcScreen, srcConfig);
    }

    return 1;

} /* xconfigMergeAllScreens() */



/*
 * xconfigMergeLayout() - Updates information in the destination's first
 * layout with that of the source's first layout.
 *
 */
static int xconfigMergeLayout(XConfigPtr dstConfig, XConfigPtr srcConfig)
{
    XConfigLayoutPtr srcLayout = srcConfig->layouts;
    XConfigLayoutPtr dstLayout = dstConfig->layouts;

    XConfigAdjacencyPtr srcAdj;
    XConfigAdjacencyPtr dstAdj;
    XConfigAdjacencyPtr lastDstAdj;

    if (!dstLayout || !srcLayout) {
        return 0;
    }

    /* Clear the destination's adjacency list */

    xconfigFreeAdjacencyList(&dstLayout->adjacencies);
    
    /* Copy adjacencies over */
    
    lastDstAdj = NULL;
    srcAdj = srcLayout->adjacencies;
    while (srcAdj) {
        
        /* Copy the adjacency */
        
        dstAdj =
            (XConfigAdjacencyPtr) calloc(1, sizeof(XConfigAdjacencyRec));

        dstAdj->scrnum = srcAdj->scrnum;
        dstAdj->screen_name = xconfigStrdup(srcAdj->screen_name);
        dstAdj->top_name = xconfigStrdup(srcAdj->top_name);
        dstAdj->bottom_name = xconfigStrdup(srcAdj->bottom_name);
        dstAdj->left_name = xconfigStrdup(srcAdj->left_name);
        dstAdj->right_name = xconfigStrdup(srcAdj->right_name);
        dstAdj->where = srcAdj->where;
        dstAdj->x = srcAdj->x;
        dstAdj->y = srcAdj->y;
        dstAdj->refscreen = xconfigStrdup(srcAdj->refscreen);

        dstAdj->screen =
            xconfigFindScreen(dstAdj->screen_name, dstConfig->screens);
        dstAdj->top =
            xconfigFindScreen(dstAdj->top_name, dstConfig->screens);
        dstAdj->bottom =
            xconfigFindScreen(dstAdj->bottom_name, dstConfig->screens);
        dstAdj->left =
            xconfigFindScreen(dstAdj->left_name, dstConfig->screens);
        dstAdj->right =
            xconfigFindScreen(dstAdj->right_name, dstConfig->screens);

        /* Add adjacency at the end of the list */
        
        if (!lastDstAdj) {
            dstLayout->adjacencies = dstAdj;
        } else {
            lastDstAdj->next = dstAdj;
        }
        lastDstAdj = dstAdj;
        
        srcAdj = srcAdj->next;
    }

    /* Merge the options */
    
    if (srcLayout->options) {
        XConfigOptionPtr srcOption;

        srcOption = srcLayout->options;
        while (srcOption) {
            xconfigMergeOption(&(dstLayout->options),
                               &(srcLayout->options),
                               xconfigOptionName(srcOption),
                               &(dstLayout->comment));
            srcOption = srcOption->next;
        }
    }

    return 1;

} /* xconfigMergeLayout() */



/*
 * xconfigMergeExtensions() - Updates information in the destination's extension
 * section with that of the source's extension section. 
 * Currently considering composite extension only.
 *
 */
static int  xconfigMergeExtensions(XConfigPtr dstConfig, XConfigPtr srcConfig)
{
   if (srcConfig->extensions) {
        XConfigOptionPtr option;

        /* Extension section was not found, create a new one */
        if (!dstConfig->extensions) {
            dstConfig->extensions =
                (XConfigExtensionsPtr) calloc(1, sizeof(XConfigExtensionsRec));
            if (!dstConfig->extensions) return 0;
        }

        option = srcConfig->extensions->options;
        while (option) {
            xconfigMergeOption(&(dstConfig->extensions->options),
                               &(srcConfig->extensions->options),
                               xconfigOptionName(option),
                               &(dstConfig->extensions->comment));
            option = option->next;
        }
    }

    return 1;

} /* xconfigMergeExtensions() */

/*
 * xconfigMergeConfigs() - Merges the source X configuration with the
 * destination X configuration.
 *
 * NOTE: This function is currently only used for merging X config files
 *       for display configuration reasons.  As such, the merge assumes
 *       that the dst config file is the target config file and that
 *       mostly, only new display configuration information should be
 *       copied from the source X config to the destination X config.
 *
 */
int xconfigMergeConfigs(XConfigPtr dstConfig, XConfigPtr srcConfig)
{
    /* Make sure the X config is valid */
    // make_xconfig_usable(dstConfig);


    /* Merge the server flag (Xinerama) section */

    if (!xconfigMergeFlags(dstConfig, srcConfig)) {
        return 0;
    }


    /* Merge the monitor sections */

    if (!xconfigMergeAllMonitors(dstConfig, srcConfig)) {
        return 0;
    }


    /* Merge the device sections */

    if (!xconfigMergeAllDevices(dstConfig, srcConfig)) {
        return 0;
    }


    /* Merge the screen sections */

    if (!xconfigMergeAllScreens(dstConfig, srcConfig)) {
        return 0;
    }


    /* Merge the first layout */
    
    if (!xconfigMergeLayout(dstConfig, srcConfig)) {
        return 0;
    }

    /* Merge the extensions */
    
    if (!xconfigMergeExtensions(dstConfig, srcConfig)) {
        return 0;
    }


    return 1;

} /* xconfigMergeConfigs() */
