/*------------------------------------------------------------\
|                                                             |
| Tool    :                    RDSGDS                         |
|                                                             |
| File    :                  gds_drive.c                      |
|                                                             |
| Authors :                 Pierre Vittet                     |
|                    Modified by Jacomme Ludovic              |
| Date    :                   04/07/93                        |
|                                                             |
\------------------------------------------------------------*/
/*------------------------------------------------------------\
|                                                             |
|                         Include Files                       |
|                                                             |
\------------------------------------------------------------*/

# include <stdio.h>
# include <malloc.h>
# include <math.h>
# include <time.h>

# include MUT_H
# include MPH_H
# include RDS_H
# include RTL_H
# include RPR_H
# include RFM_H
# include RUT_H

# include "gds.h"
# include "gds_error.h"
# include "gds_drive.h"

/*------------------------------------------------------------\
|                                                             |
|                           Constants                         |
|                                                             |
\------------------------------------------------------------*/
/*------------------------------------------------------------\
|                                                             |
|                             Macros                          |
|                                                             |
\------------------------------------------------------------*/

#define controle(a) if (a != numb) {         \
         pv_init_error();     \
         pv_error.v_error = ENOSPACE;  \
         (void)fclose(fp);    \
         pv_give_error("controle"); \
         EXIT(1); }

#define entete(a,b)  infobuf.size = sizeof(hinfo_type) + b/sizeof(char);      \
         infobuf.gdscode = a;             \
         numb = fwrite((char *)&infobuf, sizeof(hinfo_type), 1, fp);\
         controle(1)

#define cadre numb = fwrite(&CADRE, sizeof(char), 1, fp); controle(1)

/*------------------------------------------------------------\
|                                                             |
|                            Types                            |
|                                                             |
\------------------------------------------------------------*/
/*------------------------------------------------------------\
|                                                             |
|                          Variables                          |
|                                                             |
\------------------------------------------------------------*/

/* variables globales mais internes au fichier */

char CADRE = '\0';   /* pour recommencer a ecrire sur un octet pair, on cadre avec le  */
         /* caractere nul en comblant l'octet impair.          */

/*------------------------------------------------------------\
|                                                             |
|                          Functions                          |
|                                                             |
\------------------------------------------------------------*/
/***********
*
*	Preliminaires sur les nombres flottants en GDS2.
*
*	Un flottant gds est represente par :
*	- 1 bit de signe		|	ces 8 bits occupent le 1er octet
*	- 7 bits d'exposant biaise a 64	|
*	- 24 ou 56 bits de mantisse selon la precision (les 3,7 octets suivants)
*
*	SEEEEEEE MMMMMMMM MMMMMMMM MMMMMMMM
*	SEEEEEEE MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM
*
*	L'exposant doit etre diminue de 64 pour avoir sa valeur reelle.
*	Decrit sur 7 bits, cela implique que l'exposant reel est compris entre -64
*	et +63 inclus.
*	Il s'applique a une puissance de 16.
*
*	Donc la valeur absolue du flottant correspondant a ce format est egal a :
*
*		mantisse * (16 ^ [exposant -64])
*
*	La mantisse est telle que   1/16 <= mantisse < 1 (du fait de la base 16).
*
*	En consequence de quoi, la valeur absolue d'un flottant GDS est comprise
*	entre
*		1/16.16^(-64)    et  ~16^63
*	donc entre
*		5.397605347e-79  et  7.237005577e75
*
*	A ce domaine, il faut ajouter la valeur 0 qui se represente par "tout a zero"
*
***/
/***********
*
*	DOUBLE_TO_GDSREAL
*
*	Cette fonction prend un nombre flottant et le traduit dans un format propre
*	aux reels des fichiers GDS2.
*
*	ENTREE : nombre flottant a traduire
*	precision voulue 1 : simple precision
*			 2 : double precision
*
*	RETOUR : pointeur sur une chaine de caracteres contenant la traduction.
*		 NULL si manque de place memoire.
*			depassement de capacite du reel GDS
*
*	REMARQUE : Cette chaine sera sur 4 ou 8 octets et ne sera pas terminee
*		par un '\0'.
*
**/
static char *
	pv_double_to_gdsreal( value, precision )
double	value;
short	precision;
{
	register int	i;
	char		buffer[56], *pt;

	pt = (char *)calloc((unsigned)precision * 4, sizeof(char));
	if (pt == (char *)NULL) {
		pv_init_error();
		pv_error.v_error = ENOMEM;
		pv_give_error("double_to_gdsreal");
		return((char *)NULL);
	}
	if (value == 0) return(pt); /* la valeur 0 est representee par une zone remplie de 0	*/

	/* On commence par s'occuper du signe du nombre.	*/
	*pt = (value > 0)? '\0' : '\300';
	value = fabs(value);

	/* on va chercher la valeur de l'exposant du nombre exprime en base 16.			*/
	i = 0;
	while (value >= 1.0) {	/* si le nombre correspond a une puissance positive de 16	*/
		value /= 16.0;
		i++;
	}
	while (value < 0.0625) {/* si le nombre correspond a une puissance negative de 16	*/
		value *= 16.0;
		i--;
	}
	if (i > 63) {		/* On prevoit les cas d'overflow.	*/
		pv_init_error();
		pv_error.v_error = EOVERFL;
		pv_error.v_textp = "La valeur absolue du nombre est trop grande pour etre traduite en GDS.\nLa limite est : 7.23 E+75";
		free(pt);
		pv_give_error("double_to_gdsreal");
		return((char *)NULL);
	} else if (i < -64) {         /* et d'underflow.                  */
		pv_init_error();
		pv_error.v_error = EOVERFL;
		pv_error.v_textp = "La valeur absolue du nombre est trop petite pour etre traduite en GDS.\nLa limite est : 5.4 E-79";
		free(pt);
		pv_give_error("double_to_gdsreal");
		return((char *)NULL);
	}

	i += 64;	/* On biaise l'exposant a 64. Ici, on est sur que  0 <= exp <= 127 (donc sur 7 bits)	*/
	*pt |= (char)i;	/* On vient d'ecrire le signe et la valeur de l'exposant biaise a 64			*/

	/* On va maintenant decomposer, bit a bit, la mantisse dans un tableau de caracteres.			*/
	for (i = 0; i < ( 24 + 32*(precision-1) ); i++) {
		value *= 2;
		if (value >= 1) {
			buffer[i] = '\1';
			value -= 1;
		} else buffer[i] = '\0';
	}	/* Puis on replace tous ces bits au sein de quelques octets (3 ou 7 selon la precision)	*/
	for (i = 0; i < ( 24 + 32*(precision-1) ); i++)
		pt[1+i/8] |= buffer[i] << (7 - i%8);

	return(pt);
}

/***********
*
*	ECRIT_GDSREAL
*
* Cette fonction est utilisee pour afficher a l'ecran ce a quoi ressemble un reel au format GDS2.
* Elle ne sert en fait a rien si ce n'est a verifier ce que rend la fonction double_to_gdsreal.
*
****/

static void
	pv_ecrit_gdsreal( value_str, size )
char	*value_str;
short	size;
{
	register int	i, j, k;
	char		buffer[65];
	char		buffer2[72];

	k = 0;
	for (i = 0; i < size; i++)
		for (j = 7; j >= 0; j--) /*	Pour chaque bit de l'octet	*/

			buffer[k++] = (((value_str[i] >> j) & 0x0001) != 0) ? '1' : '0'; /* Stocker sa valeur dans le tableau */

	for (i = 0, j = 0; i < size*8; i++) {
		if ( (i != 0) && (i%8 == 0) ) buffer2[j++] = ' ';
		buffer2[j] = buffer[i];
		j++;
	}
	buffer2[j] = '\0';
	return;
}

/***********
*
*	RDS_TO_GDS_SYM
*
* Cette fonction prend la definition d'une symetrie en RDS et la transpose en
* son equivalent en GDS. En fait, il s'agit aussi d'une scission,  le  format
* GDS separant clairement la notion de reflection et la rotation.
*
*	ENTREE : la symetrie RDS
*		adresse du flag pour memoriser si il y a reflection autour de l'axe
*			des abcisses
*		adresse de stockage de l'angle de rotation
*
*	RETOUR : aucun
*
***/
static void
	pv_rds_to_gds_sym( rds_sym, refx_p, angle_p )
char rds_sym;
short *refx_p;
double *angle_p;
{
	switch (rds_sym) {
		case RDS_NOSYM :
			*refx_p  = 0;
			*angle_p = 0.0;
			break;
		case RDS_ROT_P :
			*refx_p  = 0;
			*angle_p = 90.0;
			break;
		case RDS_SYMXY :
			*refx_p  = 0;
			*angle_p = 180.0;
			break;
		case RDS_ROT_M :
			*refx_p  = 0;
			*angle_p = 270.0;
			break;
		case RDS_SYM_X :
			*refx_p  = 1;
			*angle_p = 180.0;
			break;
		case RDS_SY_RM :
			*refx_p  = 1;
			*angle_p = 270.0;
			break;
		case RDS_SYM_Y :
			*refx_p  = 1;
			*angle_p = 0.0;
			break;
		case RDS_SY_RP :
			*refx_p  = 1;
			*angle_p = 90.0;
	}
}



/***********
*
*	SAUVE_RECTANGLE
*
***/
static int
	pv_sauve_rectangle( rect, fp, layer )
rdsrec_list	*rect;
FILE	*fp;
short	layer;
{
register	int numb;
hinfo_type infobuf;
short datatype = 0;
coord_t tab[5];

	tab[0].X = rect->X;
	tab[0].Y = rect->Y;
	tab[1].X = rect->X;
	tab[1].Y = rect->Y + rect->DY;
	tab[2].X = rect->X + rect->DX;
	tab[2].Y = rect->Y + rect->DY;
	tab[3].X = rect->X + rect->DX;
	tab[3].Y = rect->Y;
	tab[4].X = rect->X;
	tab[4].Y = rect->Y;

	entete(BOUNDARY, 0);

	entete(LAYER0, sizeof(short));
	numb = fwrite((char *)&layer, sizeof(short), 1, fp);
	controle(1);

	entete(DATATYPE, sizeof(short));
	numb = fwrite((char *)&datatype, sizeof(short), 1, fp);
	controle(1);

	entete(XY, 5 * sizeof(coord_t));
	numb = fwrite((char *)tab, sizeof(coord_t), 5, fp);
	controle(5);

	entete(ENDEL, 0);
	return 0;
}

/***********
*
*	SAUVE_INSTANCE
*
***/
static int
	pv_sauve_instance( inst, fp )
rdsins_list	*inst;
FILE	*fp;
{
	register int numb;
	hinfo_type infobuf;
	int     bool = FALSE;
	char    *gds_angle;
	short   ref_x_axis;
	double  rds_angle;
	ushort  strans;

	entete(SREF, 0);

	numb = strlen(inst->FIGNAME);
	if ( (numb % 2) != 0 ) {/* Si la longueur du nom est impaire, on la rend paire	*/
		numb += 1;	/* car chaque enregistrement doit comporter un nombre	*/
		bool = TRUE;	/* pair de caracteres dans un fichier GDS.		*/
	}
	entete(SNAME, numb * sizeof(char));
	if ( fputs(inst->FIGNAME, fp) < 0 ) {
		pv_init_error();
		pv_error.v_error = ENOSPACE;
		(void)fclose(fp);
		pv_give_error("sauve_instance");
		return(-1);
	}
	if (bool) cadre;

	pv_rds_to_gds_sym(inst->TRANSF, &ref_x_axis, &rds_angle);
	if (ref_x_axis || rds_angle) {
		strans = (ref_x_axis != 0) ? GREFLECT : 0;
		entete(STRANS, sizeof(ushort));
		numb = fwrite((char *)&strans, sizeof(ushort), 1, fp);
		controle(1);
	}

	if (rds_angle) {
		entete(ANGLE, sizeof(angle_type));
		gds_angle = pv_double_to_gdsreal(rds_angle, 2);
		if (gds_angle == (char *)NULL) {
			(void)fclose(fp);
			return(-1);
		}
		numb = fwrite(gds_angle, sizeof(angle_type), 1, fp);
		free(gds_angle);
		controle(1);
	}

	entete(XY, sizeof(coord_t));
	numb = fwrite((char *)&inst->X, sizeof(long), 1, fp);
	controle(1);
	numb = fwrite((char *)&inst->Y, sizeof(long), 1, fp);
	controle(1);

	entete(ENDEL, 0);
	return(0);
}

/****************
*
*	SAUVE_MODELE
*
**********/
static int
	pv_sauve_modele( modele, fp, date )
rdsfig_list *modele;
FILE	*fp;
date_type  *date;
{
	register int	numb, i;
	register rdsrec_list *rect;
	register rdsins_list *inst = modele->INSTANCE;
	hinfo_type		infobuf;
	int		bool = FALSE;
	short		layer;

	entete(BGNSTR, 2 * sizeof(date_type));
	numb = fwrite((char *)&date, sizeof(date_type), 1, fp);
	controle(1);
	numb = fwrite((char *)&date, sizeof(date_type), 1, fp);
	controle(1);

	numb = strlen(modele->NAME);
	if ( (numb % 2) != 0 ) {	/* Si la longueur du nom est impaire, on la rend paire */
		numb += 1;		/* car chaque enregistrement doit comporter un nombre  */
		bool = TRUE;		/* pair de caracteres dans un fichier GDS.             */
	}
	entete(STRNAME, numb * sizeof(char));
	if ( fputs(modele->NAME, fp) < 0 ) {
		pv_init_error();
		pv_error.v_error = ENOSPACE;
		(void)fclose(fp);
		pv_give_error("sauve_modele");
		return(-1);
	}
	if (bool) cadre;

	for (i = 0; i < RDS_MAX_LAYER; i++) {
		/* layer to be driven :
		   Layers that the user does not want, or need, to drive in GDS are
		   supposed to be set to -1, illegal GDS layer number. */
		if ((layer = GET_GDS_LAYER(i)) == RDS_GDS_LAYER_EMPTY )
			continue;
		if (modele->LAYERTAB[i]) {
			rect = modele->LAYERTAB[i];
			while (rect) {
				if (pv_sauve_rectangle(rect, fp, layer) < 0) return(-1);
					rect = rect->NEXT;
			}
		}
	}

	while (inst) {
		if (pv_sauve_instance(inst, fp) < 0) return(-1);
		inst = inst->NEXT;
	}

	entete(ENDSTR, 0);

	return(0);              /* tout s'est bien passe pendant la sauvegarde sur disque de la structure. */
}


/*------------------------------------------------------------\
|                                                             |
|                         gdssaverdsfig                       |
|                                                             |
\------------------------------------------------------------*/

 void gdssaverdsfig( Figure )

    rdsfig_list *Figure;
 {
int bool = FALSE;
register int numb;
register FILE *fp;
hinfo_type infobuf;
short version = 3;
date_type date;
struct tm *p;
time_t t;
char *u_unit, *m_unit;
ptype_list *model_list;

	pv_warning = 1;	/*	1 valeur par defaut, envoi de messages.	*/
/*
*	recherche d'un modele RDS dans la liste et retour de son pointeur,
* 	pointeur sur la figure pere a sauver; son nom servira a creer
*	le nom de la librairie GDS (nom interne)
*/
	model_list = (ptype_list *)reverse((chain_list *)getrdsmodellist(Figure));

	if ( (fp = mbkfopen(Figure->NAME, "gds", "w")) == NULL ) {
		pv_init_error();
		pv_error.v_error = ENOFILE;
		pv_error.v_textp = Figure->NAME;
		pv_give_error("savegds");
		EXIT(1); 
	}

	tzset();
	if (time(&t) == (time_t)-1) { /* recuperation de la date GMT du jour	*/
		(void)fclose(fp);
		pv_init_error();
		pv_error.v_error = ECLOCK;
		pv_give_error("savegds");
		EXIT(1);   
	}
	p = localtime(&t);          /* decompactage de la date GMT en date locale */
	if (p == (struct tm *)NULL) {
		(void)fclose(fp);
		pv_init_error();
		pv_error.v_error = ENOMEM;
		pv_give_error("savegds");
		EXIT(1);
	}
	date.year  = p->tm_year;/* sauvegarde des champs qui nous interessent	*/
	date.month = p->tm_mon;	/* dans date					*/
	date.day   = p->tm_mday;
	date.hour  = p->tm_hour;
	date.min   = p->tm_min;
	date.sec   = p->tm_sec;

	entete(HEADER, sizeof(short));
	numb = fwrite((char *)&version, sizeof(short), 1, fp);
	controle(1);

	entete(BGNLIB, 2 * sizeof(date_type));
	numb = fwrite((char *)&date, sizeof(date_type), 1, fp);
	controle(1);
	numb = fwrite((char *)&date, sizeof(date_type), 1, fp);
	controle(1);

	numb = strlen(Figure->NAME);
	if ( (numb % 2) != 0 ) {/* Si la longueur du nom est impaire, on la rend paire	*/
		numb += 1;	/* car chaque enregistrement doit comporter un nombre	*/
		bool = TRUE;	/* pair de caracteres dans un fichier GDS.		*/
	}
	entete(LIBNAME, numb * sizeof(char));
	if ( fputs(Figure->NAME, fp) < 0 ) {
		pv_init_error();
		pv_error.v_error = ENOSPACE;
		(void)fclose(fp);
		pv_give_error("savegds");
		EXIT(1);
	}
	if (bool)
		cadre;

	entete(UNITS, 2 * sizeof(unit_type));
	/* who cares about user defined unit ? */
	u_unit = pv_double_to_gdsreal(1.0e-6, 2);
	/* cas d'underflow, d'overflow ou de manque d'espace memoire */
	if (u_unit == (char *)NULL) {
		(void)fclose(fp);
		EXIT(1);
	}
	numb = fwrite(u_unit, sizeof(unit_type), 1, fp);
	free(u_unit);
	controle(1);
	m_unit = pv_double_to_gdsreal( 1.0e-6 / RDS_UNIT, 2);

	/* cas d'underflow, d'overflow ou de manque d'espace memoire */
	if (m_unit == (char *)NULL) {
		(void)fclose(fp);
		EXIT(1);
	}
	numb = fwrite(m_unit, sizeof(unit_type), 1, fp);
	free(m_unit);
	controle(1);

	while (model_list) {
		if (pv_sauve_modele((rdsfig_list *)model_list->DATA, fp, &date) < 0)
			EXIT(1);
		model_list = model_list->NEXT;
	}

	entete(ENDLIB, 0);

	if (fclose(fp) < 0) {
			pv_init_error();
			pv_error.v_error = EIO;
			pv_error.v_textp = "Probleme a la fermeture du fichier.";
			pv_give_error("savegds");
			EXIT(1);
		}
}
