/*****************************************************************************
* Generic parser for the "Irit" solid modeller, in binary mode.		     *
*									     *
* Written by:  Gershon Elber				Ver 0.1, Nov. 1993   *
*****************************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
#include <setjmp.h>
#include "irit_sm.h"
#include "iritprsr.h"
#include "allocate.h"
#include "attribut.h"
#include "irit_soc.h"

#define BIN_FILE_SYNC_STAMP	0x30106000

static int InputGetBinSync(FILE *f);
static VoidPtr InputGetBinBlock(FILE *f, VoidPtr Block, int Size);
static void OutputPutBinSync(FILE *f, int Type);
static void OutputPutBinBlock(FILE *f, VoidPtr Block, int Size);
static void IritPrsrGetBinObjectAux(void *f, IPObjectStruct *PObjParent);
static IPPolygonStruct *InputGetBinPolys(FILE *f, int IsPolygon);
static CagdCrvStruct *InputGetBinCurves(FILE *f);
static CagdSrfStruct *InputGetBinSurfaces(FILE *f);
static MatrixType *InputGetBinMatrix(FILE *f);
static char *InputGetBinString(FILE *f);
static IPObjectStruct **InputGetBinOList(FILE *f, int Len);
static IPAttributeStruct *InputGetBinAttributes(FILE *f);
static void OutputPutBinPolys(FILE *f, IPPolygonStruct *Pl);
static void OutputPutBinCurves(FILE *f, CagdCrvStruct *Crv);
static void OutputPutBinSurfaces(FILE *f, CagdSrfStruct *Srf);
static void OutputPutBinAttributes(FILE *f, IPAttributeStruct *Attrs);

/*****************************************************************************
*   Routine to get a sync stamp from input stream.			     *
*   Returns zero if no input is available.				     *
*****************************************************************************/
static int InputGetBinSync(FILE *f)
{
    unsigned long l;

    InputGetBinBlock(f, (VoidPtr) &l, sizeof(long));

    if ((l & 0xffffff00) == BIN_FILE_SYNC_STAMP)
	return l & 0xff;
    else
	return 0;
}

/*****************************************************************************
*   Routine to get a block of character from input stream.		     *
* If input returns EOF wait until new inputs arrive (can happen if reading   *
* from a non io blocked socket).					     *
*****************************************************************************/
static VoidPtr InputGetBinBlock(FILE *f, VoidPtr Block, int Size)
{
    int c;
    char
	*p = (char *) Block;

    if (Block == NULL)
        Block = IritMalloc(Size);

    while (Size-- > 0) {
	if (_IritPrsrReadSocket) {
	    while ((c = SocClientReadCharNonBlock()) == EOF) {
		IritSleep(10);
	    }
	}
	else
	    c = getc(f);

	*p++ = c;
    }

    return Block;
}

/*****************************************************************************
*   Routine to put a sync stamp to output stream.			     *
*****************************************************************************/
static void OutputPutBinSync(FILE *f, int Type)
{
    unsigned long l = (Type | BIN_FILE_SYNC_STAMP);

    OutputPutBinBlock(f, (VoidPtr) &l, sizeof(long));
}

/*****************************************************************************
*   Routine to put a block of character to output stream.		     *
*****************************************************************************/
static void OutputPutBinBlock(FILE *f, VoidPtr Block, int Size)
{
    if (_IritPrsrWriteSocket) {
	SocServerWriteLine(Block, Size);
    }
    else
        fwrite(Block, Size, 1, f);
}

/*****************************************************************************
* Routine to read one object from a given binary file, directly.	     *
* Note objects may be recursively defined.				     *
*****************************************************************************/
IPObjectStruct *IritPrsrGetBinObject(void *f)
{
    IPObjectStruct
	*PObjParent = IPAllocObject("", IP_OBJ_UNDEF, NULL);

    /* If the following gain control and is non zero - its from error! */
    if (setjmp(_IritPrsrLongJumpBuffer) != 0) {
	/* Error had occured (and will be reported). Return something... */
	PObjParent -> ObjType = IP_OBJ_NUMERIC;
	PObjParent -> U.R = 0.0;
	return PObjParent;
    }

    IritPrsrGetBinObjectAux(f, PObjParent);

    return IritPrsrProcessReadObject(PObjParent);
}

/*****************************************************************************
* Routine to read one object from a given binary file, directly.	     *
* Note objects may be recursively defined.				     *
*****************************************************************************/
static void IritPrsrGetBinObjectAux(void *f, IPObjectStruct *PObjParent)
{
    int Sync = InputGetBinSync(f);

    InputGetBinBlock(f, (VoidPtr) PObjParent, sizeof(IPObjectStruct));
    PObjParent -> Pnext = NULL;
    PObjParent -> Count = 1;

    if (PObjParent -> Attrs)
	PObjParent -> Attrs = InputGetBinAttributes(f);

    switch (Sync) {
	case IP_OBJ_POLY:
	    PObjParent -> U.Pl =
		InputGetBinPolys(f, IP_IS_POLYGON_OBJ(PObjParent));
	    break;
	case IP_OBJ_NUMERIC:
	    break;
	case IP_OBJ_POINT:
	    break;
	case IP_OBJ_VECTOR:
	    break;
	case IP_OBJ_PLANE:
	    break;
	case IP_OBJ_MATRIX:
	    PObjParent -> U.Mat = InputGetBinMatrix(f);
	    break;
	case IP_OBJ_CURVE:
	    PObjParent -> U.Crvs = InputGetBinCurves(f);
	    break;
	case IP_OBJ_SURFACE:
	    PObjParent -> U.Srfs = InputGetBinSurfaces(f);
	    break;
	case IP_OBJ_STRING:
	    PObjParent -> U.Str = InputGetBinString(f);
	    break;
	case IP_OBJ_LIST_OBJ:
	    PObjParent -> U.Lst.PObjList =
		InputGetBinOList(f, PObjParent -> U.Lst.ListMaxLen);
	    break;
	case IP_OBJ_CTLPT:
	    break;
	default:
	    IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");
    }

    if (Sync != IP_OBJ_LIST_OBJ &&
	AttrGetObjectColor(PObjParent) == IP_ATTR_NO_COLOR)
    	AttrSetObjectColor(PObjParent, IP_LOAD_COLOR);
}

/*****************************************************************************
*   Routine to get a list of polys from bin input stream.		     *
*****************************************************************************/
static IPPolygonStruct *InputGetBinPolys(FILE *f, int IsPolygon)
{
    int PSync;
    IPPolygonStruct
	*PHead = NULL,
	*PTail = NULL;

    while ((PSync = InputGetBinSync(f)) == IP_OBJ_AUX_POLY) {
	int VSync;
	IPPolygonStruct
	    *Poly = IPAllocPolygon(0, 0, NULL, NULL);
	IPVertexStruct *Vrtx,
	    *VTail = NULL;

	InputGetBinBlock(f, (VoidPtr) Poly, sizeof(IPPolygonStruct));
	if (Poly -> Attrs)
	    Poly -> Attrs = InputGetBinAttributes(f);

	Poly -> PVertex = NULL;
	while ((VSync = InputGetBinSync(f)) == IP_OBJ_AUX_VERTEX) {
	    Vrtx = IPAllocVertex(0, 0, NULL, NULL);
	    InputGetBinBlock(f, (VoidPtr) Vrtx, sizeof(IPVertexStruct));
	    if (Vrtx -> Attrs)
		Vrtx -> Attrs = InputGetBinAttributes(f);

	    if (Poly -> PVertex) {
	        VTail -> Pnext = Vrtx;
		VTail = Vrtx;
	    }
	    else {
		VTail = Poly -> PVertex = Vrtx;
	    }
	}
	if (_IritPrsrPolyListCirc)
	    VTail -> Pnext = Poly -> PVertex;
	else
	    VTail -> Pnext = NULL;
	if (VSync != IP_OBJ_AUX_END)
	    IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");

	if (IsPolygon) {
	    if (!IP_HAS_PLANE_POLY(Poly))
		IritPrsrUpdatePolyPlane(Poly);

	    IritPrsrUpdateVrtxNrml(Poly, Poly -> Plane);
	}

	if (PHead) {
	    PTail -> Pnext = Poly;
	    PTail = Poly;
	}
	else {
	    PHead = PTail = Poly;
	}
    }
    if (PSync != IP_OBJ_AUX_END)
	IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");

    if (PTail)
	PTail -> Pnext = NULL;

    if (PHead == NULL)
	IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");

    return PHead;
}

/*****************************************************************************
*   Routine to get a list of curves from bin input stream.		     *
*****************************************************************************/
static CagdCrvStruct *InputGetBinCurves(FILE *f)
{
    int i, Sync, Size;
    CagdCrvStruct *Crv,
	*CHead = NULL,
	*CTail = NULL;

    while ((Sync = InputGetBinSync(f)) == IP_OBJ_AUX_CURVE) {
	Crv = (CagdCrvStruct *) IritMalloc(sizeof(CagdCrvStruct));
	InputGetBinBlock(f, (VoidPtr) Crv, sizeof(CagdCrvStruct));
	Size = sizeof(CagdRType) * Crv -> Length;

	for (i = !CAGD_IS_RATIONAL_PT(Crv -> PType);
	     i <= CAGD_NUM_OF_PT_COORD(Crv -> PType);
	     i++) {
	    Crv -> Points[i] = (CagdRType *) IritMalloc(Size);
	    InputGetBinBlock(f, (VoidPtr) (Crv -> Points[i]), Size);
	}

	for (i = CAGD_NUM_OF_PT_COORD(Crv -> PType) + 1;
	     i <= CAGD_MAX_PT_COORD;
	     i++)
	    Crv -> Points[i] = NULL;

	if (Crv -> GType == CAGD_CBSPLINE_TYPE) {
	    Size = sizeof(CagdRType) * (Crv -> Length + Crv -> Order);
	    Crv -> KnotVector = (CagdRType *) IritMalloc(Size);
	    InputGetBinBlock(f, (VoidPtr) (Crv -> KnotVector), Size);
	}

	if (CHead == NULL) {
	    CHead = CTail = Crv;
	}
	else {
	    CTail -> Pnext = Crv;
	    CTail = Crv;
	}
    }
    if (Sync != IP_OBJ_AUX_END)
	IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");

    if (CHead == NULL)
	IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");

    return CHead;
}

/*****************************************************************************
*   Routine to get a list of surfaces from bin input stream.		     *
*****************************************************************************/
static CagdSrfStruct *InputGetBinSurfaces(FILE *f)
{
    int i, Sync, Size;
    CagdSrfStruct *Srf,
	*SHead = NULL,
	*STail = NULL;

    while ((Sync = InputGetBinSync(f)) == IP_OBJ_AUX_SURFACE) {
	Srf = (CagdSrfStruct *) IritMalloc(sizeof(CagdSrfStruct));
	InputGetBinBlock(f, (VoidPtr) Srf, sizeof(CagdSrfStruct));
	Size = sizeof(CagdRType) * Srf -> ULength * Srf -> VLength;

	for (i = !CAGD_IS_RATIONAL_PT(Srf -> PType);
	     i <= CAGD_NUM_OF_PT_COORD(Srf -> PType);
	     i++) {
	    Srf -> Points[i] = (CagdRType *) IritMalloc(Size);
	    InputGetBinBlock(f, (VoidPtr) (Srf -> Points[i]), Size);
	}

	for (i = CAGD_NUM_OF_PT_COORD(Srf -> PType) + 1;
	     i <= CAGD_MAX_PT_COORD;
	     i++)
	    Srf -> Points[i] = NULL;

	if (Srf -> GType == CAGD_SBSPLINE_TYPE) {
	    Size = sizeof(CagdRType) * (Srf -> ULength + Srf -> UOrder);
	    Srf -> UKnotVector = (CagdRType *) IritMalloc(Size);
	    InputGetBinBlock(f, (VoidPtr) (Srf -> UKnotVector), Size);
	    Size = sizeof(CagdRType) * (Srf -> VLength + Srf -> VOrder);
	    Srf -> VKnotVector = (CagdRType *) IritMalloc(Size);
	    InputGetBinBlock(f, (VoidPtr) (Srf -> VKnotVector), Size);
	}

	if (SHead == NULL) {
	    SHead = STail = Srf;
	}
	else {
	    STail -> Pnext = Srf;
	    STail = Srf;
	}
    }
    if (Sync != IP_OBJ_AUX_END || SHead == NULL)
	IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");

    return SHead;
}

/*****************************************************************************
*   Routine to get a matrix from bin input stream.			     *
*****************************************************************************/
static MatrixType *InputGetBinMatrix(FILE *f)
{
    MatrixType *Mat;

    if (InputGetBinSync(f) != IP_OBJ_AUX_MATRIX)
	IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");
    Mat = (MatrixType *) IritMalloc(sizeof(MatrixType));
    InputGetBinBlock(f, (VoidPtr) Mat, sizeof(MatrixType));

    return Mat;
}

/*****************************************************************************
*   Routine to get a string from bin input stream.			     *
*****************************************************************************/
static char *InputGetBinString(FILE *f)
{
    long Len;
    char *Str;

    if (InputGetBinSync(f) != IP_OBJ_AUX_STRING)
	IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");
    InputGetBinBlock(f, (VoidPtr) &Len, sizeof(long));
    Str = (char *) IritMalloc(Len);
    InputGetBinBlock(f, (VoidPtr) Str, Len);
    return Str;
}

/*****************************************************************************
*   Routine to get an object list from bin input stream.		     *
*****************************************************************************/
static IPObjectStruct **InputGetBinOList(FILE *f, int Len)
{
    int i;
    struct IPObjectStruct *PTmp,
	**PObjList = (IPObjectStruct **)
	    IritMalloc(Len * sizeof(IPObjectStruct *));

    if (InputGetBinSync(f) != IP_OBJ_AUX_OLST)
	IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");
    InputGetBinBlock(f, (VoidPtr) PObjList, Len * sizeof(IPObjectStruct *));

    for (i = 0; i < Len && PObjList[i] != NULL; i++) {
	PTmp = IPAllocObject("", IP_OBJ_UNDEF, NULL);
	IritPrsrGetBinObjectAux(f, PTmp);
	PObjList[i] = PTmp;
    }
    if (i < Len - 1)
	PObjList[i] = NULL;

    return PObjList;
}

/*****************************************************************************
*   Routine to get a list of attributes from bin input stream.		     *
*****************************************************************************/
static IPAttributeStruct *InputGetBinAttributes(FILE *f)
{
    int Sync;
    IPAttributeStruct *Attr,
	*ATail = NULL,
	*AHead = NULL;

    while ((Sync = InputGetBinSync(f)) == IP_OBJ_AUX_ATTR) {
	long Len;

	Attr = (IPAttributeStruct *) IritMalloc(sizeof(IPAttributeStruct));
	InputGetBinBlock(f, (VoidPtr) Attr, sizeof(IPAttributeStruct));
	InputGetBinBlock(f, (VoidPtr) &Len, sizeof(long));
	Attr -> Name = IritMalloc(Len);
	InputGetBinBlock(f, (VoidPtr) (Attr -> Name), Len);
	if (Attr -> Type == IP_ATTR_STR) {
	    if (InputGetBinSync(f) != IP_OBJ_AUX_STRING)
		IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");
	    InputGetBinBlock(f, (VoidPtr) &Len, sizeof(long));
	    Attr -> U.Str = IritMalloc(Len);
	    InputGetBinBlock(f, (VoidPtr) (Attr -> U.Str), Len);
	}

	if (AHead) {
	    ATail -> Pnext = Attr;
	    ATail = Attr;
	}
	else {
	    ATail = AHead = Attr;
	}
    }
    if (Sync != IP_OBJ_AUX_END)
	IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");

    return AHead;
}

/*****************************************************************************
* Routine to write one object to a given binary file, directly.		     *
* Note objects may be recursively defined.				     *
*****************************************************************************/
void IritPrsrPutBinObject(void *f, IPObjectStruct *PObj)
{
    int i;
    long Len;
    IPObjectStruct *PTmp;

    /* If the following gain control and is non zero - its from error! */
    if (setjmp(_IritPrsrLongJumpBuffer) != 0) {
	/* Error had occured (and will be reported). */
	return;
    }

    OutputPutBinSync(f, PObj -> ObjType);
    OutputPutBinBlock(f, (VoidPtr) PObj, sizeof(IPObjectStruct));
    if (PObj -> Attrs != NULL)
	OutputPutBinAttributes(f, PObj -> Attrs);

    switch (PObj -> ObjType) {
	case IP_OBJ_POLY:
	    OutputPutBinPolys(f, PObj -> U.Pl);
	    break;
	case IP_OBJ_NUMERIC:
	    break;
	case IP_OBJ_POINT:
	    break;
	case IP_OBJ_VECTOR:
	    break;
	case IP_OBJ_PLANE:
	    break;
	case IP_OBJ_MATRIX:
	    OutputPutBinSync(f, IP_OBJ_AUX_MATRIX);
	    OutputPutBinBlock(f, (VoidPtr) (*PObj -> U.Mat),
			      sizeof(MatrixType));
	    break;
	case IP_OBJ_CURVE:
	    OutputPutBinCurves(f, PObj -> U.Crvs);
	    break;
	case IP_OBJ_SURFACE:
	    OutputPutBinSurfaces(f, PObj -> U.Srfs);
	    break;
	case IP_OBJ_STRING:
	    OutputPutBinSync(f, IP_OBJ_AUX_STRING);
	    Len = strlen(PObj -> U.Str) + 1;
	    OutputPutBinBlock(f, (VoidPtr) &Len, sizeof(long));
	    OutputPutBinBlock(f, (VoidPtr) (PObj -> U.Str), Len);
	    break;
	case IP_OBJ_LIST_OBJ:
	    OutputPutBinSync(f, IP_OBJ_AUX_OLST);
	    OutputPutBinBlock(f, (VoidPtr) PObj -> U.Lst.PObjList,
			sizeof(IPObjectStruct *) * PObj -> U.Lst.ListMaxLen);
	    for (i = 0;
		 i < PObj -> U.Lst.ListMaxLen &&
		 (PTmp = ListObjectGet(PObj, i)) != NULL;
		 i++) {
		IritPrsrPutBinObject(f, PTmp);
	    }
	    break;
	case IP_OBJ_CTLPT:
	    break;
	default:
	    IritPrsrParserAbort(IP_ERR_BIN_UNDEF_OBJ, "");
    }
}

/*****************************************************************************
*   Routine to put a list of polys to bin output stream.		     *
*****************************************************************************/
static void OutputPutBinPolys(FILE *f, IPPolygonStruct *Pl)
{
    for (; Pl != NULL; Pl = Pl -> Pnext) {
	IPVertexStruct
	    *V = Pl -> PVertex;

	OutputPutBinSync(f, IP_OBJ_AUX_POLY);
	OutputPutBinBlock(f, (VoidPtr) Pl, sizeof(IPPolygonStruct));
	if (Pl -> Attrs != NULL)
	    OutputPutBinAttributes(f, Pl -> Attrs);

	do {
	    OutputPutBinSync(f, IP_OBJ_AUX_VERTEX);
	    OutputPutBinBlock(f, (VoidPtr) V, sizeof(IPVertexStruct));
	    if (V -> Attrs != NULL)
		OutputPutBinAttributes(f, V -> Attrs);
	    V = V -> Pnext;
	}
	while (V != NULL && V != Pl -> PVertex);
	OutputPutBinSync(f, IP_OBJ_AUX_END);
    }
    OutputPutBinSync(f, IP_OBJ_AUX_END);
}

/*****************************************************************************
*   Routine to put a list of curves to bin output stream.		     *
*****************************************************************************/
static void OutputPutBinCurves(FILE *f, CagdCrvStruct *Crv)
{
    for ( ; Crv != NULL; Crv = Crv -> Pnext) {
	int i,
	    Size = sizeof(CagdRType) * Crv -> Length;

	OutputPutBinSync(f, IP_OBJ_AUX_CURVE);
	OutputPutBinBlock(f, (VoidPtr) Crv, sizeof(CagdCrvStruct));

	for (i = !CAGD_IS_RATIONAL_PT(Crv -> PType);
	     i <= CAGD_NUM_OF_PT_COORD(Crv -> PType);
	     i++) {
	    OutputPutBinBlock(f, (VoidPtr) (Crv -> Points[i]), Size);
	}

	if (Crv -> GType == CAGD_CBSPLINE_TYPE) {
	    Size = sizeof(CagdRType) * (Crv -> Length + Crv -> Order);
	    OutputPutBinBlock(f, (VoidPtr) (Crv -> KnotVector), Size);
	}
    }
    OutputPutBinSync(f, IP_OBJ_AUX_END);
}

/*****************************************************************************
*   Routine to put a list of surfaces to bin output stream.		     *
*****************************************************************************/
static void OutputPutBinSurfaces(FILE *f, CagdSrfStruct *Srf)
{
    for ( ; Srf != NULL; Srf = Srf -> Pnext) {
	int i,
	    Size = sizeof(CagdRType) * Srf -> ULength * Srf -> VLength;

	OutputPutBinSync(f, IP_OBJ_AUX_SURFACE);
	OutputPutBinBlock(f, (VoidPtr) Srf, sizeof(CagdSrfStruct));

	for (i = !CAGD_IS_RATIONAL_PT(Srf -> PType);
	     i <= CAGD_NUM_OF_PT_COORD(Srf -> PType);
	     i++) {
	    OutputPutBinBlock(f, (VoidPtr) (Srf -> Points[i]), Size);
	}

	if (Srf -> GType == CAGD_SBSPLINE_TYPE) {
	    Size = sizeof(CagdRType) * (Srf -> ULength + Srf -> UOrder);
	    OutputPutBinBlock(f, (VoidPtr) (Srf -> UKnotVector), Size);
	    Size = sizeof(CagdRType) * (Srf -> VLength + Srf -> VOrder);
	    OutputPutBinBlock(f, (VoidPtr) (Srf -> VKnotVector), Size);
	}
    }
    OutputPutBinSync(f, IP_OBJ_AUX_END);
}

/*****************************************************************************
*   Routine to put a list of attributes to bin output stream.		     *
*****************************************************************************/
static void OutputPutBinAttributes(FILE *f, IPAttributeStruct *Attrs)
{
    for ( ; Attrs != NULL; Attrs = Attrs -> Pnext) {
	if (Attrs -> Type == IP_ATTR_INT ||
	    Attrs -> Type == IP_ATTR_REAL ||
	    Attrs -> Type == IP_ATTR_STR) {
	    long Len = strlen(Attrs -> Name) + 1;

	    OutputPutBinSync(f, IP_OBJ_AUX_ATTR);
	    OutputPutBinBlock(f, (VoidPtr) Attrs, sizeof(IPAttributeStruct));
	    OutputPutBinBlock(f, (VoidPtr) &Len, sizeof(long));
	    OutputPutBinBlock(f, (VoidPtr) (Attrs -> Name), Len);

	    if (Attrs -> Type == IP_ATTR_STR) {
		Len = strlen(Attrs -> U.Str) + 1;

		OutputPutBinSync(f, IP_OBJ_AUX_STRING);
		OutputPutBinBlock(f, (VoidPtr) &Len, sizeof(long));
		OutputPutBinBlock(f, (VoidPtr) (Attrs -> U.Str), Len);
	    }	
	}
    }
    OutputPutBinSync(f, IP_OBJ_AUX_END);
}
