/*
 * c3drectangle.c --
 *
 *     This file implements the C3dItemType functions for the rectangle item.
 */

#include "c3d.h"
#include <assert.h>
#include <stdlib.h>

typedef struct TwoDItem TwoDItem;
struct TwoDItem {
    C3dItem common;
    C3dColor color;
    double width;
    double layer;
    int nFace;
    int *anVertex;
    C3dOverlayLink overlay;
};

/*
 * Instead of a C3dItemType structure, the C3d2dLine() and C3d2dPolygon()
 * functions return a pointer to an instance of the following structure.
 * Code outside of this file casts the pointer and treats the structure as
 * a C3dItemType instance. But code in this file can use the ITEM_SUBTYPE()
 * macro to determine if the item was created as a 2dline or 2dpolygon.
 */
typedef struct TwoDItemType TwoDItemType;
struct TwoDItemType {
    C3dItemType common;
    int subtype;          /* Either ITEM_LINE or ITEM_POLYGON */
};
#define ITEM_LINE    1
#define ITEM_POLYGON 2

#define ITEM_SUBTYPE(pItem) (((TwoDItemType *)((pItem)->pType)->subtype)

/*
 *---------------------------------------------------------------------------
 *
 * twoDOverlay --
 *
 *     Return a pointer to the C3dOverlayLink structure allocated as a part
 *     of each TwoDItem struct.
 *
 * Results:
 *     Pointer to C3dOverlayLink structure used to link the item into the
 *     overlay-list.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static C3dOverlayLink *
twoDOverlay(C3dItem *pItem)
{
    return &((TwoDItem *)pItem)->overlay;
}

/*
 *---------------------------------------------------------------------------
 *
 * rectangleTable --
 *
 *     Return the Tk_OptionTable used to implement [itemcget] and
 *     [itemconfigure] for 2dline and 2dpolygon items.
 *
 * Results:
 *     Tk_OptionTable token.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static Tk_OptionTable
rectangleTable(Tcl_Interp *interp)
{
    static Tk_OptionSpec rectangle_option_array[] = {
        TAGS_OPTION,
        HIDDEN_OPTION,
        C3DOPTION(TwoDItem, COLOR, color, "white", 0),
        C3DOPTION(TwoDItem, DOUBLE, width, "1.0", 0),
        C3DOPTION(TwoDItem, DOUBLE, layer, "1.0", 0),
        {TK_OPTION_END, 0, 0, 0, 0, 0, 0, 0, 0}
    };
    Tk_OptionTable rectangle_options;

    rectangle_options = C3dCreateOptionTable(interp,rectangle_option_array);
    assert(rectangle_options);

    return rectangle_options;
}

/*
 *---------------------------------------------------------------------------
 *
 * twoDDrawCallback --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
twoDDrawCallback(C3dItem *pItem, ClientData clientData)
{
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    TwoDItem *pRectangle = (TwoDItem *)pItem;
    int subtype = ((TwoDItemType *)pItem->pType)->subtype;
    int i;
    int v = 0;

    if (!pCanvas->options.saveunder || pCanvas->eSelectMode) {
        int primitive = 0;
        C3dTransform *pOrtho;
        GLdouble mOrtho[16];

        switch (subtype) {
            case ITEM_POLYGON:
                primitive = GL_POLYGON;
                break;
            case ITEM_LINE:
                glLineWidth(pRectangle->width);
                primitive = GL_LINE_STRIP;
                break;
            default:
                assert(!"Can't happen");
        }

        /* Set the projection matrix to the standard widget 'overlay' matrix. */
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glLoadIdentity();
        pOrtho = C3dTransformOverview(pCanvas);
        C3dTransformToMatrix(pOrtho, mOrtho);
        glMultMatrixd(mOrtho);
        C3dTransformDelete(pOrtho);

        glMatrixMode(GL_MODELVIEW);
        glDisable(GL_LIGHTING);

        glColor4fv(pRectangle->color.aChannel);
        for (i = 0; i < pRectangle->nFace; i++) {
            int final;
            glBegin(primitive);
            for (final = v + pRectangle->anVertex[i]; v < final; v++) {
                 glVertex2f(pItem->aVertex[v].x, pItem->aVertex[v].y);
            }
            glEnd();
        }

        glMatrixMode(GL_PROJECTION);
        glPopMatrix();
        glMatrixMode(GL_MODELVIEW);
    } else {
        GC gc;
        XGCValues gc_values;
        XColor color;
        XColor *pColor;
        Display *pDisplay = Tk_Display(pCanvas->tkwin);
        C3dVertex *pPrev = 0;
        int nStripe = (pRectangle->width + 0.1);
        int nOff = (nStripe / 2);

        XPoint *aPoints = 0;


        color.red = 65535 * pRectangle->color.aChannel[0];
        color.green = 65535 * pRectangle->color.aChannel[1];
        color.blue = 65535 * pRectangle->color.aChannel[2];

        pColor = C3dGetColorByValue(pCanvas->tkwin, &color);
        assert(pColor);

        gc_values.foreground = pColor->pixel;
        gc = C3dGetGC(pCanvas->tkwin, GCForeground, &gc_values);

        for (i = 0; i < pRectangle->nFace; i++) {
            int final = v + pRectangle->anVertex[i];
            int nPoint = final - v;
            aPoints = (XPoint*)C3dRealloc((char*)aPoints,sizeof(XPoint)*nPoint);
            for (/* void */; v < final; v++) {
                C3dVertex *pThis = &pItem->aVertex[v];
                if (subtype == ITEM_LINE) {
                    if (pPrev) {
                        int j = 0;
                        int yMul = 0;
                        if (abs(pThis->x-pPrev->x) > abs(pThis->y-pPrev->y)) {
                            yMul = 1;
                        }
                        for (j = 0; j < nStripe; j++) {
                            XDrawLine(
                                pDisplay, pCanvas->pixmap, gc,
                                pPrev->x+(yMul?0:j-nOff),
                                pPrev->y+(yMul?j-nOff:0),
                                pThis->x+(yMul?0:j-nOff),
                                pThis->y+(yMul?j-nOff:0)
                            );
                        }
                    }
                    pPrev = ((v + 1)==final) ? 0 : pThis;
                } else {
                    aPoints[final - v - 1].x = pThis->x;
                    aPoints[final - v - 1].y = pThis->y;
                }
            }
            if (subtype == ITEM_POLYGON) {
		XFillPolygon(pDisplay, pCanvas->pixmap, gc, aPoints,
                     nPoint, Nonconvex, CoordModeOrigin);
            }
        }

        C3dFree((char *)aPoints);
        C3dFreeColor(pColor);
        C3dFreeGC(pDisplay, gc);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * rectangleDraw --
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
twoDDraw(C3dWidget *pCanvas, C3dItem *pItem)
{
    TwoDItem *p = (TwoDItem *)pItem;
    C3dItemDrawCallback(pCanvas, twoDDrawCallback, pItem, pCanvas, p->layer);
}

/*
 *---------------------------------------------------------------------------
 *
 * twoDMultiCreate --
 *
 *     This function creates either a new 2dline or 2dpolygon item,
 *     depending on the value of the 'item' argument.
 *
 * Results:
 *     A new C3dItem structure, containing either a 2dline or 2dpolygon
 *     item.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static C3dItem *
twoDMultiCreate(
    C3dWidget *pCanvas,     /* Pointer to widget struct */
    int nFace,              /* Number of entries in apCoords */
    Tcl_Obj **apCoords,     /* List of 2dCoordList lists. */
    int item                /* Either ITEM_LINE or ITEM_POLYGON */
) {
    int i;
    int v = 0;
    int nCoord = 0;
    TwoDItem *pRectangle;
    int nBytes;
    Tcl_Interp *interp = pCanvas->interp;

    for (i = 0; i < nFace; i++) {
        int n;
        if (TCL_OK != Tcl_ListObjLength(interp, apCoords[i], &n)) {
            return 0;
        }
        if (n % 2 || (item==ITEM_LINE && n<4) || (item==ITEM_POLYGON && n<6)) {
            Tcl_ResetResult(interp);
            Tcl_AppendResult(interp, "Invalid 2d-coordlist: ", 0);
            Tcl_AppendResult(interp, Tcl_GetString(apCoords[i]), 0);
            return 0;
        }
        nCoord += n;
    }

    nBytes = sizeof(TwoDItem) +
             sizeof(C3dVertex) * nCoord / 2 +
             sizeof(int) * nFace;
    pRectangle = (TwoDItem *)C3dAlloc(nBytes);
    memset(pRectangle, 0, nBytes);
    pRectangle->common.aVertex = (C3dVertex *)&pRectangle[1];
    pRectangle->common.nVertex = nCoord / 2;
    pRectangle->anVertex = (int *)&pRectangle->common.aVertex[nCoord / 2];

    for (i = 0; i < nFace; i++) {
        int j;
        int n = 0;
        Tcl_Obj **a;
        Tcl_ListObjGetElements(interp, apCoords[i], &n, &a);
        assert(n > 0 && 0 == (n % 2));
        for (j = 0; j < n; j += 2) {
            double x, y;
            if (
                TCL_OK != Tcl_GetDoubleFromObj(interp, a[j], &x) ||
                TCL_OK != Tcl_GetDoubleFromObj(interp, a[j+1], &y)
            ) {
                return 0;
            }
            pRectangle->common.aVertex[v].x = x;
            pRectangle->common.aVertex[v].y = y;
            v++;
        }
        pRectangle->anVertex[i] = n / 2;
    }
    assert(v == nCoord / 2);
    pRectangle->nFace = nFace;

    return (C3dItem *)pRectangle;
}

/*
 *---------------------------------------------------------------------------
 *
 * lineCreate --
 *
 *     Create a new 2dline item. The actual work is done by
 *     twoDMultiCreate().
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static C3dItem *
lineCreate(
    C3dWidget *pCanvas,
    int nFace,
    Tcl_Obj **apCoords
) {
    return twoDMultiCreate(pCanvas, nFace, apCoords, ITEM_LINE);
}

/*
 *---------------------------------------------------------------------------
 *
 * polygonCreate --
 *
 *     Create a new 2dpolygon item. The actual work is done by
 *     twoDMultiCreate().
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static C3dItem *
polygonCreate(
    C3dWidget *pCanvas,
    int nFace,
    Tcl_Obj **apCoords
) {
    return twoDMultiCreate(pCanvas, nFace, apCoords, ITEM_POLYGON);
}

/*
 *---------------------------------------------------------------------------
 *
 * twoDCoords --
 *
 *     Implementation of the [pathName coords] command for 2dline and
 *     2dpolygon items. After this function returns, Tcl object pRet has
 *     been populated with the value that should be returned for
 *     [pathName coords].
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
twoDCoords(C3dItem *pItem, Tcl_Obj *pRet)
{
    TwoDItem *pRectangle = (TwoDItem *)pItem;
    int nBytes;
    Tcl_Obj **apFace;
    Tcl_Obj **apVertex;
    int v = 0;
    int i;

    nBytes = sizeof(Tcl_Obj *) * pItem->nVertex * 2 +
             sizeof(Tcl_Obj *) * pRectangle->nFace;
    apFace = (Tcl_Obj **)C3dAlloc(nBytes);
    apVertex = &apFace[pRectangle->nFace];

    for (i = 0; i < pItem->nVertex; i++) {
        apVertex[i*2 + 0] = Tcl_NewDoubleObj(pItem->aVertex[i].x);
        apVertex[i*2 + 1] = Tcl_NewDoubleObj(pItem->aVertex[i].y);
    }

    for (i = 0; i < pRectangle->nFace; i++) {
        int nv = 2 * pRectangle->anVertex[i];
        apFace[i] = Tcl_NewListObj(nv, &apVertex[v * 2]);
        v += pRectangle->anVertex[i];
    }

    Tcl_SetListObj(pRet, pRectangle->nFace, apFace);
    C3dFree((char *)apFace);
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d2dLine --
 *
 *     Return the C3dItemType struct for 2dline items.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
C3dItemType *
C3d2dLine(void)
{
    static TwoDItemType line = {
        {
            "2dline",           /* zType    */
            twoDOverlay,        /* xOverlay */
            0,                  /* xCreate  */
            lineCreate,         /* xMultiCreate  */
            0,                  /* xDelete  */
            twoDDraw,           /* xDraw    */
            rectangleTable,     /* xTable   */
            0,                  /* xStatistics */
            twoDCoords          /* xCoords */
        },
        ITEM_LINE
    };
    return (C3dItemType *)&line;
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d2dPolygon --
 *
 *     Return the C3dItemType struct for 2dpolygon items.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
C3dItemType *
C3d2dPolygon(void)
{
    static TwoDItemType polygon = {
        {
            "2dpolygon",           /* zType    */
            twoDOverlay,        /* xOverlay */
            0,                  /* xCreate  */
            polygonCreate,      /* xMultiCreate  */
            0,                  /* xDelete  */
            twoDDraw,           /* xDraw    */
            rectangleTable,     /* xTable   */
            0,                  /* xStatistics */
            twoDCoords          /* xCoords */
        },
        ITEM_POLYGON
    };
    return (C3dItemType *)&polygon;
}
