/*
*  Windows Metafile Reader
* 
* $Log: wmf.c,v $
*  Revision 1.16  1995/12/04  19:23:10  fuscoj
*  Added debug code.
*
*  Revision 1.15  1995/02/20  21:21:03  JFusco
*  Pre-release.
*
*  Revision 1.14  1994/11/08  22:10:52  JFusco
*  Added iff save.
*
*  Revision 1.13  1994/06/27  22:40:55  JFusco
*  Removed color printf()'s.
*
*  Revision 1.12  1994/06/26  18:03:53  JFusco
*  Better support for colors and brushes, still no fill patterns.
*
*  Revision 1.11  1994/06/25  02:28:03  JFusco
*  Better fills (?).  Tries to match luminance of color values.
*
*  Revision 1.10  1994/06/22  22:22:09  JFusco
*  Checking to determine if Handles are being handled properly.
*
*  Revision 1.9  1994/06/20  23:23:06  JFusco
*  Added polygon fill routines
*
*  Revision 1.8  1994/06/19  22:59:29  JFusco
*  Removed printfs.  Uses stderr for really important info.
*
*  Revision 1.7  1994/06/18  03:10:00  JFusco
*  Lots of changes, too numerous to mention.
*  Added amiga.c and window.c so it now draws to the screen.
*  
*/

#include <sys/types.h> /* ULONG, UWORD, etc. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "dformat.h"
#include "wmf.h"
#include "window.h"

/* Unconditional pointer test or DIE!!!. */
#define ASSERT(a)   if (!(a)) { perror("no memory!\n"); CleanExit(1); }
#ifndef TRUE
#define TRUE 1
#endif
#ifdef DEBUG
int AbortFlag=0;
int RecCtr=0;
#define DBGREC printf("RecCtr=%d\n",RecCtr);
#else
#define DBGREC
#endif

/* Local Prototypes. */
void FunctionHandler( struct rec_tmpl * );
char *wmf_func_name( unsigned short, unsigned char );
unsigned CreateHandle( wmf_objects, struct rec_tmpl * );

char            *filename=NULL, *savename=NULL;
wmf_hdr_t       FileHdr;
static wmf_handle_t    *Handle;
static wmf_place_hdr_t PlaceHdr;

unsigned     window_okay = 0;

main(int argc, char **argv)
{
   FILE *fin;
   unsigned short hbuf[2];
   struct rec_tmpl *Rec;

   if ((argc<2) || (! (fin = fopen( argv[1], "rb" )))) {
      fprintf(stderr,"WMF viewer version 1.0 by John Fusco, 1994\n");
      fprintf(stderr,"usage: wmf {wmf_filename} [{save_as_iff}]\n");
      exit(1);
   }
   filename = argv[1];
   if (argc==3) savename = argv[2];

   /* Read 4 bytes to determine if this is a normal or placeable metafile. */
   fread( (char *) hbuf, 1, 4, fin );
   reformat_short( hbuf, sizeof(hbuf) );

   /* Most files should have the place header. */
   if (hbuf[0] == 0xcdd7 && hbuf[1]==0x9ac6) {
      memcpy( &PlaceHdr, &hbuf[0], 4);
      fread((char *)&PlaceHdr.unused0, 1,sizeof(PlaceHdr)-4,fin);
      reformat_short(&PlaceHdr.unused0, sizeof(PlaceHdr)-4);

      /* Set the window extent to maximum until we're told otherwise. */
      WindowExt.X = abs( PlaceHdr.Left - PlaceHdr.Right );
      WindowExt.Y = abs( PlaceHdr.Top - PlaceHdr.Bottom );

      /* We can use what we have if necessary. */
      window_okay = TRUE;

      /* Read the file header, which immediately follows. */
      fread((char *)&FileHdr, 1,sizeof(FileHdr),fin);
      reformat_short( (unsigned short *)&FileHdr, 4);
   }
   else {
      /* No place header, just a file header. */
      memcpy( &FileHdr, &hbuf[0], sizeof(hbuf));
      fread((char *)&FileHdr.Ver, 1,sizeof(FileHdr)-4,fin);
   }

   /* Finish little endian to big endian conversion. */
   reformat_short(&FileHdr.Ver, 14);
   fix_mixed_longs( &FileHdr.Fsize, sizeof(FileHdr.Fsize));
   fix_mixed_longs( &FileHdr.Largest, sizeof(FileHdr.Largest));

   /* Sanity checks. */
   if ( FileHdr.Type != 1  || 
        FileHdr.Hsize != 9 || 
        FileHdr.Largest > FileHdr.Fsize ) {
      fprintf(stderr,"Not a Windows Metafile!\n");
      exit(1);
   }

   /* All wmf sizes are in 16-bit words. */
   FileHdr.Largest <<= 1;
   Rec = (struct rec_tmpl *) malloc(FileHdr.Largest);
   Handle = (wmf_handle_t *)calloc(sizeof(wmf_handle_t),FileHdr.numHandles);
   if (Rec==NULL || Handle==NULL) {
      perror("allocating initial space.");
      exit(1);
   }

#ifdef DEBUG
   fprintf(stderr,"Top Left     x=%6d y=%6d\n",PlaceHdr.Left,PlaceHdr.Top);
   fprintf(stderr,"Bottom Right x=%6d y=%6d\n",PlaceHdr.Right,PlaceHdr.Bottom);
   fprintf(stderr,"%d pixels per inch\n",PlaceHdr.pixelsPerInch);
   fprintf(stderr,"%d Handles used.\n\n",FileHdr.numHandles);
#endif

   while ( fread((char *)Rec, 1, 6, fin ) == 6 ) {

#ifdef DEBUG
      RecCtr++;
#endif
      /* Little endian to big endian conversion. */
      reformat_long( &Rec->Size, 4);
      reformat_short(&Rec->Func, 2);

      Rec->Size <<= 1;
      if (Rec->Size>FileHdr.Largest) {
         fprintf(stderr,"Record too large: %ld bytes; expected %ld\n",
		 Rec->Size, FileHdr.Largest);
         break;
      }

      /* Read the rest of the record. */
      Rec->Size -= 6;
      if (fread((char *)Rec->Data, 1, Rec->Size, fin ) != Rec->Size ) break;

      /* Convert data, any embedded longs will have to be corrected. */
      reformat_short( Rec->Data, Rec->Size );

      FunctionHandler( Rec );

#ifdef DEBUG
      if (AbortFlag) break;
#endif
   };

   fclose(fin);
   if (savename) {
      AmigaSave( savename );
   }
   else {
      AmigaWait();
   }
   CleanExit(0);
}

void FunctionHandler( struct rec_tmpl *Rec )
{
   switch( Rec->Func ) {
#ifdef DEBUG
   case WMF_AnimatePalette:
   case WMF_Arc:
   case WMF_BitBlt:
   case WMF_BitBlt3:
   case WMF_Chord:
   case WMF_DrawText:
   case WMF_Ellipse:
   case WMF_Escape:
   case WMF_ExcludeClipRect:
   case WMF_ExtTextOut:
   case WMF_FloodFill:
   case WMF_InsertClipRect:
   case WMF_LineTo:
   case WMF_MoveTo:
   case WMF_OffsetClipRgn:
   case WMF_OffsetViewportOrg:
   case WMF_OffsetWindowOrg:
   case WMF_PatBlt:
   case WMF_Pie:
   case WMF_PolyPolygon:
   case WMF_RealizePalette:
   case WMF_Rectangle:
   case WMF_ResizePalette:
   case WMF_RestoreDC:
   case WMF_RoundRect:
   case WMF_SaveDC:
   case WMF_ScaleViewportExt:
   case WMF_ScaleWindowExt:
   case WMF_SelectClipRgn:
   case WMF_SelectPalette:
   case WMF_SetBkColor:
   case WMF_SetBkMode:
   case WMF_SetDIBitsToDevice:
   case WMF_SetMapMode:
   case WMF_SetMapperFlags:
   case WMF_SetPaletteEntries:
   case WMF_SetPixel:
   case WMF_SetStretchBltMode:
   case WMF_SetTextAlign:
   case WMF_SetTextCharExtra:
   case WMF_SetTextColor:
   case WMF_SetTextJustification:
   case WMF_SetViewportExt:
   case WMF_SetViewportOrg:
   case WMF_StretchBlt:
   case WMF_StretchBlt3:
   case WMF_StretchDIBits:
   case WMF_TextOut:
        MyDeepestApologies( Rec->Func );
        break;
#endif
   /* Got to keep track of these even if I don't implement them. */
   case WMF_CreateFontIndirect:
        CreateHandle( FONT, Rec );
        break;

   case WMF_CreatePalette:
        CreateHandle( PALETTE, Rec );
        break;

   case WMF_CreatePatternBrush:
        CreateHandle( PATTERNBRUSH, Rec );
        break;

   case WMF_CreatePatternBrush3:
        CreateHandle( PATTERNBRUSH3, Rec );
        break;

   case WMF_CreateRegion:
        MyDeepestApologies( Rec->Func );
        fprintf(stderr,"Did you know this function is undocumented!\n");
        CleanExit(1);
        break;

   /* Stuff I've actually implemented!!! (or plan to).*/
   case WMF_CreatePenIndirect:
        {
          static WarnPenColor=1;
          pen_t *Pen = (pen_t *) Rec->Data;
          fix_mixed_longs( &Pen->Color, 4 );
          if (Pen->Color.IDX.Type==1 && WarnPenColor) {
             WarnPenColor=0;
             fprintf(stderr,"Color index not implemented: %08lx\n",
                                                           Pen->Color);
          }
        }
        CreateHandle( PEN, Rec );
        break;

   case WMF_CreateBrushIndirect:
        {
          static WarnBrushColor=1;
          brush_t *Brush = (brush_t *) Rec->Data;
          fix_mixed_longs( &Brush->Color, 4 );
          if (Brush->Color.IDX.Type==1 && WarnBrushColor) {
             DBGREC;
             WarnBrushColor=0;
             fprintf(stderr,"Color index not implemented: %08lx\n",
                                                           Brush->Color);
          }
        }
        CreateHandle( BRUSH, Rec );
        break;

   case WMF_SelectObject:
        assert( Rec->Data[0]<FileHdr.numHandles && Rec->Data[0] >= 0 );
	SelectObject( Rec->Data[0] );
        break;

   case WMF_DeleteObject:
        assert( Rec->Data[0]<FileHdr.numHandles && Rec->Data[0] >= 0 );
        {
           unsigned idx = Rec->Data[0];
           Handle[idx].Type = NOTANOBJ;
           free( Handle[idx].Data );
           Handle[idx].Data = NULL;
        }
        break;


   case WMF_Polygon:
        {
          unsigned i;
          poly_point_tmpl_t *Poly= (poly_point_tmpl_t *) Rec->Data;

          PolygonMoveWC( Poly->Point[0].X, Poly->Point[0].Y );
          for (i=1; i<Poly->numPoints; i++) {
            PolygonDrawWC( Poly->Point[i].X, Poly->Point[i].Y );
          }
          PolygonEndWC();
        }
        break;

   case WMF_Polyline:
        {
          unsigned i;
          poly_point_tmpl_t *Poly= (poly_point_tmpl_t *) Rec->Data;

          MoveWC( Poly->Point[0].X, Poly->Point[0].Y );
          for (i=1; i<Poly->numPoints; i++) {
            DrawWC( Poly->Point[i].X, Poly->Point[i].Y );
          }
        }
        break;

   case WMF_SetPolyFillMode:
        {
           static warn=0;
           if (warn==0) {
              DBGREC;
              warn = 1;
              fprintf(stderr,"Using default fill mode\n");
           }
        }
        break;

   case WMF_SetWindowExt:
        memcpy( &WindowExt, &Rec->Data[0], sizeof(WindowExt));
        InitVirtualWindow( &PlaceHdr );
        OpenAmigaWindow();
        break;

   case WMF_SetWindowOrg:
        memcpy( &WindowOrg, &Rec->Data[0], sizeof(WindowOrg));
        break;

   case WMF_SetROP2:
        SetDrawingMode( Rec->Data[0] ); 
        break;

   case WMF_Null:
        break;

   default:
        DBGREC;
        fprintf(stderr,"%s: Unknown function (%x)\n", filename, Rec->Func);
        break;
   }
   return;
}

MyDeepestApologies( USHORT Func )
{
   DBGREC;
   fprintf(stderr,"WARNING: %s unimplemented and is being ignored!\n",
                   wmf_func_name( Func, 0 ));
}

CleanExit(int x)
{
   CleanAmiga();
   exit(x);
}

/* Lookup values for metafile functions. */
struct function_code {
   unsigned short opcode;
   char *name, found; 
} wmf_function[] = {
      {0x000,"Null",0},
      {0x01e,"SaveDC",0},
      {0x035,"RealizePalette",0},
      {0x037,"SetPaletteEntries",0},
      {0x0f7,"CreatePalette",0},
      {0x102,"SetBkMode",0},
      {0x103,"SetMapMode",0},
      {0x104,"SetROP2",0},
      {0x106,"SetPolyFillMode",0},
      {0x107,"SetStretchBltMode",0},
      {0x108,"SetTextCharExtra",0},
      {0x127,"RestoreDC",0},
      {0x12c,"SelectClipRgn",0},
      {0x12d,"SelectObject",0},
      {0x12e,"SetTextAlign",0},
      {0x139,"ResizePalette",0},
      {0x142,"CreatePatternBrush3",0},
      {0x1f0,"DeleteObject",0},
      {0x1f9,"CreatePatternBrush",0},
      {0x201,"SetBkColor",0},
      {0x209,"SetTextColor",0},
      {0x20a,"SetTextJustification",0},
      {0x20b,"SetWindowOrg",0},
      {0x20c,"SetWindowExt",0},
      {0x20d,"SetViewportOrg",0},
      {0x20e,"SetViewportExt",0},
      {0x20f,"OffsetWindowOrg",0},
      {0x211,"OffsetViewportOrg",0},
      {0x213,"LineTo",0},
      {0x214,"MoveTo",0},
      {0x220,"OffsetClipRgn",0},
      {0x231,"SetMapperFlags",0},
      {0x234,"SelectPalette",0},
      {0x2fa,"CreatePenIndirect",0},
      {0x2fb,"CreateFontIndirect",0},
      {0x2fc,"CreateBrushIndirect",0},
      {0x324,"Polygon",0},
      {0x325,"Polyline",0},
      {0x400,"ScaleWindowExt",0},
      {0x412,"ScaleViewportExt",0},
      {0x415,"ExcludeClipRect",0},
      {0x416,"InsertClipRect",0},
      {0x418,"Ellipse",0},
      {0x419,"FloodFill",0},
      {0x41b,"Rectangle",0},
      {0x41f,"SetPixel",0},
      {0x436,"AnimatePalette",0},
      {0x521,"TextOut",0},
      {0x538,"PolyPolygon",0},
      {0x61c,"RoundRect",0},
      {0x61d,"PatBlt",0},
      {0x626,"Escape",0},
      {0x62f,"DrawText",0},
      {0x6ff,"CreateRegion",0},
      {0x817,"Arc",0},
      {0x81a,"Pie",0},
      {0x830,"Chord",0},
      {0x922,"BitBlt",0},
      {0x940,"BitBlt3",0},
      {0xa32,"ExtTextOut",0},
      {0xb23,"StretchBlt",0},
      {0xb41,"StretchBlt3",0},
      {0xd33,"SetDIBitsToDevice",0},
      {0xf43,"StretchDIBits",0},
      {0xffff,"Unknown Function",0} /* This must be last */
};

char *wmf_func_name( unsigned short func, unsigned char dup )
{
   unsigned i;

   /* Last function will be "Unknown" if we got through the loop
   /* without a find. */
   for(i=0; i<sizeof(wmf_function)/sizeof(wmf_function[0]); i++) {
      if (wmf_function[i].opcode >= func) break;
   }
   if ((wmf_function[i].opcode == func) && ! (wmf_function[i].found) ) {
      if (dup) wmf_function[i].found = 1;
      return( wmf_function[i].name );
   }
   else if (wmf_function[i].found)
     return( NULL );
   else
     return( wmf_function[i].name ); /* Unknown function. */
}

SelectObject( unsigned short idx )
{
   /* NOTANOBJ, BRUSH, FONT, PALETTE, PATTERNBRUSH, PATTERNBRUSH3, PEN */
   switch ( Handle[idx].Type ) {

   case PEN:
      SetPenStyleAmiga( Handle[idx].Data );
      break;

   case BRUSH:
      SetBrushStyleAmiga( Handle[idx].Data );
      break;

   case FONT:
      {
         static warn=0;
         if (!(warn)) {
            warn=1;
            DBGREC;
            fprintf(stderr,"Font objects not selectable\n"); 
         }
      }
      break;

   case PALETTE:
      {
         static warn=0;
         if (!(warn)) {
            warn=1;
            DBGREC;
            fprintf(stderr,"Palette objects not selectable\n"); 
         }
      }
      break;

   case PATTERNBRUSH:
   case PATTERNBRUSH3:
      {
         static warn=0;
         if (!(warn)) {
            warn=1;
            DBGREC;
            fprintf(stderr,"Pattern brush objects not selectable\n"); 
         }
      }
      break;

   default:
      DBGREC;
      fprintf(stderr,"Unimplemented object selected %ld\n",Handle[idx].Type); 
      break;
   };
}

unsigned CreateHandle( wmf_objects Type, struct rec_tmpl *Rec )
{
   unsigned i;

   /* Find free slot. */
   for (i=0; i<FileHdr.numHandles; i++) {
      if (Handle[i].Type == NOTANOBJ) break;
   };

   if (i>=FileHdr.numHandles) {
      fprintf(stderr,"Corrupt WMF file!  Bad handle.\n");
      CleanExit(1);
   };
      
   Handle[i].Type = Type;
   Handle[i].Data = (USHORT *) malloc( Rec->Size );
   ASSERT(Handle[i].Data);
   memcpy( &Handle[i].Data[0], &Rec->Data[0], Rec->Size );
   return i;
}
