/* HowDif 2.0 for Amiga

   Quick-and-dirty routine written to compare two files. The assumption is
   that they are files known to be different from one another, already. I
   want to know HOW different they are from one another. Essentially, this
   program just reads two files, notes the number of bytes read, and notes
   how many bytes do not match. (This is a strict byte-for-byte comparison.)

   Written by James Jacobs and Ward Shrake
   Last update: October 13, 2002 */

#include <exec/types.h>
#include <dos/dos.h>

#include <stdlib.h>          /* EXIT_SUCCESS, EXIT_FAILURE */

typedef signed char  FLAG;   /* 8-bit signed quantity (replaces BOOL) */
typedef signed char  SBYTE;  /* 8-bit signed quantity (replaces Amiga BYTE) */
typedef signed short SWORD;  /* 16-bit signed quantity (replaces Amiga WORD) */
typedef signed long  SLONG;  /* 32-bit signed quantity (same as LONG) */
#define elif         else if
#define AGLOBAL      ;       /* global (project-scope) */
#define MODULE       static  /* external static (file-scope) */
#define PERSIST      static  /* internal static (function-scope) */
#define AUTO         auto    /* automatic (function-scope) */

#define BYTESPERLINE 14

MODULE STRPTR                MemoryPtr[3]  = {NULL, NULL};
MODULE struct FileInfoBlock* FIBPtr        = NULL;
MODULE BPTR                  FileHandle    = NULL;
MODULE TEXT                  output[1024 + 1],
                             ansi1[20],
                             ansi2[20];
MODULE FLAG                  inverted      = FALSE;
MODULE ULONG                 size[3];

MODULE void cleanexit(SLONG rc);
MODULE void hexalize(UBYTE data);
MODULE void invert(void);
MODULE void deinvert(void);

int main(int argc, char** argv)
{   AUTO    ULONG  i, j,
                   length,
                   percent1, percent2,
                   br,
                   bw = 0;     // number of total mismatches found
    AUTO    FLAG   compare;
    PERSIST TEXT   arcadia[] = " /\\abcdefghijklm0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.,+$nopqrstu";
    AUTO    STRPTR primary, secondary;

    if (0)
    {   Printf("$VER: HowDif 2.0 for Amiga (13.10.2002)");
    }

    ansi1[0] = 0x9b;
    ansi1[1] = 0;
    strcat(ansi1, "0;32;40;>0m"); // means `go white on grey'

    ansi2[0] = 0x9b;
    ansi2[1] = 0;
    strcat(ansi2, "0;39;39;>0m"); // means `go back to vanilla'

    Printf("HowDif 2.0 for Amiga\n\n");

    if (argc == 2)
    {   compare = FALSE;
    } elif (argc == 3)
    {   compare = TRUE;
    } else
    {   Printf("Usage: %s <file1> <file2>\n", argv[0]);
        cleanexit(EXIT_FAILURE);
    }

    for (i = 0; i <= 1; i++)
    {   if (i == 0 || compare)
        {   if (!(FileHandle = (BPTR) Lock(argv[i + 1], ACCESS_READ)))
            {   Printf("Can't lock %s!\n", argv[i + 1]);
                exit(EXIT_FAILURE);
            }
            if (!(FIBPtr = AllocDosObject(DOS_FIB, NULL)))
            {   UnLock(FileHandle);
                Printf("Can't allocate DOS object!\n");
                exit(EXIT_FAILURE);
            }
            if (!(Examine(FileHandle, FIBPtr)))
            {   FreeDosObject(DOS_FIB, FIBPtr);
                UnLock(FileHandle);
                Printf("Can't examine %s!\n", argv[i + 1]);
            }
            size[i] = FIBPtr->fib_Size;
            FreeDosObject(DOS_FIB, FIBPtr);
            FIBPtr = NULL;
            UnLock(FileHandle);
            FileHandle = NULL;
            if (!(MemoryPtr[i] = AllocMem(size[i], NULL)))
            {   Printf("Out of memory!\n");
                cleanexit(EXIT_FAILURE);
            }
            if (!(FileHandle = (BPTR) Open(argv[i + 1], MODE_OLDFILE)))
            {   Printf("Can't open %s for reading!\n", argv[i + 1]);
                cleanexit(EXIT_FAILURE);
            }
            if (Read(FileHandle, MemoryPtr[i], size[i]) != size[i])
            {   Printf("Can't read from %s!\n", argv[i + 1]);
                cleanexit(EXIT_FAILURE);
            }
            Close(FileHandle);
            FileHandle = NULL;
    }   }

    if (compare)
    {   if (size[0] > size[1])
        {   size[2] = size[0];
            size[0] = size[1];
            size[1] = size[2];
            MemoryPtr[2] = MemoryPtr[0];
            MemoryPtr[0] = MemoryPtr[1];
            MemoryPtr[1] = MemoryPtr[2];
            primary   = argv[2];
            secondary = argv[1];
        } else
        {   primary   = argv[1];
            secondary = argv[2];
        }
        Printf("  Primary file is %s (%ld bytes).\n",     primary, size[0]);
        Printf("Secondary file is %s (%ld bytes).\n\n", secondary, size[1]);

        for (br = 0; br < size[0]; br++) // do this loop as long as the file has not ended
        {   if (MemoryPtr[0][br] != MemoryPtr[1][br]) // compare the two single bytes
            {   bw++;    // add one to the counter showing # of mismatches
    }   }   }
    else
    {   Printf("File is %s (%ld bytes).\n\n", argv[1], size[0]);
    }

    if (size[0] > 65535)
    {   Printf("  ");
    }
    Printf("       0011 2233 4455 6677 8899 AABB CCDD 0123456789ABCD 0123456789ABCD\n");
    if (size[0] > 65535)
    {   Printf("  ");
    }
    Printf("       ---------------------------------- -------------- --------------\n");

    for (i = 0; i < size[0]; i += BYTESPERLINE)
    {   output[0] = 0;
        deinvert();
        strcat(output, "$");

        if (size[0] > 65535)
        {   hexalize(i / 16777216); // do 1st byte
            j = i % 16777216;       // remove 1st byte
            hexalize(j / 65536);    // do 2nd byte
            j %= 65536;             // remove 2nd byte
        } else
        {   j = i;
        }
        hexalize(j / 256);      // do 3rd byte
        hexalize(j % 256);      // do 4th byte

        strcat(output, ": ");
        for (j = 0; j < BYTESPERLINE; j++)
        {   if (size[0] > i + j)
            {   if (compare)
                {   if (MemoryPtr[0][i + j] != MemoryPtr[1][i + j])
                    {   deinvert();
                    } else
                    {   invert();
                }   }
                hexalize(MemoryPtr[0][i + j]);
            } else
            {   deinvert();
                strcat(output, "##");
            }
            if (j % 2)
            {   deinvert();
                strcat(output, " ");
        }   }

        for (j = 0; j < BYTESPERLINE; j++)
        {   if (size[0] > i + j)
            {   if (compare)
                {   if (MemoryPtr[0][i + j] != MemoryPtr[1][i + j])
                    {   deinvert();
                    } else
                    {   invert();
                }   }
                if
                (    MemoryPtr[0][i + j] < ' '
                 || (MemoryPtr[0][i + j] >= 0x80 && MemoryPtr[0][i + j] <= 0x9F)
                ) // if an unprintable character
                {   strcat(output, ".");
                } else
                {   length = strlen(output);
                    output[length] = MemoryPtr[0][i + j];
                    output[length + 1] = 0;
            }   }
            else
            {   deinvert();
                strcat(output, "#");
        }   }

        deinvert();
        strcat(output, " ");

        for (j = 0; j < BYTESPERLINE; j++)
        {   if (size[0] > i + j)
            {   if (compare)
                {   if (MemoryPtr[0][i + j] != MemoryPtr[1][i + j])
                    {   deinvert();
                    } else
                    {   invert();
                }   }
                if (MemoryPtr[0][i + j] < 64)
                {   length = strlen(output);
                    output[length] = arcadia[MemoryPtr[0][i + j]];
                    output[length + 1] = 0;
                } else strcat(output, ".");
            } else
            {   deinvert();
                strcat(output, "#");
        }   }

        deinvert();
        Printf("%s\n", output);
    }

    // when the program is all done comparing, print a summary report onscreen

    Printf("\nTotal bytes read: %ld\n", size[0]);

    if (compare)
    {   Printf("Mismatches found: %ld\n", bw);

        /* calculate percentage:
            percent1 is the percentage magnified 1 million times.
            eg. 1 million = 1%, 100 million = 100%, 56,723,432 = 56.723432%.
            By scaling in this way we preserve precision whilst avoiding
            the need for true floating point mathematics.
            percent2 is the actual integer percentage. */
    
        if (!bw)
        {   percent2 = 0;
        } else
        {   percent1 = (100000000L / size[0]) * bw;
            percent2 = percent1 / 1000000L;
        }
        Printf("Difference:       %ld%%\n", percent2);
    }

    cleanexit(EXIT_SUCCESS); // end of the program. go back to DOS
}

MODULE void invert(void)
{   if (!inverted)
    {   strcat(output, ansi1);
        inverted = TRUE;
}   }
MODULE void deinvert(void)
{   if (inverted)
    {   strcat(output, ansi2);
        inverted = FALSE;
}   }

MODULE void cleanexit(SLONG rc)
{   if (FIBPtr)
    {   FreeDosObject(DOS_FIB, FIBPtr);
    }
    if (FileHandle)
    {   Close(FileHandle);
    }
    if (MemoryPtr[0])
    {   FreeMem(MemoryPtr[0], size[0]);
    }
    if (MemoryPtr[1])
    {   FreeMem(MemoryPtr[1], size[1]);
    }
    exit(EXIT_SUCCESS);
}

MODULE void hexalize(UBYTE data)
{   TEXT tempstring[3];

    // Converts an unsigned byte into a hexadecimal string

    // do the high byte
    if (data / 16 >= 10)
    {   tempstring[0] = (data / 16) - 10 + 'A'; // must be done in this order to prevent overflow during calculation of the value
    } else
    {   tempstring[0] = (data / 16) + '0';
    }

    // now the low byte
    if (data % 16 >= 10)
    {   tempstring[1] = (data % 16) - 10 + 'A';
    } else
    {   tempstring[1] = (data % 16) + '0';
    }
    tempstring[2] = 0;
    strcat(output, tempstring);
}
