/******************************************************************************
*
*	$Id: copper.c,v 39.21 93/05/06 12:10:47 spence Exp Locker: spence $
*
******************************************************************************/

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/alerts.h>
#include "/copper.h"
#include "/gfx.h"
#include "/monitor.h"
#include "tune.h"
#include "/view.h"
#include "/display.h"
#include "/gfxbase.h"
#include "/macros.h"
#include "/gfxpragmas.h"
#include "gfxprivate.h"
#include <hardware/custom.h>
#include <pragmas/exec_pragmas.h>
#include "c.protos"

/* copper driver */
/*#define DEBUG*/
#define USEGBDEBUG
 
#ifdef USEGBDEBUG
#define GBDEBUG if (GBASE->Debug)
#else
#define GBDEBUG
#endif

#ifdef DEBUG
#define D(x) {GBDEBUG {x};}
#else
#define D(x)
#endif

/* #define NO_CLIP_VP */

#define QWE

#ifdef QWE
#define HFIX(x) (((x)*(cs->ratioh))>>4)
#else
#define HFIX(x)	((x))
#endif

#define CLIP_VPOS(x)    (MAX((x),0))
/* #define CLIP_VPOS(x)    (MAX((x),min_ytop)) */ 
/* clip to absolute minimum of 0 to allow for "pre-screen" instructions */

#define MAKE_DY_POS(x)	(x)
/* #define MAKE_DY_POS(x)	(MAX((x),0)) */
/* tmp hack from Dale, Bart needs to check out */

/*#define HACKDEBUG*/

/*#define NAMEDEBUG*/

#define NEWDOWNCODE

extern struct Custom custom;

static struct copstuff
{
    struct CopList *first;
    SHORT   *copptr;
    SHORT   *copIptr;
    SHORT   maxins;	/* number of allocated slots */
    SHORT   beamx,beamy;
    SHORT   flags;
    SHORT   cnt;    /* number of instructions for LOF list left */
    SHORT   icnt;   /*      for SHORT FRAME LIST left*/
    SHORT   DyOffset;   /* from view */
    UWORD   moreflags;	/* misc */
    SHORT   *fcopptr;	/* first copptr location for hack_hwait stuff */
    SHORT   *cop2ptr;	/* hedley stuff */
    SHORT   *cop3ptr;	/* hedley stuff */
    SHORT   *cop4ptr;	/* hedley stuff */
    SHORT   *cop5ptr;	/* hedley stuff */
    SHORT   cnt2;
    SHORT   cnt3;
    SHORT   cnt4;
    SHORT   cnt5;
#ifdef QWE
    int ratioh;
    UWORD colorclocks;
#endif
    SHORT   ibeamx,ibeamy;
    ULONG   topline;
    UWORD   thisbeamy;
    UWORD   totalrows;
};
#define COPPERVPOS_OVERFLOW 1
#define COPPERIVPOS_OVERFLOW 8
#define STRADDLES_256 VPXF_STRADDLES_256
#define STRADDLES_512 VPXF_STRADDLES_512
#define WHOLE_LINE 64

#ifdef RAMDEBUG
extern	UBYTE	Debug;
#endif

#ifndef	NO_CLIP_VP

void __regargs mark_coplist(struct CopList *cl,UWORD mark)
{
    struct CopIns *c = cl->CopPtr;

    do
    {
	if((3&(c->OpCode)) == CPRNXTBUF)
	{
		cl = c->NXTLIST; 
		if(!cl) goto MARK_DONE;
		c = cl->CopPtr;
	}
	do
	{
	    if((3&(c->OpCode)) == COPPER_WAIT)
	    {
		if ((c->VWAITPOS == 10000) && (c->HWAITPOS == 255))
		{
		    goto MARK_DONE;
		}
		else
		{
		    c->OpCode |= mark;
		    c++;
		}
	    }

	    while ((3&(c->OpCode)) == COPPER_MOVE) 
	    {
		c->OpCode |= mark;
		c++;
	    }

	}   while ((3&(c->OpCode)) == COPPER_WAIT);

    }   while ((3&(c->OpCode)) == CPRNXTBUF);

MARK_DONE:	;

}

#endif

void __regargs append_clist(cstuff,element,vp,user)
struct copstuff *cstuff;
struct CopList *element;
struct ViewPort *vp;
BOOL user;
{
    struct CopList *cl;

#ifndef	NO_CLIP_VP
    struct ColorMap *cm = vp->ColorMap;
#endif

    D(kprintf("checking cstuff->first=%lx element=%lx\n",cstuff->first,element);)
    if (element != 0) 
	if (element->Count != 0)
	{
	    /* count instructions */
	    D(kprintf("now count instructions in this list\n");)
	    cl = cstuff->first;
	    cstuff->first = element;
	    for( ; element; element = element->Next)
	    {
		element->CopSStart = 0;
		element->CopLStart = 0;
		element->_CopList = cl; /* link to next */
		element->_ViewPort = vp;
		element->CopPtr = element->CopIns;  /* initialize */
		cstuff->maxins += element->Count;   /* count instructions */
	    }

#ifndef	NO_CLIP_VP

	    if( (user) && (cm) && (cm->Flags & USER_COPPER_CLIP)) 
	    {
		mark_coplist( cstuff->first, CPR_NT_SYS );
	    }

#endif
	    /* removing next line takes care of most */
	    /* out of memory cases for wait(260)->255,5 hack */
	    /*cstuff->maxins--;*/   /* try not to count multiple ends */
	}
}

BOOL __asm IsAUCopIns(register __a0 struct ViewPort *vp, register __a1 struct CopList *cl);

#define PROTOTYPE_DISPLAY
#ifdef  PROTOTYPE_DISPLAY

#include "/displayinfo_internal.h"

#define MrgCopAllocMem(numbytes,flags) (APTR)(AllocMem(numbytes,flags))

ULONG mrgcop(view)
register struct View *view;
{
    /* merge the different srcs of copper instructions into one stream */
    struct CopIns *c,*old_c;  /* ptr to copper bitplane instructions */
    struct CopList *cl,*nxtcl, *prevcl = NULL;
    struct UCopList *qwe;
    LONG nxtbeam;
    struct copstuff copstuff;
    struct cprlist *loflist,*shflist;
    struct ViewPort *vp, *firstvp;
    struct ViewExtra *ve = NULL;
    struct ViewPortExtra *vpe = NULL;
    struct MonitorSpec *mspc;
    UBYTE first_vp_type = NULL;
    UWORD allocated;

    mspc = GBASE->natural_monitor;

    if (view->Modes & EXTEND_VSTRUCT)
    {
	if((ve = (struct ViewExtra *) gfx_GfxLookUp(view)) && ve->Monitor)
	{
	    mspc = ve->Monitor;
	}
    }

    D(kprintf("MRGCOP(%lx)\n",view);)

    /* construct link list of all copper sources and count instructions */
    copstuff.moreflags = 0;
    copstuff.first = 0;
    copstuff.maxins = 0;
    copstuff.DyOffset = view->DyOffset;

    if( mspc->ms_Flags & MSF_REQUEST_A2024 ) 
    {
	first_vp_type = 2;
    }

    GBASE->hedley_flags = 0;
    for( vp = view->ViewPort; vp ; vp = vp->Next)
    {
	D(kprintf("loop vp = 0x%lx\n", vp);)

	/* Check that we want to use this ViewPort.
	 * a) if VP_HIDE is set, then ignore this ViewPort.
	 * b) if vp->DspIns is NULL, then ignore this ViewPort. We need to
	 * check for DspIns being NULL, because that is the case where the ViewPort
	 * is off the bottom of the display, but there may still be Spr/Clr/UCopIns
	 * copperlists which we do not want to merge.
	 */
	if (((vp->Modes & VP_HIDE) == 0) && (vp->DspIns))
	{
	    D(kprintf("not hidden\n");)	    	
	 /* make sure no hybrid system */
	    switch( first_vp_type )
	    {
		case( 0 ):
		{
		    if (vp->ExtendedModes & VPF_A2024) 
		    {
			first_vp_type = 3; /* compatibility mode */
		    }
		    else 
		    {
			first_vp_type = 1;
		    }
		}   break;

		case( 1 ):
		{
		    if (vp->ExtendedModes & VPF_A2024) 
		    {
			/*vp->Modes &= VP_HIDE;*/
			vp->Modes = 0;		/* VP_HIDE *must* be clear to be here - spence Oct 24 1990 */
		    }
		}   break;

		case( 2 ):
		{
		    ULONG hedley_mode = new_mode(vp);

		    copstuff.DyOffset = 0x2c;
		    if ((vp->ExtendedModes & VPF_A2024) ||
		        ((hedley_mode == A2024TENHERTZ_KEY) ||
			 (hedley_mode == A2024FIFTEENHERTZ_KEY)))
		    {
			first_vp_type = 3;
			GBASE->hedley_flags = VPF_A2024;
			if (vp == (struct ViewPort *)GBASE->hedley[5])
			{
				/* show this is the same ViewPort as last time */
				GBASE->hedley_flags |= 0x80;
			}
			else
			{
				/* show this is a new viewport */
				GBASE->hedley_flags &= 0xff7f;
				GBASE->hedley[5] = (ULONG)vp;
			}

		    	if (hedley_mode == A2024TENHERTZ_KEY)
		    	{
		    		GBASE->hedley_flags |= VPF_TENHZ;		    		
		    	}

			if ((vp->RasInfo) && (GBASE->hedley[6] = (ULONG)(vp->RasInfo->BitMap)))
			{
			    	GBASE->hedley_tmp = vp->RasInfo->BitMap->Depth;
			}
			else
			{
				GBASE->hedley_tmp = 0;
			}
		    }
		    else
		    {
			first_vp_type = 1;
		    }
		}   break;

		case( 3 ):
		{
		    /*vp->Modes &= VP_HIDE;*/
		    vp->Modes = 0;
		}   break;

		default:
		{
		    /* error */
		}   break;
	    }

	    /*if ((vp->Modes & VP_HIDE) == 0)*/
	    {
		if ((vp->ColorMap) && (vp->ColorMap->Type) && (vp->ColorMap->cm_vpe))
			vp->ColorMap->cm_vpe->cop1ptr=vp->ColorMap->cm_vpe->cop2ptr=0;
				/* clear cache pointers! */
		append_clist(&copstuff,vp->DspIns,vp,FALSE);
		append_clist(&copstuff,vp->SprIns,vp,FALSE);
		append_clist(&copstuff,vp->ClrIns,vp,FALSE);
		for ( qwe = vp->UCopIns; qwe ; qwe = qwe->Next)
		{
		    append_clist(&copstuff,qwe->FirstCopList,vp,TRUE);
		}

	    }
	}
    }
	/* 200 micro seconds to get to here */
    
    if (copstuff.first == 0)
    {
	/* This happens if MrgCop() is called with no ViewPorts in the list,
	 * or all the ViewPorts are marked as hidden. This shouldn't happen,
	 * and if it does, it's an intuition bug.
	 */
	return(MCOP_NOP);
    }
    D(kprintf("total number of instructions = %lx\n",copstuff.maxins);)
    D(kprintf("moreflags = %lx\n",copstuff.moreflags);)

    copstuff.maxins += 10;	/* add 2 for auto ping/pong, + extras for extra
    				 * WAIT 255 instructions. This should be plenty
    				 * (probably more than enough, even for Motivator).
    				 */

    copstuff.beamx = copstuff.beamy = 0;

    copstuff.ibeamx = copstuff.ibeamy = 0;

    copstuff.flags = view->Modes;

	allocated = 0;
    /* must always have a LOF buffer */
    if ((loflist = view->LOFCprList) == 0)
    {
		D(kprintf("User set up no hardware LOFcopper lists\n");)
		loflist = (struct cprlist *)MrgCopAllocMem(sizeof(struct cprlist),MEMF_PUBLIC);
		if (!loflist)
			goto bad_mrgcop ;
		D(kprintf("allocated loflist %lx\n", loflist);)
		allocated |= 1 ;
		view ->LOFCprList = loflist;
		loflist->Next = 0;
		loflist->start = (short *)MrgCopAllocMem(copstuff.maxins<<2,MEMF_PUBLIC|MEMF_CHIP);
		if (!loflist->start)
			goto bad_mrgcop ;
		D(kprintf("allocated loflist->start %lx\n", loflist->start);)
		allocated |= 2 ;
		loflist->MaxCount = copstuff.maxins;
    }
    else    /* try to get exaxt amount */
    {
		/* keep old header */
		if (loflist->MaxCount < copstuff.maxins)
		{
	    	FreeMem(loflist->start,loflist->MaxCount<<2);
	    	loflist->start = (short *)MrgCopAllocMem(copstuff.maxins<<2,MEMF_PUBLIC|MEMF_CHIP);
	    	if (!loflist->start)
				goto bad_mrgcop ;
			D(kprintf("allocated loflist->start %lx\n", loflist->start);)
			allocated |= 2 ;
	    	loflist->MaxCount = copstuff.maxins;
		}
    }
    copstuff.cnt = loflist->MaxCount;
    copstuff.fcopptr = copstuff.copptr = loflist->start;       /* use this buffer */
    /*copstuff.fcopptr = loflist->start;*/	/* for hack stuff */

	shflist = 0;
    if (copstuff.flags & INTERLACE)
    {
		if ((shflist = view->SHFCprList) == 0)
		{
	    	D(kprintf("User set up no hardware SOFcopper lists\n");)
	    	shflist = (struct cprlist *)MrgCopAllocMem(sizeof(struct cprlist),MEMF_PUBLIC);
	    	if (!shflist)
				goto bad_mrgcop ;
			D(kprintf("allocated shflist %lx\n", shflist);)
			allocated |= 4 ;
	    	view->SHFCprList =  shflist;
	    	shflist->Next = 0;
	    	shflist->start = (short *)MrgCopAllocMem(copstuff.maxins<<2,MEMF_PUBLIC|MEMF_CHIP);
	    	if (!shflist->start)
				goto bad_mrgcop ;
			D(kprintf("allocated shflist->start %lx\n", shflist->start);)
			allocated |= 8 ;
	    	shflist->MaxCount = copstuff.maxins;
		}
		copstuff.icnt = shflist->MaxCount;
		copstuff.copIptr = shflist->start;
    }
    else
    {
	 copstuff.copIptr = 0;
	/* free off any old copper lists if possible */
	freecprlist(view->SHFCprList);
	view->SHFCprList = 0;
    }
	/* do we have some leftover copper lists? */
	freecprlist(loflist->Next);
	loflist->Next = 0;
	if (shflist)
	{
		freecprlist(shflist->Next);
		shflist->Next = 0;
	}
#ifdef QWE
	copstuff.ratioh = 1<<4;
	copstuff.ratioh = mspc->ratioh;
	copstuff.colorclocks = mspc->total_colorclocks;
	D(kprintf("ccs = 0x%lx\n", copstuff.colorclocks);)
#endif

	D(kprintf("Start main loop\n");)
    copstuff.topline = -1;
    while(copstuff.beamx < 255)
    {
		struct CopList *oldcl;
		ULONG newmode;

		oldcl = (struct CopList *) (&copstuff.first);

		for(cl = copstuff.first; cl ; cl = cl->_CopList)
		{
		    do
		    {
			D(kprintf("calling coppermover()\n");)
#ifdef NEWDOWNCODE
			c = (struct CopIns *)coppermover(&copstuff, cl->CopPtr);
#endif
			if (c->OpCode == 2)
			{
			    /* jump to next buffer */
			    cl = c->u3.nxtlist;     /* get next buffer */
			    if (oldcl == (struct CopList *) (&copstuff.first))
			    {
				copstuff.first = cl;
			    }
			    else
			    {
				struct CopList *qwe;
				for ( qwe = oldcl ; qwe ; qwe = qwe->Next)
				{
				    qwe->_CopList = cl;
				}
			    }
			}
		    }   while (c->OpCode == 2);
		    cl->CopPtr = c;
		    oldcl = cl;
		}
		/* all sources are now at waits */
		/* find first to start up */
		nxtbeam = (30000)<<10;
		/*nxtbeam = (264+copstuff.DyOffset)<<10;*/
		/*nxti dsnt need to be initialized */
    	D(kprintf("at wait sync copstuff.first = %lx\n",copstuff.first);)
		old_c = 0;
		nxtcl = 0;
		for(cl = copstuff.first; cl ; cl = cl->_CopList)
		{
		    int v_waitpos;
		    int qwe;
		    c = cl->CopPtr;
		    /*v_waitpos = c->VWAITPOS+MAX((cl->_ViewPort->DyOffset),0);*/
		    v_waitpos= c->VWAITPOS+MAKE_DY_POS(cl->_ViewPort->DyOffset);
		    D(kprintf("v_waitpos = 0x%lx\n", v_waitpos);)
		    if ((newmode = new_mode(cl->_ViewPort)) & LACE)
		    {
			v_waitpos >>= 1;
		    }
		    D(kprintf("newmode = 0x%lx\n",newmode);)
		    if (newmode & DOUBLESCAN)
		    {
			D(kprintf("ScanDbl\n");)
			v_waitpos <<= 1;
			v_waitpos += ((cl->Flags & HALF_LINE) ? 1 : 0);
		    }

		    D(kprintf("v_waitpos = 0x%lx, nxtbeam = 0x%lx\n", v_waitpos, nxtbeam);)
		    if ((qwe = (v_waitpos<<10)+c->HWAITPOS) <= nxtbeam)
		    {
			if((old_c) && (qwe == nxtbeam)) 
			{
			    /* old denise odd height hack */
			    if( old_c->OpCode & (CPR_NT_LOF|CPR_NT_SHT) )
			    {
				    continue; /* give old_c priority */
			    }
			}

			old_c = c;
			nxtcl = cl;
			nxtbeam = qwe;
		    }
		}

		/*check_first(nxtcl,&copstuff);*/
/*	if (!nxtcl)
	{
		D(kprintf("panic: nxtcl uninitialized\n");)
	}
*/
	D(kprintf("nxtcl = 0x%lx, prevcl = 0x%lx\n", nxtcl, prevcl);)
    	if (nxtcl->CopLStart == 0)
    	{
			D(kprintf("setting back link of %lx to %lx\n",nxtcl,copstuff.copptr);)

			nxtcl->CopLStart = copstuff.copptr;
			nxtcl->CopSStart = copstuff.copIptr;
    	}

	/* See if the Inter-ViewPort Gap straddles line 255,
	 * and if it does, show it for the next time through.
	 *
	 * ie, show that the *previous* ViewPort straddled, or this ViewPort
	 * straddles but the next time through is for this ViewPort's extra
	 * copper instructions (SprIns, ClrIns or UCopIns).
	 *
	 * NB - don't do this if this is the last time through.
	 * (I *really* hate this code!!)
	 */
	copstuff.moreflags &= ~(STRADDLES_256 | STRADDLES_512 | WHOLE_LINE);
	if (prevcl == NULL)
	{
		firstvp = nxtcl->_ViewPort;
	}
	{
		struct ViewPort *vp = (prevcl ? prevcl->_ViewPort : firstvp);
		if (((prevcl) && ((nxtcl != prevcl) && (nxtcl->_ViewPort != firstvp))) ||
		    ((nxtcl == vp->SprIns) || (nxtcl == vp->ClrIns) || (vp->UCopIns && IsAUCopIns(vp, nxtcl))))
		{
			if ((vp->ColorMap) && (vp->ColorMap->Type) && (vpe = vp->ColorMap->cm_vpe))
			{
				D(kprintf("MrgCop() vp = 0x%lx, vpe->Flags = 0x%lx\n", vp, vpe->Flags);)
				copstuff.moreflags |= (vpe->Flags & (VPXF_STRADDLES_256 | VPXF_STRADDLES_512));
			}
			copstuff.moreflags |= ((prevcl && (prevcl->Flags & EXACT_LINE)) ? WHOLE_LINE : 0);
		}
	}
	do_wait(&copstuff,nxtcl->CopPtr,nxtcl,mspc);

#ifdef NO_CLIP_VP
	/* should bump all copper lists that wait for the same position */
	nxtcl->CopPtr++;
#else
	/* if clipped skip additional moves -- bart */

#define clip_wait(cs,c,cl,mspc) \
(cl->_ViewPort->ColorMap \
&& (cl->_ViewPort->ColorMap->Flags & USER_COPPER_CLIP))? \
MIN(0,(MAKE_DY_POS(cl->_ViewPort->DyOffset+cl->_ViewPort->DHeight-1) - \
(c->VWAITPOS + MAKE_DY_POS(cl->_ViewPort->DyOffset)))) : 0

	if (clip_wait(&copstuff,nxtcl->CopPtr,nxtcl,mspc))
	{
	    struct CopIns *c = nxtcl->CopPtr;

	    if ((c->VWAITPOS == 10000) && (c->HWAITPOS == 255))
	    {
		goto CLIP_DONE;
	    }
	    else
	    {
		++nxtcl->CopPtr;

		if( !(c->OpCode & CPR_NT_SYS) ) goto CLIP_DONE;
		c = nxtcl->CopPtr;
	    }

	    do
	    {
		if((3&(c->OpCode)) == CPRNXTBUF)
		{
			nxtcl = c->NXTLIST; 
			if(!nxtcl) goto CLIP_DONE;
			c = nxtcl->CopPtr;
		}
		do
		{
		    if((3&(c->OpCode)) == COPPER_WAIT)
		    {
			if ((c->VWAITPOS == 10000) && (c->HWAITPOS == 255))
			{
			    goto CLIP_DONE;
			}
			else
			{
			    if( !(c->OpCode & CPR_NT_SYS) ) goto CLIP_DONE;
			    c = ++nxtcl->CopPtr;
			}
		    }

		    while ((3&(c->OpCode)) == COPPER_MOVE) 
		    {
			if( !(c->OpCode & CPR_NT_SYS) ) goto CLIP_DONE;
			c = ++nxtcl->CopPtr;
		    }

		}   while ((3&(c->OpCode)) == COPPER_WAIT);

	    }   while ((3&(c->OpCode)) == CPRNXTBUF);

CLIP_DONE: ;

	}
	else
	{
	    /* should bump all copper lists that wait for same position */
	    nxtcl->CopPtr++;
	}
#endif
	prevcl = nxtcl;
    }

    if ((GBASE->ChipRevBits0 & GFXF_AA_LISA) && ve)
    {
    	ULONG t = copstuff.topline;
	t = MIN(t, TOPLINE);
	ve->TopLine = (UWORD)t;
    }

	D(kprintf("Special interlace test\n");)
	/* the last instruction should be a wait 255 now, we need to */
	/* move this down and add reloads for cop2ptr */
	/* set up for noninterlaced screen */
	if (view->Modes & LACE)
	{
		/* the following code required incase interrupts disappear */
		register UWORD *copptr,*copIptr;
		struct Custom *io = &custom;

		copIptr = copstuff.copIptr;
		copptr = copstuff.copptr;

		/* move wait instruction down */
		copIptr[2] = copptr[2] = copptr[-2];
		copIptr[3] = copptr[3] = copptr[-1];
		/* now set up move.l xxx,cop2ptr */
		copIptr[-2] = copptr[-2] = 0x1fe & (ULONG)(&io->cop2lc);
		copIptr[0] = copptr[0] = 0x1fe & ( (ULONG)(&io->cop2lc) + 2);

		/* set short frame to jump to long frame */
		copIptr[-1] = copptr[-1] = (int)(loflist->start)>>16;
		copIptr[1] = copptr[1] = (UWORD)loflist->start;

		/* set long frame to go to short frame */
		copptr[-1] = (ULONG)(shflist->start)>>16;
		copptr[1] = (UWORD)shflist->start;
	}
	D(kprintf("exiting mrgcop\n");)
	return(MCOP_OK);

/* Low memory condition - free the coplists we have allocated,
   and put NULL pointers in the lists. */

bad_mrgcop:
	if (allocated & 8)
	{
	D(kprintf("Freeing shflist->start %lx\n", shflist->start) ;)
		FreeMem(shflist->start, (copstuff.maxins<<2)) ;
		view->SHFCprList->start = NULL ;
	}
	if (allocated & 4)
	{
	D(kprintf("Freeing shflist %lx\n", shflist) ;)
		FreeMem(shflist, sizeof(struct cprlist)) ;
		view->SHFCprList = NULL ;
	}
	if (allocated & 2)
	{
	D(kprintf("Freeing loflist->start %lx\n", loflist->start) ;)
		FreeMem(loflist->start, (copstuff.maxins<<2)) ;
		view->LOFCprList->start = NULL ;
	}
	if (allocated & 1)
	{
	D(kprintf("Freeing loflist %lx\n", loflist) ;)
		FreeMem(loflist, sizeof(struct cprlist)) ;
		view->LOFCprList = NULL ;
	}

	D(kprintf("Leaving bad MrgCop\n") ;)

	return(MCOP_NO_MEM);
}

#endif
