#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/* All drivers should typically include these */
#include "xf86.h"
#include "xf86_OSproc.h"

/* All drivers need this */

#include "compiler.h"

#include "mga.h"
#include "mga_macros.h"
#include "mga_reg.h"
#include "mga_merge.h"

#include "fbdevhw.h"

static int
StrToRanges(range* r, const char* s) {
    float num=0.0;
    int rangenum=0;
    Bool gotdash = FALSE;
    Bool nextdash = FALSE;
    const char* strnum=NULL;
    do {
        switch(*s) {
            case '0': case '1': case '2': case '3': case '4': case '5':
            case '6': case '7': case '8': case '9': case '.':
                if(strnum == NULL) {
                    strnum = s;
                    gotdash = nextdash;
                    nextdash = FALSE;
                 }
  
                break;
            case '-':
            case ' ': case 0:
                if(strnum == NULL) break; /*is extra seperator */
                if(strnum != NULL) sscanf(strnum,"%f",&num);
                if(gotdash) /*if wasn't singlet: correct. */
                    r[rangenum-1].hi = num;
                else { /*first, assume singlet */
                    r[rangenum].lo = num;
                    r[rangenum].hi = num;
                    rangenum++;
                }
                strnum = NULL;
                if(*s == '-')
                    nextdash = (rangenum != 0); /*ignore dash if before any number.*/
                break;
            default :
                return 0;        
        }
    } while(*(s++) != 0); /* run loop for every char including null terminator.*/
       
    return rangenum;
}            


/* Copys mode i, links the result to dest, and returns it.
 * Links i and j in Private record.
 * if dest is NULL, return value is copy of i linked to itself.
 */
static DisplayModePtr
CopyModeNLink(ScrnInfoPtr pScrn, DisplayModePtr dest, DisplayModePtr i, DisplayModePtr j, MgaScrn2Rel srel) {
    DisplayModePtr mode;
    int dx = 0,dy = 0;
    /* start with first node */
    mode = malloc(sizeof(DisplayModeRec));
    memcpy(mode,i, sizeof(DisplayModeRec));
    mode->Private = malloc(sizeof(MergedDisplayModeRec));
    ((MergedDisplayModePtr)mode->Private)->Monitor1 = i;
    ((MergedDisplayModePtr)mode->Private)->Monitor2 = j;
    ((MergedDisplayModePtr)mode->Private)->Monitor2Pos = srel;
    mode->PrivSize = 0;
    
        switch(srel) {
            case mgaLeftOf: 
            case mgaRightOf:
                dx = min(pScrn->virtualX,i->HDisplay + j->HDisplay) -  mode->HDisplay;
                dy = min(pScrn->virtualY,  max(i->VDisplay,j->VDisplay)) - mode->VDisplay;
                break;
            case mgaAbove: 
            case mgaBelow:
                dy = min(pScrn->virtualY,i->VDisplay + j->VDisplay) - mode->VDisplay;
                dx = min(pScrn->virtualX, max(i->HDisplay,j->HDisplay)) - mode->HDisplay;
                break;
            case mgaClone:
                dx = min(pScrn->virtualX, max(i->HDisplay,j->HDisplay)) - mode->HDisplay;
                dy = min(pScrn->virtualY, max(i->VDisplay,j->VDisplay)) - mode->VDisplay;
                break;
        }
    mode->HDisplay += dx;
    mode->HSyncStart += dx;
    mode->HSyncEnd += dx;
    mode->HTotal += dx;
    mode->VDisplay += dy;
    mode->VSyncStart += dy;
    mode->VSyncEnd += dy;
    mode->VTotal += dy;
    mode->Clock = 0; /* Shows we're in Merge mode. */
   
    mode->next = mode;
    mode->prev = mode;

    if(dest) {
        /* Insert node after "dest" */
        mode->next = dest->next; 
        dest->next->prev = mode;
        mode->prev = dest;
        dest->next = mode;
    } 

    return mode;
}

static DisplayModePtr
GetModeFromName(const char* str, DisplayModePtr i)
{
    DisplayModePtr c = i;
    if(!i) return NULL;
    do {
        if(strcmp(str,c->name) == 0) return c;
        c = c->next;
    } while(c != i);
    return NULL;
}

/* takes a config file string of MetaModes and generates a MetaModeList */
static DisplayModePtr
GenerateModeList(ScrnInfoPtr pScrn, const char* str,
		 DisplayModePtr i, DisplayModePtr j, MgaScrn2Rel srel) {
    const char* strmode = str;
    char modename[256];
    Bool gotdash = FALSE;
    MgaScrn2Rel sr;
    
    DisplayModePtr mode1 = NULL;
    DisplayModePtr mode2 = NULL;
    DisplayModePtr result = NULL;
    do {
        switch(*str) {
            case 0:
            case '-':
            case ' ':
	    case ',':
	    case ';':
                if((strmode != str)) {/*we got a mode */
                    /* read new entry */
                    strncpy(modename,strmode,str - strmode);
                    modename[str - strmode] = 0;
                    
                    if(gotdash) {
                        if(mode1 == NULL) return NULL;
                        mode2 = GetModeFromName(modename,j);
                        if(!mode2) {
                            xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
                                "Mode: \"%s\" is not a supported mode for monitor 2\n",modename);
                            xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
                                "Skipping metamode \"%s-%s\".\n",mode1->name,modename);
                            mode1 = NULL;
                        } 
                    } else {
                        mode1 = GetModeFromName(modename,i);
                        if(!mode1) {
                            const char* tmps = str;
                            xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
                                "Mode: \"%s\" is not a supported mode for monitor 1\n",modename);
                            /* find if a monitor2 mode follows */
                            gotdash = FALSE;
                            while(*tmps == ' ' || *tmps == ';') tmps++;
                            if(*tmps == '-' || *tmps == ',') { /* skip the next mode */
                                tmps++;
                                while(*tmps == ' ' || *tmps == ';') tmps++; /*skip spaces */
                                while(*tmps && *tmps != ' ' && *tmps != ';' && *tmps != '-' && *tmps != ',') tmps++; /*skip modename */
                                /* for error message */
                                strncpy(modename,strmode,tmps - strmode);
                                modename[tmps - strmode] = 0;
                                str = tmps-1;
                            }
                            xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
                                "Skipping metamode \"%s\".\n",modename);
                            mode1 = NULL;
                        } 
                    }
                    gotdash = FALSE;
                }
                strmode = str+1; /* number starts on next char */
                gotdash |= (*str == '-' || *str == ',');
                
                if(*str != 0) break; /* if end of string, we wont get a chance to catch a char and run the
                                        default case. do it now */
                
            default:
                if(!gotdash && mode1) { /* complete previous pair */
                    sr = srel;
                    if(!mode2) { 
                        mode2 = GetModeFromName(mode1->name,j);
                        sr = mgaClone;
                    }
                    if(!mode2) { 
                        xf86DrvMsg(pScrn->scrnIndex, X_CONFIG, 
                            "Mode: \"%s\" is not a supported mode for monitor 2\n",mode1->name);
                        xf86DrvMsg(pScrn->scrnIndex, X_CONFIG,
                            "Skipping clone mode \"%s\".\n", mode1->name);
                        mode1 = NULL;
                    } else {
                        result = CopyModeNLink(pScrn,result,mode1,mode2,sr);
                        mode1 = NULL;
                        mode2 = NULL;
                    }
                }
                break;
                
        }
    } while(*(str++) != 0);
    return result;
}
    

/* second CRTC init funcitons. Used to check monitor timings and refreshes. 
 * this function looses lots of maintainability points due to redundancy, 
 * but it still was the cleanest and least-intrusive way I found. */

Bool
MGAPreInitMergedFB(ScrnInfoPtr pScrn1, int flags)
{
    ScrnInfoPtr pScrn;
    MGAPtr pMga;
    MGAPtr pMga1;
    MessageType from;
    int i;
    const char* s;
    ClockRangePtr clockRanges;
    MgaScrn2Rel Monitor2Pos;

    xf86DrvMsg(pScrn1->scrnIndex, X_INFO, "==== Start of second screen initialization ====\n");
    pScrn = malloc(sizeof(ScrnInfoRec));
    memcpy(pScrn,pScrn1,sizeof(ScrnInfoRec));
   
    pScrn->driverPrivate = NULL; 
    /* Allocate the MGARec driverPrivate */
    if (!MGAGetRec(pScrn)) {
	return FALSE;
    }

    pMga = MGAPTR(pScrn);
    pMga1 = MGAPTR(pScrn1);
    pMga1->pScrn2 = pScrn;
  
    /* Get the entity, and make sure it is PCI. */
    pMga->pEnt = pMga1->pEnt;
    
    /* Set pMga->device to the relevant Device section */
    pMga->device = pMga1->device;
    
    if (flags & PROBE_DETECT) {
	MGAProbeDDC(pScrn, pMga->pEnt->index); /*FIXME make shure this probes second monitor */
	return TRUE;
    }

#ifndef XSERVER_LIBPCIACCESS
    pMga->PciTag = pMga1->PciTag;
#endif
    pMga->Primary = pMga1->Primary;

    /* Set pScrn->monitor */
    {
        pScrn->monitor = malloc(sizeof(MonRec));
        /* copy everything we don't care about */
        memcpy(pScrn->monitor,pScrn1->monitor,sizeof(MonRec));
        pScrn->monitor->DDC = NULL;   /*FIXME:have to try this */ 
        if ((s = xf86GetOptValString(pMga1->Options, OPTION_HSYNC2))) { 
            pScrn->monitor->nHsync = StrToRanges(pScrn->monitor->hsync,s);
        }
        if ((s = xf86GetOptValString(pMga1->Options, OPTION_VREFRESH2))) { 
            pScrn->monitor->nVrefresh = StrToRanges(pScrn->monitor->vrefresh,s);
        }
    
    
    
   }
  
    pMga->SecondCrtc = TRUE;
    pMga->HWCursor = FALSE;
    pScrn->AdjustFrame = MGAAdjustMergeFrames;
    pScrn1->AdjustFrame = MGAAdjustMergeFrames;
    
/*    if (!xf86SetDepthBpp(pScrn, 0, 0, 0, flags24))   FIXME:have to copy result form scrn1 
  if (!xf86SetWeight(pScrn, zeros, zeros)) {
*/

    /* We use a programamble clock */
    pScrn->progClock = TRUE;

    /* Collect all of the relevant option flags (fill in pScrn->options) */
    pScrn->options = pScrn1->options;

/*   xf86ProcessOptions(pScrn->scrnIndex, pScrn->options, pMga->Options);*/
    pMga->Options = pMga1->Options;

    
    /* Set the bits per RGB for 8bpp mode */
    if (pScrn->depth == 8)
	pScrn->rgbBits = 8;

    /*
     * Set the Chipset and ChipRev, allowing config file entries to
     * override.
     */
    pScrn->chipset = pScrn1->chipset;
    pMga->Chipset = pMga1->Chipset;
    pMga->ChipRev = pMga1->ChipRev;

#ifdef MGADRI
    pMga->agpMode = pMga1->agpMode;
#endif

    pMga->NoAccel = pMga1->NoAccel;
    pMga->UsePCIRetry = pMga1->UsePCIRetry;
    pMga->SyncOnGreen = pMga1->SyncOnGreen;
    pMga->ShowCache = pMga1->ShowCache;
    pMga->HasSDRAM = pMga1->HasSDRAM;
    pMga->MemClk = pMga1->MemClk;
    pMga->colorKey = pMga1->colorKey;
    pScrn->colorKey = pScrn1->colorKey;
    pScrn->overlayFlags = pScrn1->overlayFlags;
    pMga->videoKey = pMga1->videoKey;
    /* unsupported options */
    pMga->HWCursor = FALSE;
    pMga->ShadowFB = FALSE;
    pMga->FBDev = FALSE;
    
    pMga->OverclockMem = pMga1->OverclockMem;
    pMga->TexturedVideo = pMga1->TexturedVideo;
    pMga->MergedFB = TRUE;

    pMga->Rotate = 0;

    switch (pMga->Chipset) {
    case PCI_CHIP_MGA2064:
    case PCI_CHIP_MGA2164:
    case PCI_CHIP_MGA2164_AGP:
	MGA2064SetupFuncs(pScrn);
	break;
    case PCI_CHIP_MGA1064:
    case PCI_CHIP_MGAG100:
    case PCI_CHIP_MGAG100_PCI:
    case PCI_CHIP_MGAG200:
    case PCI_CHIP_MGAG200_PCI:
    case PCI_CHIP_MGAG200_SE_A_PCI:
    case PCI_CHIP_MGAG200_SE_B_PCI:
    case PCI_CHIP_MGAG200_WINBOND_PCI:
    case PCI_CHIP_MGAG200_EW3_PCI:
    case PCI_CHIP_MGAG200_EV_PCI:
    case PCI_CHIP_MGAG200_EH_PCI:
    case PCI_CHIP_MGAG200_ER_PCI:
    case PCI_CHIP_MGAG200_EH3_PCI:
    case PCI_CHIP_MGAG400:
    case PCI_CHIP_MGAG550:
	MGAGSetupFuncs(pScrn);
	break;
    }

    pMga->FbAddress = pMga1->FbAddress;
    pMga->PciInfo = pMga1->PciInfo;
#ifndef XSERVER_LIBPCIACCESS
    pMga->IOAddress = pMga1->IOAddress;
    pMga->ILOADAddress = pMga1->ILOADAddress;
    pMga->BiosFrom = pMga1->BiosFrom;
    pMga->BiosAddress = pMga1->BiosAddress;
#endif

    /*
     * Read the BIOS data struct
     */

    mga_read_and_process_bios( pScrn );

    /* HW bpp matches reported bpp */
    pMga->HwBpp = pMga1->HwBpp;

    /*
     * Reset card if it isn't primary one
     */
    if ( (!pMga->Primary && !pMga->FBDev) )
        MGASoftReset(pScrn);

    
    pScrn->videoRam = pScrn1->videoRam;
    pMga->FbMapSize = pMga1->FbMapSize;
    pMga->SrcOrg = pMga1->SrcOrg;
    pMga->DstOrg = pMga1->DstOrg;

   /* Set the bpp shift value */
    pMga->BppShifts[0] = 0;
    pMga->BppShifts[1] = 1;
    pMga->BppShifts[2] = 0;
    pMga->BppShifts[3] = 2;

    /*
     * fill MGAdac struct
     * Warning: currently, it should be after RAM counting
     */
    (*pMga->PreInit)(pScrn);

#if !defined(__powerpc__)

    /* Read and print the Monitor DDC info */
/*    pScrn->monitor->DDC = MGAdoDDC(pScrn);*/ /*FIXME: have to try this*/
#endif /* !__powerpc__ */

    /*
     * If the driver can do gamma correction, it should call xf86SetGamma()
     * here.
     */
    {
	Gamma zeros = {0.0, 0.0, 0.0};

	if (!xf86SetGamma(pScrn, zeros)) {
	    return FALSE;
	}
    }


    /* Set the min pixel clock */
    pMga->MinClock = pMga1->MinClock;	/* XXX Guess, need to check this */
    xf86DrvMsg(pScrn->scrnIndex, X_DEFAULT, "CRTC2: Min pixel clock is %d MHz\n",
	       pMga->MinClock / 1000);
   /* Override on 2nd crtc */

    if (pMga->ChipRev >= 0x80 || (pMga->Chipset == PCI_CHIP_MGAG550)) {
	/* G450, G550 */
        pMga->MaxClock = 234000;
    } else {
        pMga->MaxClock = 135000;
    }
    xf86DrvMsg(pScrn->scrnIndex, X_DEFAULT, "CRTC2: Max pixel clock is %d MHz\n",
	       pMga->MaxClock / 1000);
    /*
     * Setup the ClockRanges, which describe what clock ranges are available,
     * and what sort of modes they can be used for.
     */
    clockRanges = xnfcalloc(sizeof(ClockRange), 1);
    clockRanges->next = NULL;
    clockRanges->minClock = pMga->MinClock;
    clockRanges->maxClock = pMga->MaxClock;
    clockRanges->clockIndex = -1;		/* programmable */
    clockRanges->interlaceAllowed = TRUE;
    clockRanges->doubleScanAllowed = TRUE;
    clockRanges->interlaceAllowed = FALSE; /*no interlace on CRTC2 */

    clockRanges->ClockMulFactor = 1;
    clockRanges->ClockDivFactor = 1;
    /* Only set MemClk if appropriate for the ramdac */
    if (pMga->Dac.SetMemClk) {
	if (pMga->MemClk == 0) {
	    pMga->MemClk = pMga->Dac.MemoryClock;
	    from = pMga->Dac.MemClkFrom;
	} else
	    from = X_CONFIG;
	xf86DrvMsg(pScrn->scrnIndex, from, "CRTC2: MCLK used is %.1f MHz\n",
		   pMga->MemClk / 1000.0);
    }

    /*
     * xf86ValidateModes will check that the mode HTotal and VTotal values
     * don't exceed the chipset's limit if pScrn->maxHValue and
     * pScrn->maxVValue are set.  Since our MGAValidMode() already takes
     * care of this, we don't worry about setting them here.
     */
    {
	int Pitches1[] =
	  {640, 768, 800, 960, 1024, 1152, 1280, 1600, 1920, 2048, 0};
	int Pitches2[] =
	  {512, 640, 768, 800, 832, 960, 1024, 1152, 1280, 1600, 1664,
		1920, 2048, 0};
	int *linePitches = NULL;
	int minPitch = 256;
	int maxPitch = 2048;

        switch(pMga->Chipset) {
	case PCI_CHIP_MGA2064:
	   if (!pMga->NoAccel) {
		linePitches = malloc(sizeof(Pitches1));
		memcpy(linePitches, Pitches1, sizeof(Pitches1));
		minPitch = maxPitch = 0;
	   }
	   break;
	case PCI_CHIP_MGA2164:
	case PCI_CHIP_MGA2164_AGP:
	case PCI_CHIP_MGA1064:
	   if (!pMga->NoAccel) {
		linePitches = malloc(sizeof(Pitches2));
		memcpy(linePitches, Pitches2, sizeof(Pitches2));
		minPitch = maxPitch = 0;
	   }
	   break;
	case PCI_CHIP_MGAG100:
	case PCI_CHIP_MGAG100_PCI:
	   maxPitch = 2048;
	   break;
	case PCI_CHIP_MGAG200:
	case PCI_CHIP_MGAG200_PCI:
	case PCI_CHIP_MGAG200_SE_A_PCI:
	case PCI_CHIP_MGAG200_SE_B_PCI:
        case PCI_CHIP_MGAG200_WINBOND_PCI:
	case PCI_CHIP_MGAG200_EW3_PCI:		
        case PCI_CHIP_MGAG200_EV_PCI:
        case PCI_CHIP_MGAG200_EH_PCI:
	case PCI_CHIP_MGAG200_ER_PCI:		
	case PCI_CHIP_MGAG200_EH3_PCI:
	case PCI_CHIP_MGAG400:
	case PCI_CHIP_MGAG550:
	   maxPitch = 4096;
	   break;
	}

        pScrn->modePool=NULL;
        pScrn->modes = NULL;
	i = xf86ValidateModes(pScrn, pScrn->monitor->Modes,
			      pScrn->display->modes, clockRanges,
			      linePitches, minPitch, maxPitch,
			      pMga->Roundings[(pScrn->bitsPerPixel >> 3) - 1] *
					pScrn->bitsPerPixel, 128, 2048,
			      pScrn->display->virtualX,
			      pScrn->display->virtualY,
			      pMga->FbMapSize,
			      LOOKUP_BEST_REFRESH);
        
	free(linePitches);
    }


    if (i < 1 && pMga->FBDev) {
	fbdevHWUseBuildinMode(pScrn);
	pScrn->displayWidth = pScrn->virtualX; /* FIXME: might be wrong */
	i = 1;
    }
    if (i == -1) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "CRTC2: Validate Modes Failed\n");
	MGAFreeRec(pScrn);
	return FALSE;
    }

    /* Prune the modes marked as invalid */
    xf86PruneDriverModes(pScrn);

    if (i == 0 || pScrn->modes == NULL) {
	xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "CRTC2: No valid modes found\n");
	MGAFreeRec(pScrn);
	return FALSE;
    }

    /*
     * Set the CRTC parameters for all of the modes based on the type
     * of mode, and the chipset's interlace requirements.
     *
     * Calling this is required if the mode->Crtc* values are used by the
     * driver and if the driver doesn't provide code to set them.  They
     * are not pre-initialised at all.
     */
    MGA_NOT_HAL(xf86SetCrtcForModes(pScrn, INTERLACE_HALVE_V));

    /* Set the current mode to the first in the list */
    pScrn->currentMode = pScrn->modes;

    /* Print the list of modes being used */
    xf86PrintModes(pScrn);

    /* Set display resolution */
    xf86SetDpi(pScrn, 0, 0);

    /*
     * Compute the byte offset into the linear frame buffer where the
     * frame buffer data should actually begin.  According to DDK misc.c
     * line 1023, if more than 4MB is to be displayed, YDSTORG must be set
     * appropriately to align memory bank switching, and this requires a
     * corresponding offset on linear frame buffer access.
     * This is only needed for WRAM.
     */

    pMga->YDstOrg = pMga1->YDstOrg; 
    xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, 2, "CRTC2: YDstOrg is set to %d\n",
		   pMga->YDstOrg);
    pMga->FbUsableSize = pMga1->FbUsableSize;
    pMga->FbCursorOffset = pMga1->FbCursorOffset;

    pMga->CurrentLayout.bitsPerPixel = pScrn->bitsPerPixel;
    pMga->CurrentLayout.depth = pScrn->depth;
    pMga->CurrentLayout.displayWidth = pScrn->displayWidth;
    pMga->CurrentLayout.weight.red = pScrn->weight.red;
    pMga->CurrentLayout.weight.green = pScrn->weight.green;
    pMga->CurrentLayout.weight.blue = pScrn->weight.blue;
    pMga->CurrentLayout.mode = pScrn->currentMode;


    Monitor2Pos = mgaRightOf;
    if ((s = xf86GetOptValString(pMga1->Options, OPTION_MONITOR2POS))) {
        switch(s[0]) {
            case 'L': case 'l': case 'G': case 'g':
                Monitor2Pos = mgaLeftOf;
                break;
            case 'R': case 'r': case 'D': case 'd':
                Monitor2Pos = mgaRightOf;
                break;

            case 'A': case 'a': case 'H': case 'h':
                Monitor2Pos = mgaAbove;
                break;

            case 'B': case 'b':
                Monitor2Pos = mgaBelow;
                break;

            case 'C': case 'c':
                Monitor2Pos = mgaClone;
                break;
            default:
                Monitor2Pos = mgaRightOf;
                break;
        } 
    }

    /* Fool xf86 into thinking we have huge modes */
    /* Keep the original values somewhere */
    pMga1->M1modes = pScrn1->modes;
    pMga1->M1currentMode = pScrn1->currentMode;
    /* make a copy of the mode list, so we can modify it. */
    if ((s = xf86GetOptValString(pMga1->Options, OPTION_METAMODES))) {
        pScrn1->modes = GenerateModeList(pScrn,s,pMga1->M1modes,pScrn->modes,Monitor2Pos); /*FIXME: free this list*/
        if(!pScrn1->modes) {
	    xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Parse Error reading MetaModes, or No modes left.\n");
            return FALSE;
        }
            
        pScrn1->modes = pScrn1->modes->next;
        pScrn1->currentMode = pScrn1->modes;
    } else {
        xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "MetaModes option missing.\n");
        return FALSE;
    }
    xf86DrvMsg(pScrn1->scrnIndex, X_INFO, "==== End of second screen initialization ====\n");
    return TRUE;
}

void
MGADisplayPowerManagementSetMerged(ScrnInfoPtr pScrn, int PowerManagementMode,
				  int flags)
{
    MGADisplayPowerManagementSet(pScrn,PowerManagementMode,flags);
    MGADisplayPowerManagementSetCrtc2(pScrn,PowerManagementMode,flags);
}

typedef struct _region {
    int x0,x1,y0,y1;
    } region;

static Bool
InRegion(int x, int y, region r) {
    return (r.x0 <= x) && (x < r.x1) && (r.y0 <= y) && (y < r.y1);
}


#define BOUND(test,low,hi) { \
    if(test < low) test = low; \
    if(test > hi) test = hi; }
#define REBOUND(low,hi,test) { \
    if(test < low) { \
        hi += test-low; \
        low = test; } \
    if(test > hi) { \
        low += test-hi; \
        hi = test; } }
 void 
MGAMergePointerMoved(SCRN_ARG_TYPE arg, int x, int y)
{
  SCRN_INFO_PTR(arg);
  MGAPtr        pMga = MGAPTR(pScrn);
  ScrnInfoPtr   pScr2 = pMga->pScrn2;

  region out,in1,in2,f2,f1;

  int deltax,deltay;

  /* for ease. */
  f1.x0 = pMga->M1frameX0;
  f1.x1 = pMga->M1frameX1+1;
  f1.y0 = pMga->M1frameY0;
  f1.y1 = pMga->M1frameY1+1;
  f2.x0 = pScr2->frameX0;
  f2.x1 = pScr2->frameX1+1;
  f2.y0 = pScr2->frameY0;
  f2.y1 = pScr2->frameY1+1;


  /*specify outer clipping region. crossing this causes all frames to move*/
  out.x0 = pScrn->frameX0;
  out.x1 = pScrn->frameX1+1;
  out.y0 = pScrn->frameY0;
  out.y1 = pScrn->frameY1+1;

  /* 
   * specify inner sliding window. beeing outsize both frames, and inside 
   * the outer cliping window, causes corresponding frame to slide
   */
  in1 = out;
  in2 = out;
  switch(((MergedDisplayModePtr)pScrn->currentMode->Private)->Monitor2Pos) {
      case mgaLeftOf : 
          in1.x0 = f1.x0; 
          in2.x1 = f2.x1; 
          break;
      case mgaRightOf : 
          in1.x1 = f1.x1; 
          in2.x0 = f2.x0; 
          break;
      case mgaBelow : 
          in1.y1 = f1.y1; 
          in2.y0 = f2.y0; 
          break;
      case mgaAbove : 
          in1.y0 = f1.y0; 
          in2.y1 = f2.y1; 
          break;
      case mgaClone : 
          break;
      }

  
    deltay = 0;
    deltax = 0;
  
    if(InRegion(x,y,out)) {
        if( InRegion(x,y, in1) && !InRegion(x,y, f1) ) {
            REBOUND(f1.x0,f1.x1,x);
            REBOUND(f1.y0,f1.y1,y);
            deltax = 1; /*force frame update */
        }
        if( InRegion(x,y, in2) && !InRegion(x,y, f2) ) {
            REBOUND(f2.x0,f2.x1,x);
            REBOUND(f2.y0,f2.y1,y);
            deltax = 1; /*force frame update */
        }
    }
    else {  /*outside outer clipping region*/
        if ( out.x0 > x) { 
            deltax = x - out.x0;
        }
        if ( out.x1 < x) { 
            deltax = x - out.x1;
        }
        f1.x0 += deltax;
        f1.x1 += deltax;
        f2.x0 += deltax;
        f2.x1 += deltax;
        pScrn->frameX0 += deltax;
        pScrn->frameX1 += deltax;
        

        if ( out.y0 > y) { 
            deltay = y - out.y0;
        }
        if ( out.y1 < y) { 
            deltay = y - out.y1;
        }
        f1.y0 += deltay;
        f1.y1 += deltay;
        f2.y0 += deltay;
        f2.y1 += deltay;
        pScrn->frameY0 += deltay;
        pScrn->frameY1 += deltay;
    } 

    
    if (deltax != 0 || deltay != 0) {
        /* back to reality. */
        pMga->M1frameX0 = f1.x0; 
        pMga->M1frameY0 = f1.y0;
        pScr2->frameX0 = f2.x0;
        pScr2->frameY0 = f2.y0;

        /*Adjust Granularity */
        MGAAdjustGranularity(pScrn,&pMga->M1frameX0,&pMga->M1frameY0);
        MGAAdjustGranularity(pScrn,&pScr2->frameX0,&pScr2->frameY0);
        MGAAdjustGranularity(pScrn,&pScrn->frameX0,&pScrn->frameY0);
        
        pMga->M1frameX1 = pMga->M1frameX0 + MDMPTR(pScrn)->Monitor1->HDisplay -1;
        pMga->M1frameY1 = pMga->M1frameY0 + MDMPTR(pScrn)->Monitor1->VDisplay -1;
        pScr2->frameX1 = pScr2->frameX0 + MDMPTR(pScrn)->Monitor2->HDisplay -1;
        pScr2->frameY1 = pScr2->frameY0 + MDMPTR(pScrn)->Monitor2->VDisplay -1;
        pScrn->frameX1 = pScrn->frameX0 + pScrn->currentMode->HDisplay -1;
        pScrn->frameY1 = pScrn->frameY0 + pScrn->currentMode->VDisplay -1;
        
        MGAAdjustFrame(ADJUST_FRAME_ARGS(pScrn, pMga->M1frameX0, pMga->M1frameY0));
        MGAAdjustFrameCrtc2(ADJUST_FRAME_ARGS(pScrn, pScr2->frameX0, pScr2->frameY0));
    }

/*  if(pMga->PointerMoved)  
      (*pMga->PointerMoved)(scrnIndex, x, y);  FIXME: do I need to call old func?*/

}

   
void
MGAAdjustMergeFrames(ADJUST_FRAME_ARGS_DECL) {
    SCRN_INFO_PTR(arg);
    ScrnInfoPtr pScrn1 = pScrn;
    MGAPtr pMga = MGAPTR(pScrn1); 
    ScrnInfoPtr pScrn2 = pMga->pScrn2;
    int VTotal = pScrn1->currentMode->VDisplay; 
    int HTotal = pScrn1->currentMode->HDisplay; 
    int VMax = VTotal;
    int HMax = HTotal;

    BOUND(x,0,pScrn1->virtualX-HTotal);
    BOUND(y,0,pScrn1->virtualY-VTotal); 
    switch(MDMPTR(pScrn1)->Monitor2Pos) {
        case mgaLeftOf:
            pScrn2->frameX0 = x;
            BOUND(pScrn2->frameY0,y,y + VMax - MDMPTR(pScrn1)->Monitor2->VDisplay);
            pMga->M1frameX0 = x+MDMPTR(pScrn1)->Monitor2->HDisplay;
            BOUND(pMga->M1frameY0,y,y + VMax - MDMPTR(pScrn1)->Monitor1->VDisplay);
            break;
        case mgaRightOf:
            pMga->M1frameX0 = x;
            BOUND(pMga->M1frameY0,y,y + VMax - MDMPTR(pScrn1)->Monitor1->VDisplay);
            pScrn2->frameX0 = x+MDMPTR(pScrn1)->Monitor1->HDisplay;
            BOUND(pScrn2->frameY0,y,y + VMax - MDMPTR(pScrn1)->Monitor2->VDisplay);
            break;
        case mgaAbove:
            BOUND(pScrn2->frameX0,x,x + HMax - MDMPTR(pScrn1)->Monitor2->HDisplay);
            pScrn2->frameY0 = y;
            BOUND(pMga->M1frameX0,x,x + HMax - MDMPTR(pScrn1)->Monitor1->HDisplay);
            pMga->M1frameY0 = y+MDMPTR(pScrn1)->Monitor2->VDisplay;
            break;
        case mgaBelow:
            BOUND(pMga->M1frameX0,x,x + HMax - MDMPTR(pScrn1)->Monitor1->HDisplay);
            pMga->M1frameY0 = y;
            BOUND(pScrn2->frameX0,x,x + HMax - MDMPTR(pScrn1)->Monitor2->HDisplay);
            pScrn2->frameY0 = y+MDMPTR(pScrn1)->Monitor1->VDisplay;
            break;
        case mgaClone:
            BOUND(pMga->M1frameX0,x,x + HMax - MDMPTR(pScrn1)->Monitor1->HDisplay);
            BOUND(pMga->M1frameY0,y,y + VMax - MDMPTR(pScrn1)->Monitor1->VDisplay);
            BOUND(pScrn2->frameX0,x,x + HMax - MDMPTR(pScrn1)->Monitor2->HDisplay);
            BOUND(pScrn2->frameY0,y,y + VMax - MDMPTR(pScrn1)->Monitor2->VDisplay);
            break;
    }
    /* sanity checks. Make shure were not out of bounds */
    BOUND(pMga->M1frameX0,0,pScrn1->virtualX -MDMPTR(pScrn1)->Monitor1->HDisplay);
    BOUND(pMga->M1frameY0,0,pScrn1->virtualY -MDMPTR(pScrn1)->Monitor1->VDisplay);
    BOUND(pScrn2->frameX0,0,pScrn2->virtualX -MDMPTR(pScrn1)->Monitor2->HDisplay);
    BOUND(pScrn2->frameY0,0,pScrn2->virtualY -MDMPTR(pScrn1)->Monitor2->VDisplay);
    
    pScrn1->frameX0 = x;
    pScrn1->frameY0 = y;
    
    /* check granularity */
    MGAAdjustGranularity(pScrn1,&pMga->M1frameX0,&pMga->M1frameY0);
    MGAAdjustGranularity(pScrn1,&pScrn2->frameX0,&pScrn2->frameY0);
    MGAAdjustGranularity(pScrn1,&pScrn1->frameX0,&pScrn1->frameY0);
    
    /* complete shitty redundant info */
    pMga->M1frameX1 = pMga->M1frameX0 + MDMPTR(pScrn1)->Monitor1->HDisplay -1;
    pMga->M1frameY1 = pMga->M1frameY0 + MDMPTR(pScrn1)->Monitor1->VDisplay -1;
    pScrn2->frameX1 = pScrn2->frameX0 + MDMPTR(pScrn1)->Monitor2->HDisplay -1;
    pScrn2->frameY1 = pScrn2->frameY0 + MDMPTR(pScrn1)->Monitor2->VDisplay -1;
    pScrn1->frameX1 = pScrn1->frameX0 + pScrn1->currentMode->HDisplay -1;
    pScrn1->frameY1 = pScrn1->frameY0 + pScrn1->currentMode->VDisplay -1;

    MGAAdjustFrame(ADJUST_FRAME_ARGS(pScrn, pMga->M1frameX0, pMga->M1frameY0));
    MGAAdjustFrameCrtc2(ADJUST_FRAME_ARGS(pScrn, pScrn2->frameX0, pScrn2->frameY0));
    return;
}

Bool
MGACloseScreenMerged(ScreenPtr pScreen) {
    ScrnInfoPtr pScrn1 = xf86ScreenToScrn(pScreen);
    MGAPtr pMga = MGAPTR(pScrn1); 
    ScrnInfoPtr pScrn2 = pMga->pScrn2;

    if(pScrn2) {
        free(pScrn2->monitor);
        pScrn2->monitor = NULL;

        free(pScrn2);
        pMga->pScrn2 = NULL;
    }

    if(pScrn1->modes) {
        pScrn1->currentMode = pScrn1->modes;
        do {
            DisplayModePtr p = pScrn1->currentMode->next; 
            free(pScrn1->currentMode->Private);
            free(pScrn1->currentMode);
            pScrn1->currentMode = p;
        }while( pScrn1->currentMode != pScrn1->modes);
    }
 
    pScrn1->currentMode = pMga->M1currentMode;
    pScrn1->modes = pMga->M1modes;
        
    return TRUE;       
}

Bool
MGASaveScreenMerged(ScreenPtr pScreen, int mode)
{
    ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
    MGAPtr pMga = MGAPTR(pScrn); 
    BOOL on = xf86IsUnblank(mode);
    CARD8 reg;
    
    if (on) {
/*        SetTimdeSinceLastInputEvent();*/

        /* power on Dac1 */
        reg = inMGAdac(MGA1064_MISC_CTL);
        reg |= MGA1064_MISC_CTL_DAC_EN;
        outMGAdac(MGA1064_MISC_CTL, reg);

        /* power on Dac2 */
        reg = inMGAdac(MGA1064_PWR_CTL);
        reg |= MGA1064_PWR_CTL_DAC2_EN;
        outMGAdac(MGA1064_PWR_CTL, reg);
    } else {
        /* power off Dac1 */
        reg = inMGAdac(MGA1064_MISC_CTL);
        reg &= ~MGA1064_MISC_CTL_DAC_EN;
        outMGAdac(MGA1064_MISC_CTL, reg);

        /* power off Dac2 */
        reg = inMGAdac(MGA1064_PWR_CTL);
        reg &= ~MGA1064_PWR_CTL_DAC2_EN;
        outMGAdac(MGA1064_PWR_CTL, reg);
    }

    return TRUE;
}


