/*
 * DBtechpaint.c --
 *
 * Management of composition rules and the paint/erase tables.
 *
 *     ********************************************************************* 
 *     * Copyright (C) 1985, 1990 Regents of the University of California. * 
 *     * Permission to use, copy, modify, and distribute this              * 
 *     * software and its documentation for any purpose and without        * 
 *     * fee is hereby granted, provided that the above copyright          * 
 *     * notice appear in all copies.  The University of California        * 
 *     * makes no representations about the suitability of this            * 
 *     * software for any purpose.  It is provided "as is" without         * 
 *     * express or implied warranty.  Export of this software outside     * 
 *     * of the United States of America may require an export license.    * 
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] = "$Header: /ufs/repository/magic/database/DBtpaint.c,v 1.6 2006/02/08 03:46:09 fang Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <ctype.h>
#include "misc/magic.h"
#include "utils/geometry.h"
#include "utils/utils.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "database/databaseInt.h"
#include "tech/tech.h"
#include "textio/textio.h"

    /* Painting and erasing tables */
PaintResultType DBPaintResultTbl[NP][NT][NT];
PaintResultType DBEraseResultTbl[NP][NT][NT];
PaintResultType DBWriteResultTbl[NT][NT];
unsigned int DBTypePaintPlanesTbl[NT];
unsigned int DBTypeErasePlanesTbl[NT];

    /* Components */
TileTypeBitMask DBComponentTbl[NT];

/* ----------------- Data local to tech file processing --------------- */

/*
 * Tables telling which rules are default, and which have come
 * from user-specified rules.  The bit is CLEAR if the type is
 * a default type.
 */
TileTypeBitMask dbNotDefaultEraseTbl[NT];
TileTypeBitMask dbNotDefaultPaintTbl[NT];

/* --------------------- Data local to this file ---------------------- */

int dbNumSavedRules = 0;
Rule dbSavedRules[NT];

static void
	dbTechBitTypeInit(register TileType *bitToType, 
		int n, int pNum, bool composeFlag);

static bool dbIsPrimary(int n);

static bool
	dbTechSaveCompose(int ruleType, TileType t, int argc, char *argv[]);

static bool
	dbTechCheckImages(TileType t, register TileType r, TileType s);

/*
 * ----------------------------------------------------------------------------
 *
 * DBTechInitCompose --
 *
 * Initialize the painting and erasing rules prior to processing
 * the "compose" section.  The rules for builtin types are computed
 * here, as well as the default rules for all other types.  This
 * procedure must be called after the "types" and "contacts" sections
 * have been read, since we need to know about all existing tile types.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies the paint and erase tables.
 *
 * ----------------------------------------------------------------------------
 */

/* Void */
void
DBTechInitCompose()
{
    register TileType s, t, r;
    int pNum, ps;
    /* Default painting rules for error types */
    static TileType errorBitToType[] =
    {
	TT_SPACE,	/* 0 */		TT_ERROR_P,	/* 1 */
	TT_ERROR_S,	/* 2 */		TT_ERROR_PS,	/* 3 */
    };

    /* Painting and erasing are no-ops for undefined tile types */
    for (pNum = 0; pNum < PL_MAXTYPES; pNum++)
    {
	for (s = 0; s < TT_MAXTYPES; s++)
	{
	    for (t = 0; t < TT_MAXTYPES; t++)
	    {
		/* Paint and erase are no-ops */
		dbSetEraseEntry(s, t, pNum, s);
		dbSetPaintEntry(s, t, pNum, s);

		/* Write overwrites existing contents */
		dbSetWriteEntry(s, t, t);
	    }
	}
    }

    /* All painting and erasing rules are default initially */
    for (s = 0; s < DBNumTypes; s++)
    {
	dbNotDefaultEraseTbl[s] = DBZeroTypeBits;
	dbNotDefaultPaintTbl[s] = DBZeroTypeBits;
    }

    /*
     *	For each type t:
     *	    erase(t, t, plane(t)) -> SPACE
     *
     *	For each type s, t:
     *	    paint(s, t, plane(t)) -> t
     *	    paint(s, t, ~plane(t)) -> s
     */
    for (s = 0; s < DBNumTypes; s++)
    {
	if ((ps = DBPlane(s)) > 0)
	{
	    for (t = 0; t < DBNumUserLayers; t++)
	    {
		if (DBPlane(t) > 0)
		{
		    r = (ps == DBPlane(t)) ? t : s;
		    dbSetEraseEntry(s, t, ps, s);
		    dbSetPaintEntry(s, t, ps, r);
		}
	    }

	    /* Everything can be erased to space on its home plane */
	    dbSetEraseEntry(s, s, ps, TT_SPACE);

	    /* Everything paints over space on its home plane */
	    dbSetPaintEntry(TT_SPACE, s, ps, s);
	}
    }

    /*
     * Special handling for check tile and error tile combinations.
     */
#define	PCHK	PL_DRC_CHECK
#define	PERR	PL_DRC_ERROR
#define	tblsize(t)	( (sizeof (t)) / (sizeof (t[0])) )
    dbTechBitTypeInit(errorBitToType, tblsize(errorBitToType), PERR, FALSE);
#undef	tblsize

    /*
     * Paint results are funny for check plane because
     * CHECKPAINT+CHECKSUBCELL = CHECKPAINT
     */
    dbSetPaintEntry(TT_SPACE, TT_CHECKPAINT, PCHK, TT_CHECKPAINT);
    dbSetPaintEntry(TT_SPACE, TT_CHECKSUBCELL, PCHK, TT_CHECKSUBCELL);
    dbSetPaintEntry(TT_CHECKPAINT, TT_CHECKSUBCELL, PCHK, TT_CHECKPAINT);
    dbSetPaintEntry(TT_CHECKSUBCELL, TT_CHECKPAINT, PCHK, TT_CHECKPAINT);
#undef	PCHK
#undef	PERR
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbTechBitTypeInit --
 *
 * Handle initialization of the paint and erase result tables for a
 * set of ln2(n) primary types with n distinct mutual overlap types.
 * The table bitToType points to a table containing n TileTypes
 * (the overlap types) with the property that
 *
 *	bitToType[i] and bitToType[j] combine to yield bitToType[i | j]
 *
 * Also (unless composeFlag is set) erasing bitToType[j] from bitToType[i] 
 * gives bitToType[i & (~j)],
 * i.e., it clears all of the j-type material out of the i-type material.
 * The bitToType[k] for which k's binary representation has only a single
 * bit set in it are the "primary" types.
 *
 * If composeFlag is set, the above is modified slightly to be analagous
 * to compose rules, specifically, erase rules for nonprimary types are
 * the default rules, i.e. they only erase precisely themselves.  This
 * makes ":erase *-primary" work in the expected way.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See above.
 *
 * ----------------------------------------------------------------------------
 */

void
dbTechBitTypeInit(
    register TileType *bitToType, 
    int n, int pNum,
    bool composeFlag)
{
    register int i, j;
    TileType have, type;

    for (i = 0; i < n; i++)
    {
	have = bitToType[i];
	for (j = 0; j < n; j++)
	{
	    type = bitToType[j];
	    dbSetPaintEntry(have, type, pNum, bitToType[i | j]);
	    if(!composeFlag || dbIsPrimary(j))
	    {
	        dbSetEraseEntry(have, type, pNum, bitToType[i & (~j)]);
	    }
	}
    }
}

/* Returns nonzero if exactly one bit set */ 
bool
dbIsPrimary(int n)
{
    int bitCount;

    for(bitCount=0; n>0; n=n>>1)
    {
        if(n&1)
        {
	    bitCount++;
        }
    }

    return (bitCount==1);
}
      

/*
 * ----------------------------------------------------------------------------
 *
 * DBTechAddCompose --
 *
 * Process a single compose/erase rule.  If the type being described is
 * a contact, save the rule and defer processing it until the end of this
 * section, because we need to know the behavior of all non-contact types
 * that might be residues before processing a contact composition rule.
 * Rules for non-contact types are processed here.
 *
 * Results:
 *	TRUE if successful, FALSE on error.
 *
 * Side effects:
 *	Modifies the paint/erase tables if the type being described
 *	is not a contact; otherwise, appends a rule to the list of
 *	contact erase/compose rules for later processing.  Marks the
 *	paint/erase table entries affected to show that they contain
 *	user-specified rules instead of the default ones, so we don't
 *	override them later.
 *
 * ----------------------------------------------------------------------------
 */

    /*ARGSUSED*/
bool
DBTechAddCompose(sectionName, argc, argv)
    char *sectionName;
    int argc;
    char *argv[];
{
    register TileType type, r, s;
    int pNum, ruleType, i;
    static char *ruleNames[] =
	{ "compose", "decompose", "paint", "erase", 0 };
    static int ruleTypes[] =
	{ RULE_COMPOSE, RULE_DECOMPOSE, RULE_PAINT, RULE_ERASE };

    if (argc < 4)
    {
	TechError("Line must contain at least ruletype, result + pair\n");
	return FALSE;
    }

    /* Look up and skip over type of rule */
    i = Lookup(*argv, ruleNames);
    if (i < 0)
    {
	TechError("%s rule type %s.  Must be one of:\n\t",
		i == -1 ? "Ambiguous" : "Unknown", *argv);
	for (i = 0; ruleNames[i]; i++)
	    TxError("\"%s\" ", ruleNames[i]);
	TxError("\n");
	return FALSE;
    }
    ruleType = ruleTypes[i];
    argv++, argc--;

    /* Paint or erase rules are processed specially */
    switch (ruleType)
    {
	case RULE_PAINT:
	    return (dbTechAddPaint(sectionName, argc, argv));
	case RULE_ERASE:
	    return (dbTechAddErase(sectionName, argc, argv));
    }

    /* Compose or decompose rule: find result type and then skip over it */
    if ((type = DBTechNoisyNameType(*argv)) < 0)
	return FALSE;
    argv++, argc--;
    if (argc & 01)
    {
	TechError("Types on RHS of rule must be in pairs\n");
	return FALSE;
    }

    /* Compose/decompose rules for contacts are saved away */
    if (IsContact(type))
	return dbTechSaveCompose(ruleType, type, argc, argv);

    /* Rules for non-contacts are processed here */
    for ( ; argc > 0; argc -= 2, argv += 2)
    {
	if ((r = DBTechNoisyNameType(argv[0])) < 0
		|| (s = DBTechNoisyNameType(argv[1])) < 0)
	    return FALSE;

	if (IsContact(r) || IsContact(s))
	{
	    TechError("Can't have contact layers on RHS of non-contact rule\n");
	    return FALSE;
	}

	pNum = DBPlane(r);
	switch (ruleType)
	{
	    case RULE_COMPOSE:
		dbSetPaintEntry(r, s, pNum, type);
		dbSetPaintEntry(s, r, pNum, type);
		TTMaskSetType(&dbNotDefaultPaintTbl[r], s);
		TTMaskSetType(&dbNotDefaultPaintTbl[s], r);
		/* Fall through to */
	    case RULE_DECOMPOSE:
		dbSetPaintEntry(type, r, pNum, type);
		dbSetPaintEntry(type, s, pNum, type);
		dbSetEraseEntry(type, r, pNum, s);
		dbSetEraseEntry(type, s, pNum, r);
		TTMaskSetType(&dbNotDefaultPaintTbl[type], r);
		TTMaskSetType(&dbNotDefaultPaintTbl[type], s);
		TTMaskSetType(&dbNotDefaultEraseTbl[type], r);
		TTMaskSetType(&dbNotDefaultEraseTbl[type], s);
		break;
	}
    }

    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbTechSaveCompose --
 *
 * Save a compose rule for a contact 't' in the table dbSavedRules.
 * Check to make sure the rule is legal.
 *
 * Results:
 *	Returns TRUE if successful, FALSE on error.
 *
 * Side effects:
 *	Updates dbSavedRules[] and increments dbNumSavedRules.
 *
 * ----------------------------------------------------------------------------
 */

bool
dbTechSaveCompose(int ruleType, TileType t, int argc, char *argv[])
{
    register TileType r, s;
    register Rule *rp;

    rp = &dbSavedRules[dbNumSavedRules++];
    rp->r_ruleType = ruleType;
    rp->r_result = t;
    rp->r_npairs = 0;
    for ( ; argc > 0; argc -= 2, argv += 2)
    {
	r = DBTechNoisyNameType(argv[0]);
	s = DBTechNoisyNameType(argv[1]);
	if (r < 0 || s < 0)
	    return FALSE;

	/* At most one of r and s may be a contact */
	if (IsContact(r) && IsContact(s))
	{
	    TechError("Only one type in each pair may be a contact\n");
	    return FALSE;
	}

	/*
	 * The planes comprising 't' must be a superset of the
	 * planes comprising 'r' and the planes comprising 's'.
	 */
	if (((PlaneMask(r) | PlaneMask(s)) & ~PlaneMask(t)) != 0)
	{
	    TechError("Component planes are a superset of result planes\n");
	    return FALSE;
	}

	if (ruleType == RULE_COMPOSE)
	{
	    /* Types r and s can't appear on planes outside of t's */
	    if ((PlaneMask(r) | PlaneMask(s)) != PlaneMask(t))
	    {
		TechError("Union of pair planes must = result planes\n");
		return FALSE;
	    }

	    if (!dbTechCheckImages(t, r, s) || !dbTechCheckImages(t, s, r))
		return FALSE;
	}

	rp->r_pairs[rp->r_npairs].rp_a = r;
	rp->r_pairs[rp->r_npairs].rp_b = s;
	rp->r_npairs++;
    }

    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbTechCheckImages --
 *
 * When processing a compose rule for 't' with RHS components
 * 'r' and 's', check to be sure that the images of 't' on
 * those planes present in 'r' but not in 's' are identical to
 * the images of 'r' on those planes.  This is necessary in order
 * that the result on these planes not depend on types present on
 * other planes.
 *
 * Results:
 *	Returns TRUE if successful, FALSE on error.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

bool
dbTechCheckImages(
    TileType t,			/* Type that is composed */
    register TileType r,	/* First constituent */
    TileType s)			/* Second constituent */
{
    register int pNum, pMask;

    if ((pMask = (PlaneMask(r) & ~PlaneMask(s))))
    {
	for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++)
	    if (PlaneMaskHasPlane(pMask, pNum)
		    && dbImageOnPlane(t, pNum) != dbImageOnPlane(r, pNum))
	    {
		TechError("\
Result image on plane %s must be the same as image of %s on plane %s\n",
			DBPlaneLongName(pNum),
			DBTypeLongName(r),
			DBPlaneLongName(pNum));
		return FALSE;
	    }
    }

    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbTechAddPaint --
 *
 * Add a new entry to the paint table.
 * The semantics is that painting a tile of type1 with paint type2
 * yields a tile of typeres.
 *
 * Results:
 *	Returns TRUE if successful, FALSE on error.
 *
 * Side effects:
 *	Updates the database technology variables.
 *
 * ----------------------------------------------------------------------------
 */

    /*ARGSUSED*/
bool
dbTechAddPaint(sectionName, argc, argv)
    char *sectionName;
    int argc;
    char *argv[];
{
    int pNum;
    TileType t1, t2, tres;

    if (argc < 3)
    {
	TechError("Line must contain at least 3 types\n");
	return FALSE;
    }

    if ((t1 = DBTechNoisyNameType(argv[0])) < 0) return FALSE;
    if ((t2 = DBTechNoisyNameType(argv[1])) < 0) return FALSE;
    if ((tres = DBTechNoisyNameType(argv[2])) < 0) return FALSE;
    if (argc == 3)
    {
	if (t1 == TT_SPACE)
	{
	    TechError("<%s, %s, %s>:\n\
Must specify plane in paint table for painting space\n",
			argv[0], argv[1], argv[2]);
	    return FALSE;
	}
	pNum = DBPlane(t1);
    }
    else if ((pNum = DBTechNoisyNamePlane(argv[3])) < 0) return FALSE;

    if (tres != TT_SPACE && DBPlane(tres) != pNum)
    {
	TechError("<%s, %s, %s, %s>:\n\
Warning: type %s (%d) belongs on plane %s (%d), not %s (%d)\n",
		argv[0], argv[1], argv[2], argv[3],
		argv[2], tres,
		DBPlaneLongName(DBPlane(tres)), DBPlane(tres),
		argv[3], pNum);
    }

    dbSetPaintEntry(t1, t2, pNum, tres);
    TTMaskSetType(&dbNotDefaultPaintTbl[t1], t2);
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbTechAddErase --
 *
 * Add a new entry to the erase table.
 * The semantics is that erasing paint of type2 from a tile of type1
 * yields a tile of typeres.
 *
 * Results:
 *	Returns TRUE if successful, FALSE on error.
 *
 * Side effects:
 *	Updates the database technology variables.
 *
 * ----------------------------------------------------------------------------
 */

    /*ARGSUSED*/
bool
dbTechAddErase(sectionName, argc, argv)
    char *sectionName;
    int argc;
    char *argv[];
{
    TileType t1, t2, tres;
    int pNum;

    if (argc < 3)
    {
	TechError("Line must contain at least 3 types\n");
	return FALSE;
    }

    if ((t1 = DBTechNoisyNameType(argv[0])) < 0) return FALSE;
    if ((t2 = DBTechNoisyNameType(argv[1])) < 0) return FALSE;
    if ((tres = DBTechNoisyNameType(argv[2])) < 0) return FALSE;

    if (argc == 3)
    {
	if (t1 == TT_SPACE)
	{
	    TechError("<%s, %s, %s>:\n\
Must specify plane in paint table for erasing space\n",
			argv[0], argv[1], argv[2]);
	    return FALSE;
	}
	pNum = DBPlane(t1);
    }
    else if ((pNum = DBTechNoisyNamePlane(argv[3])) < 0) return FALSE;

    if (tres != TT_SPACE && DBPlane(tres) != pNum)
    {
	TechError("<%s, %s, %s, %s>:\n\
Warning: type %s (%d) belongs on plane %s (%d), not %s (%d)\n",
		argv[0], argv[1], argv[2], argv[3],
		argv[2], tres,
		DBPlaneLongName(DBPlane(tres)), DBPlane(tres),
		argv[3], pNum);
    }

    dbSetEraseEntry(t1, t2, pNum, tres);
    TTMaskSetType(&dbNotDefaultPaintTbl[t1], t2);
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbTechCheckPaint --
 *
 * DEBUGGING.
 * Check painting and erasing rules to make sure that the result
 * type is legal for the plane being affected.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Prints stuff in the event of an error.
 *
 * ----------------------------------------------------------------------------
 */

/* Void */
void
dbTechCheckPaint(where)
    char *where;	/* If non-null, print this as header */
{
    TileType have, t, result;
    bool printedHeader = FALSE;

    for (have = TT_TECHDEPBASE; have < DBNumTypes; have++)
    {
	for (t = TT_TECHDEPBASE; t < DBNumTypes; t++)
	{
	    result = DBStdPaintEntry(have, t, DBPlane(have));
	    if (result != TT_SPACE && DBPlane(result) != DBPlane(have))
	    {
		if (!printedHeader && where)
		    TxPrintf("\n%s:\n", where), printedHeader = TRUE;
		TxPrintf("%s + %s -> %s\n",
			DBTypeShortName(have), DBTypeShortName(t),
			DBTypeShortName(result));
	    }
	    result = DBStdEraseEntry(have, t, DBPlane(have));
	    if (result != TT_SPACE && DBPlane(result) != DBPlane(have))
	    {
		if (!printedHeader && where)
		    TxPrintf("\n%s:\n", where), printedHeader = TRUE;
		TxPrintf("%s - %s -> %s\n",
			DBTypeShortName(have), DBTypeShortName(t),
			DBTypeShortName(result));
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbTechPrintPaint --
 *
 * DEBUGGING.
 * Print painting and erasing rules.  If contactsOnly is TRUe, only
 * print those rules involving pairs of contact types.  The argument
 * "where" is printed as a header if it is non-NULL.  If doPaint is
 * TRUE, we print the paint rules, else we print the erase rules.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Prints stuff.
 *
 * ----------------------------------------------------------------------------
 */

/* Void */
void
dbTechPrintPaint(where, doPaint, contactsOnly)
    char *where;	/* If non-null, print this as header */
    bool doPaint;	/* TRUE -> print paint tables, FALSE -> print erase */
    bool contactsOnly;
{
    TileType have, paint, erase, result;

    if (where)
	TxPrintf("\f\n%s:\n\n", where);

    if (doPaint)
    {
	TxPrintf("PAINTING RULES:\n");
	for (have = TT_TECHDEPBASE; have < DBNumTypes; have++)
	{
	    if (contactsOnly && !IsContact(have)) continue;
	    for (paint = TT_TECHDEPBASE; paint < DBNumUserLayers; paint++)
	    {
		if (contactsOnly && !IsContact(paint)) continue;
		result = DBStdPaintEntry(have, paint, DBPlane(have));
		if (result != have)
		{
		    TxPrintf("%s + %s -> %s\n",
			    DBTypeShortName(have),
			    DBTypeShortName(paint),
			    DBTypeShortName(result));
		}
	    }
	}
    }
    else
    {
	TxPrintf("ERASING RULES:\n");
	for (have = TT_TECHDEPBASE; have < DBNumTypes; have++)
	{
	    if (contactsOnly && !IsContact(have)) continue;
	    for (erase = TT_TECHDEPBASE; erase < DBNumUserLayers; erase++)
	    {
		if (contactsOnly && !IsContact(erase)) continue;
		result = DBStdEraseEntry(have, erase, DBPlane(have));
		if (result != have)
		{
		    TxPrintf("%s - %s -> %s\n",
			    DBTypeShortName(have),
			    DBTypeShortName(erase),
			    DBTypeShortName(result));
		}
	    }
	}
    }
}
