/* TDCREATE.C

Description:
    Compares Autocad R12 drawing files (DWG extension assumed)
    for same TDCREATE systemvars.
	If two or more files have the same TDCREATE date
	list these filenames.

    Useful for AutoCAD course teachers to check if any two students
    started with the same drawing, somebody used anothers dwg as
    prototype, but it cannot be checked if someone inserts another
    dwg in to his. (my students aren't that good with blocks :)

	Though this may come in the next version, it is possible by checking
    the block handles.


Written and copyright by:
 	(c) URBAN Reinhard, Graz 1995 <rurban@sbox.tu-graz.ac.at>

Thanks to Frans J. Faase <faase@cs.utwente.nl>
   Without his DWG format description I wouldn't had tried
   to write this piece (but these vars are hardcoded, at the same pos,
   so I didn't need it)

Forbidden to be used for commercial purposes.
No warranties.

Compiled with Watcom 10.0a C/C++
  wcl /4/7/os/l=com /k8192 tdcreate getopts
Note:
  _splitpath2() is Watcom specific, but you can use the normal
    splitpath() without the temp. buffer, but allocate the paths
    dynamically because of stack overflows with deep nested directories.
  opendir() is Watcom specific too but POSIX compliant.
  For a simplier compiler use dos_findfirst() instead.


Usage:
    TDCREATE [Options] [file(s)...]

Revisions:
  V1.0   first release, without options
  V1.1   first public release, options: h,c,u,a,v,d
         date_print() corrected
  V1.2   options: s - recurse subdirs
         filespec() functions added, default extension ".DWG"
  v1.3	 bugfix: julian date conversion corrected,
 		   after the comma it was wrong
         filespec() functions removed
           (they were from the Symantec compiler package)
*/
char ur_header[]="$Id: tdcreate.c 1.3 1995/11/05 15:14:22 Reini Exp $";
char ur_cpyrght[]="(c) Reinhard Urban, Graz - Austria, 1995";

#define PROTOTYPES 1
#undef FUNCS		//no function translation yet

#include  <stdio.h>
#include  <string.h>
#include  <assert.h>
#include  <malloc.h>

#include  <stdlib.h>	// _splitpath():	watcom specific
#include  <direct.h>    // opendir()
#include  <search.h>    // lsearch()
#include  <math.h>      // floor()
#include  <sys\stat.h>

#include    "getopts.h"
//#include    "filespec.h"

/**************************************************************************/
/*  GLOBAL DEFINES  */
/**************************************************************************/
#ifndef ELEMENTS
  #define ELEMENTS(array) (sizeof(array)/sizeof((array)[0]))
#endif

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

#define DEBUG_P(X)  if (debug) printf X
#define VERBOSE_P(X)  if (verbose) printf X

#define TDCREATE_POS 0x31F
#define TDUPDATE_POS 0x327
#define NUMFILES 	 30


/**************************************************************************/
/*  EXTERNAL FUNCTIONS  DECLARATIONS */
/**************************************************************************/

/***************************************************************************/
/*  GLOBAL STRUCTURES  */
/***************************************************************************/
typedef struct
{  char path[PATH_MAX];			// stdio.h
   double date;
} FileStr;

/**************************************************************************/
/*  LOCAL FUNCTIONS  DECLARATIONS */
/**************************************************************************/
int dwg_getdate (double *d1,char *s);
char *date_print (char *s, double date);
void showhelp ();
int  check (char *infile);
void doit (char *arg);
void *checknull (void *p);

/**************************************************************************/
/*  GLOBAL VARIABLES  */
/**************************************************************************/
FileStr *files;
unsigned numfiles, numdone=0, numsame=0;

int  	help    = FALSE;   // displays usage
int  	all     = FALSE;   // show all dates
int  	create  = TRUE;    // look for TDCREATE
int  	update  = FALSE;   // look for TDUPDATE
int  	debug   = FALSE;
int  	verbose = FALSE;
int     recurse = FALSE;

opt_t opttable[] =
{
	{ "?", OPTBOOL, &help },
	{ "h", OPTBOOL, &help },
   	{ "c", OPTBOOL, &create },
   	{ "u", OPTBOOL, &update },
   	{ "a", OPTBOOL, &all },
   	{ "d", OPTBOOL, &debug },
   	{ "v", OPTBOOL, &verbose },

    { "s", OPTBOOL, &recurse }, // v1.2
    { NULL, 0, NULL }
};



/**************************************************************************/
/* Function definitions */
/**************************************************************************/
void showhelp (int full)
{
    puts (" \
TDCREATE V1.3, (c) and written by Reini Urban, Graz, 05.11.95\n \
Public Domain. Forbidden to be used for commercial purposes. No warranties\n \
Usage:\n \
   TDCREATE [Options] [file(s)...]\n \
Description:\n \
   List all AutoCAD R12 drawing files with the same TDCREATE systemvar\n \
   to check the same origin with multiple drawings.\n \
Arguments:\n \
   infile(s)...   none or more filenames, default: *.DWG\n \
Options:\n \
   /h   help\n \
   /a   all, shows all dates, not only the different.\n \
   /c   create, checks systemvar TDCREATE \n \
   /u   update, checks systemvar TDUPDATE \n \
   /s   recurse into subdirectories \n \
   /v   verbose printing\n \
   /d   debug, prints internal information too \n \
");
}

// conversion of julian date into calendar date
// returns formatted date output
// from "Numerical Recipes in Pascal", p.12
char *date_print (char *s, double date)
{
double t,ss;
int y,m,d,hh,mm;
long ja,jalpha,jb,jc,jd,je;

#define TRUNC(n) (long) floor(n)

	t = 86400*(date-floor(date));	 // in seconds
	date = floor (date);
	if ( date>2299161.0 )
	{	jalpha = TRUNC(((date-1867216)-0.25)/36524.25);
		ja = (long) (date+1+jalpha-TRUNC(0.25*jalpha));
	} else ja = (long)date;
	jb = ja+1524;
	jc = TRUNC(6680.0+((jb-2439870)-122.1)/365.25);
	jd = 365*jc+TRUNC(0.25*jc);
	je = TRUNC((jb-jd)/30.6001);
	d = (int) (jb-jd-TRUNC(30.6001*je));

	m = (int) (je-1);
	if ( m>12 ) mm -= 12;
	y = (int) (jc-4715);
	if ( m>2 ) y--;
	if ( y<=0 ) y--;

	hh = (int) floor(t/3600.0);
	t -= hh*3600.0;
	mm = (int) floor(t/60.0);
	ss = t- (mm*60.0);
	DEBUG_P (("\n%02d.%02d.%4d  %02d:%02d:%05.2f", d,m,y,hh,mm,ss));

	sprintf (s, "%02d.%02d.%4d  %02d:%02d:%05.2f", d,m,y,hh,mm,ss);
	return s;
}

/**************************************************************************/
/*.doc dwg_getpoint(internal)						  */
/*+
  returns TDCREATE date as <julian date> . <daytime ratio>
  from file s
-*/
/**************************************************************************/
int dwg_getdate ( double *d1, char *s )
{
FILE *fp;
unsigned long l1,l2;
char header[7];
unsigned long pos = TDCREATE_POS;

	if ( update )
		pos = TDUPDATE_POS;
	fp = fopen ( s,"rb" );
	if(!fp) {
		printf( "\nFile %s not found ",s );
		return FALSE;
	}

	fseek ( fp, 0, SEEK_SET );
	fread ( &header, 1, 6, fp ); header[6]=0;
    // check if dwg
    if ((strncmp ( header,"AC10",4 ) != 0))     //v1.3, accept only R12 dwg's
	  goto nodwg;
    if ((strcmp ( &header[4],"00" ) < 0))
	  goto nodwg;
    if ((strcmp ( &header[4],"09" ) > 0))       //specific R13 msg
	  goto nor13dwg;
    if ((strcmp ( &header[4],"09" ) < 0))
	  goto nor12dwg;

    fseek ( fp,pos,SEEK_SET );
	fread ( &l1,sizeof(l1),1,fp );
	fread ( &l2,sizeof(l2),1,fp );
	fclose ( fp );

	DEBUG_P (("\njulian date: %ld day/seconds*1000: %ld\n",l1,l2));

	*d1 = (double)l1;

	*d1 += (double)l2 / 86400000.0;           //v1.3: now this is correct
	return TRUE;

nodwg:
    printf( "\nFile %12s is no AutoCAD R12 DWG", s);
    return FALSE;
nor13dwg:
    printf( "\nFile %12s is an AutoCAD R13 DWG - ignored", s);
    return FALSE;
nor12dwg:
    printf( "\nFile %12s is an earlier AutoCAD DWG - not checked yet", s);
    return TRUE;
}

static void *checknull (void *p)
{
	if (p== (void *) 0)	{
		fprintf (stderr, "\nNull pointer - aborted.");
		abort();
	}
	return p;
}

int datecmp (const void *e1, const void *e2)
{
	return ((FileStr *)e1)->date == ((FileStr *)e2)->date ? 0 : 1;
}

int check (char *infile)
{
double date;
static unsigned int actfiles = 0;	// V1.3 unsigned added to get rid of
									// compiler warning in lsearch()
static FileStr fs = { "", 0.0 };    // dummy struct for adding with lsearch()
static char datestr[40];

	DEBUG_P (( "\ncheck ('%s') ",infile));
	if (dwg_getdate(&date,infile) == TRUE)
	{   FileStr *p;
		int num = actfiles;

		if ( actfiles >= numfiles-1 )
		{   files = (FileStr *) checknull (realloc (files, numfiles+(NUMFILES*sizeof(FileStr))));
			numfiles += NUMFILES;
		}
		fs.date = date;
		strcpy (fs.path, infile);
		numdone++;
		// search for the same date, if found actfiles is incremented, else
		// the dummy struct fs is added to files
	  	p = (FileStr *) lsearch (&fs, files, &actfiles, sizeof(FileStr), datecmp);
		DEBUG_P (("%0.8f: %11s  %11s",fs.date,fs.path,p->path));
		if ( num==actfiles )
		{	numsame++;
			printf ("\n%s  %11s == %11s",date_print(datestr,fs.date),
										 fs.path,p->path);
		}
		else
		    if (verbose || all)
			  printf("\n%s  %11s",date_print(datestr,fs.date),
								  fs.path);
	}
	return TRUE;
}

// check() for each file matching the argument
void doit (char *arg)
{
    DIR     *dirp;
    struct dirent *dd;
	char 	path [_MAX_PATH];
	char   *dir, *fname;
	char   *drive, *ext;

	static char *tmp_path, *tmpdir; // dynamic paths

	static char tmp_rpath [20];     // those vars stay during recursion
	static char *tmpfn, *tmpext;    // to keep the stack smaller

// note: POSIX compliant directory handling with
// opendir(),readdir(), instead of the old dos_findfirst()

    DEBUG_P (("\ndoit ('%s')",arg));
	tmp_path = (char *)checknull(malloc(_MAX_PATH2));
	_splitpath2 (arg, tmp_path, &drive, &dir, &fname, &ext);
	if ( (strcmp(arg,"..") == 0) )	// special case
	{   strcpy (dir, "..\\");
		*fname=0; *ext=0;
	}
	if ( (strcmp(arg,".") == 0) )	// special case
	{   *dir=0; *fname=0; *ext=0;
	}
	if ( !(*ext) )	// empty ext
		strcpy (ext, ".DWG");
	if ( !(*fname) )	// empty ext
		strcpy (fname, "*");
	_makepath (path, drive, dir, fname, ext);
    DEBUG_P (("\n path   : '%s'",path));
//    DEBUG_P (("\n arg:'%s',dir:'%s',fname:'%s',ext:'%s' path: '%s'",
//				arg,dir,fname,ext,path));

	// first check all the files
    if ((dirp = opendir (path) ))
 	{	for(;;)
 		{	dd = readdir ( dirp );
            if( dd == NULL ) break;
            if ( (dd->d_attr & 0x1E) !=0 ) continue;
            // only normal files 0x21 and read-only, archived
			// split into fname and extension
			_splitpath2 (dd->d_name, tmp_rpath, NULL, NULL, &tmpfn, &tmpext);
			// got only the filename
			_makepath (path, drive, dir, tmpfn, tmpext);
            //for each matching file
            check (path);
        }
        closedir ( dirp );
    }

	// now check all the subdirs
	if ( recurse )
	{   // check all the files in the dir
		_makepath (path, drive, dir, "*", ".*");
    	DEBUG_P (("\nchecking subdirs ..."));
    	DEBUG_P (("\n path   : '%s'",path));
		if ((dirp = opendir (path) ))
		{	for(;;)
			{	dd = readdir ( dirp );
			    if( dd == NULL ) break;
			    if (((dd->d_attr & _A_SUBDIR) != 0) &&
					 strcmp (dd->d_name,".") &&
					 strcmp (dd->d_name,".."))
			    {   // add it to dir (dir already has a trailing \\)
					tmpdir = (char *)checknull(malloc(_MAX_DIR));
					strcpy (tmpdir, dir);
					strcat (tmpdir, dd->d_name);
					strcat (tmpdir, "\\");
					_makepath (path, drive, tmpdir, fname, ext);
    				DEBUG_P   (("\n  recurse into path:  '%s'",path));
    				VERBOSE_P (("\n  recurse into '%s'", path));
				    doit (path);
					free (tmpdir);
    				//DEBUG_P   (("\n  out of:  '%s'",path));
			    }
			}
			closedir ( dirp );
    		//DEBUG_P   (("\n  no more dirs found in '%s'",path));
		}
	}
	free (tmp_path);
}

int main (int argc, char *argv[])
{
	int l,nextarg = 1;

  	nextarg = getopts(argc,argv,opttable);	//external function
    // check params for getopts() error
    for ( l=0; l<argc; l++ )
	{   if ( argv[l][0]=='-') argv[l][0]='/';
        DEBUG_P (("\nargv[%d]: \"%s\"",l,argv[l]));
    }
	if ( update )
		create = FALSE;
    DEBUG_P (("\nhelp    : %s",(help   == TRUE) ? "TRUE" : "FALSE"));
    DEBUG_P (("\ncreate  : %s",(create == TRUE) ? "TRUE" : "FALSE"));
    DEBUG_P (("\nupdate  : %s",(update == TRUE) ? "TRUE" : "FALSE"));
    DEBUG_P (("\nall     : %s",(all    == TRUE) ? "TRUE" : "FALSE"));
    DEBUG_P (("\nrecurse : %s",(recurse== TRUE) ? "TRUE" : "FALSE"));
    DEBUG_P (("\ndebug   : %s",(debug  == TRUE) ? "TRUE" : "FALSE"));
    DEBUG_P (("\nverbose : %s\n",(verbose== TRUE) ? "TRUE" : "FALSE"));
    VERBOSE_P (("TDCREATE V1.3, (c) and written by Reini Urban, Graz, 05.11.95\n"));
	if (update)
	  VERBOSE_P (("\nChecking $TDUPDATE sysvar"));
	else
	  VERBOSE_P (("\nChecking $TDCREATE sysvar"));
	if ( all )
	  VERBOSE_P (("\nShowing all dates"));
	else
	  VERBOSE_P (("\nSearches for same dates"));
	if ( help )
	{   showhelp(0);
	}

	files = (FileStr *) checknull (calloc (NUMFILES,sizeof(FileStr)));
	numfiles = NUMFILES;

	if ( nextarg == argc )
        doit ("*.DWG"); // no argument given
	else
	for ( ; nextarg < argc; nextarg++)
    {   doit (argv[nextarg]);
    }
	printf ("\n%d drawings processed, %d had equal vars",numdone,numsame);
    return 0;
}


