#ifndef _EXPLIB_H_
#define _EXPLIB_H_ 80

/*
  1108941413 <- last update (ddmmyyhh'')
     _____________________________________________________________
    /                                                             \ 
    | Header file:  EXPANSION CLASS LIBRARY
    \_____________________________________________________________/

    Description:    Header file containing macros, functions,
                    structures etc. that can easily be re-used
                    in C++ programs.

    Notes:          This source is 100% public domain and might be
                    copied, modified and used by everyone as he likes it.

    Contents:

        RootBase :  this class holds basic things like
                    - log file name & filehandle
                    - debug switches
                    - memory surveillance support class
                    - log support functions (perr,pmsg,dmsg)
                    - program counter tracking using the _cp_ macro

    KNOWN PROBLEMS:

        Float Tracking: if any checkpoint is reached BEFORE RootBase
                        is constructed, the logidx is undefined, which
                        may(!) lead to illegal writes into mem.
                        Presumed probability for this is 1:100

        Mem Tracking:   same problem: if 'new'/'delete' is tracked BEFORE
                        RootBase construction, list of memblocks isn't
                        initialized.

    Terminology:

        _cp_    simple checkpoint macro
        _       simple checkpoint macro
        _cpb_   big    checkpoint with 4 user parms

    NOTE!!!

        If you change the RootBase, RECOMPILE ALL sources which
        use the expansion library!

    Developers: ID: Name:
    =========== --- -----
                JTH Jürgen Thumm

  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 930417 0.50000 JTH created ANSI C version of this
 930421 0.60    JTH FreeBTree now also working
 930501 0.61    JTH added 'logmsg'; logfile now opened/closed everytime
                    something's added to it; logmodes "wp"/"ap" added;
                    added NextCLOpt, NextCLParm
 930504 0.62    JTH created the 'logging function triplet': log functions
                    are now also non-explib compilable

 930814 0.70    JTH upgraded for use under C++; encapsulated anything
                    into the 'RootBase' class.
 930825 0.80        now supports surveillance of any standard 'new' and
                    'delete' operator call. detailed list of forgotten
                    memory blocks will be shown at program end if
                    explib_root.Prolog() is called at program start
                    with MEMTRACK option set.
 940202             errstr()
 940205             intf datatype created
 940315 0.85        MemTracker: illegal pre/post user memblock write hit
                    surveillance, i.e. crashing array boundaries will
                    now be detected on 'delete'.
                    TRACK_PC compile switch, _cp_ macro.
 940316             FloatControl class, checkpoint logging in mem,
                    MemHook for post mortem retrieve of program data,
                    ModInit() macro for integrity checks at module start
 940317             _cpb_ optional big checkpoints with 4 user ulong parms
 940320 1.08        FloatTrack: INDEXPOW, INDEXMASK created. Now, in the
                    potential case that a checkpoint is called BEFORE the
                    RootBase was initialized, logidx can no longer be out
                    of bounds 'cause it's masked now with INDEXMASK.
                    RootBase.valid flag: set after construction of Root
 940402 1.10        MemTracker.TestAdr(x)
 940811 1.11        FloatControl::dumplastpcs

 940905 1.15        PORTED TO WINDOWS 3.1 :
                    - changed all 'const' members of classes to '#define'
                    - global delete operator now without size parm
                    - RootBase::logmsg,err,msg etc. now static&return int
 941025 1.16            MemControl::allocated() added
 941123 1.17        replaced 'valid' flag by more reliable alive().
 941130 1.18        if NO_MEMTRACK defined, testadr() is compiled as (0).
 940421 1.19        added optional NOLOG mode.
*/

#   define  EXPLIB_VERSION  119     // current version (x.xx) * 100

#   ifndef  _EXPDEF_H_
#   include "expdef.h"
#   endif

//  _____________________________________________________________
// /                                                             \ 
// | memory handling & data type adaption
// |
// | do NOT include any SYSTEM includes (e.g. stdio.h) AFTER this!!!
// \_____________________________________________________________/

#ifdef  WIN16

#   define malloc(x)        farmalloc(x)
#   define calloc(x1,x2)    farcalloc(x1,x2)
#   define free(x)          farfree(x)

typedef unsigned long lsize_t;

#   define size_t lsize_t

#endif

// ---------------------------------------------------------------

#ifdef  AMIGA
#   include <pragma/dos_lib.h>
#   include <pragma/exec_lib.h>
#   include <pragma/graphics_lib.h>
#   include <pragma/intuition_lib.h>
#endif

#   include "list.h"

#ifdef  WIN16
#   ifndef __WINDOWS_H
#       include <windows.h>
#   endif
#endif

//  ---------------------------------------
//  --- type adaption
//  ---------------------------------------

typedef int sysint;     // int with system-dependent size

#ifdef  WIN16

#   define  int long    // all 'normally' used ints long in WIN16 sources

#endif

//  ===================================================================
//  ===                 ROOT BASE
//  ===================================================================

class MemBlock : public ListNode {

    friend class MemControl;

        ulong   calcsum();  // calc checksum value

    public:

        void    *adr;
        size_t  size;
        ulong   seqno;
        char*   src;    // source file name
        int     line;   // line in the source file

        void    setsum();   // update checksum entry
        bool    testsum();  // returns 1 if csum is invalid

        ulong   getsum()    {return csum;}

    private:

        ulong   csum;   // checksum over whole instance
        // this MUST be the last member of the class!

        };

class MemControl {

            size_t  ovalloc;    // overall allocated bytes
            long    seqno;      // allocation sequence number

            ListHead ls;    // list of MemBlocks

            // the following stuff is very(!) private, but global
            // new/delete operators must use it, therefore the
          public:

            // support functions for illegal write hit checks:
            void    SetPrePost(MemBlock* mb, size_t pre, size_t post);
            rcode   TestPrePost(MemBlock* mb, size_t pre, size_t post);

            // list adress, size etc. of a MemBlock using 'perr':
            void    LogBlockErr(char* errstr, MemBlock* mb);

          public:

            bool    active;     // if 1, supervise new/delete

            ulong   newcnt;     // how often was 'new' called
            ulong   delcnt;     // how often was 'delete' called

            MemControl()    {ovalloc=0; newcnt=delcnt=seqno=0;}

            MemBlock *AddMemBlock(void *adr, size_t size,char* src,int line);
            MemBlock *FindMemBlock(void *adr);
            void    RemMemBlock(MemBlock *mb);

            void    report(char *sourcename=0, int sourceline=-1);

            bool    TestAdr(void* x, char* src, int line);
            // test for a non-zero adress x if it's valid, i.e. if it's
            // in the memory space alloc'ed by 'new'

            // the following performs an integrity test over
            // the explib's internal memory list.
            // currently, it terminates the program if it finds
            // inconsistencies, but it may also just return
            // non-OK value in that case.

            rcode   checkup();  // scan whole memory list

            ulong   allocated() {   return  ovalloc;    }
            // total number of bytes allocated

            };

class FloatControl {

         public:

#           define  ENTRYPOW    7               // 2 ^ 7 == 128 log entries
#           define  ENTRIES     (1 << ENTRYPOW)
#           define  INDEXMASK   (ENTRIES - 1)   // valid index mask
            // \ after every increment, logidx is masked with this,
            //   so if it reaches ENTRIES, it's re-set to 0 and thus cycles

            struct  PCEntry {

                char*   source;     // name of source code

                ulong   line;       // line of PC position in source
                // \ may have additional infos coded in high 4 bits
                //   for private purposes

                long    seqno;      // sequential number
                // \ -1 means entry is empty

                ulong   parm[4];    // 4 optional user parameters

                PCEntry()   {   seqno=-1;   }

                }
                pclog[ENTRIES];

            ushort  logidx; // current index in pclog
            long    seqcnt; // sequential counter

         public:

            FloatControl()  {   logidx=0;   seqcnt=0;   }

            // writing a program counter entry to the log:

            void    logpc(char* source, int line);

            // the same, but with upto 4 user data parameters
            // (this eats performance!)

            void    logpcbig(char* src, int line,
                        ulong parm0=0, ulong parm1=0,
                        ulong parm2=0, ulong parm3=0);

            // if perr() is called, it will call this to make clear
            // what pc's were lately passed before the error happened

            void    dumplastpcs();
    };

#ifdef  WIN16

// ------------------------------------------------------------------
// ---  Windows only: base control class of an application's instance   
// ------------------------------------------------------------------

class InstanceBase {

    public:

        char    appname[20];    // the application's name

        HINSTANCE   inst;       // on which instance is the code running
        int     instcnt;        // instance counter

        HWND    mainwin;        // main window of this instance

        sysint  InitFirstInst(HINSTANCE, HINSTANCE, sysint);
        
    };

#endif

//|| RootBase 

// message macros: will work only with an activated RootBase

#   define  pmsg explib_root.msg
#   define  perr explib_root.err
#   define  dmsg explib_root.deb

    //  The 'RootBase' class holds global variables needed by
    //  some explib classes.

    class RootBase {

      public:

        // ---- post mortem memory scan hook: ----
        // This MUST be the very first member of RootBase!!! [see [x]]

        struct  MemHook {

            // the following two tags combined define a valid hook:

            uchar   id[2*4];    // J T 9 4 X L R B
            void*   MatchTag;   // points  to itself

            RootBase* root;     // pointer to RootBase

            MemHook()
            {
                root        = (RootBase*)this;  //  [x]
                strncpy(id, "JT94XLRB", 8);
                MatchTag    = &MatchTag;    // now it's valid
            }

            ~MemHook()
            {
                id[0]       = '\0'; // de-validate hook
                MatchTag    = 0;    // of properly terminated program
            }

            }   memhook;

        ushort  private_vrsn;   // for internal use only

        ulong   validmatch;     // contains own adress after init

        bool    alive()         // returns non-zero if root initialized

                {   return (validmatch == (ulong)&validmatch);  }

        // ---- Root version and size: ----

        // if size of Root or offsets of members change,
        // anything should be recompiled.

        ushort  version();  // of this RootBase
        size_t  size();     // of this RootBase

        MemControl      MemTracker;

        // ---- program float tracking for post-mortem analysis: ----

        FloatControl    FloatTracker;

        enum RootModes
        {
            nil      = 0,
            DEBUG    = 1,   // all 'dmsg' statements create logfile output
            MEMTRACK = 2,   // supervise 'new'/'delete' operator calls
                // and create report on forgotten memory at program end
            NOLOG    = 4    // total disabling of any log output

        } mode;

        ushort  deblev; // debug level; the higher the level,
        // the more debug information is logged. By default,
        // any dmsg() is logged if mode has DEBUG bit set.

        int argc;
        char **argv;

        char **opt;     /* options from shell (without parms)       */
        /* \ e.g. if option format "-option" selected, and there    */
        /*   was an option "-x" given from the shell, one of these  */
        /*   ptrs points to "x", i.e. without "-"   */
        /* \ allocated by SplitOptParms(), freed by FreeOptParms()  */

        char **parm;    /* parameters from shell (without options)  */
        /* \ allocated by SplitOptParms(), freed by FreeOptParms()  */

        FILE *logfile;

#       define PROGNAME_MAX 40
        char progname[PROGNAME_MAX+2];  // copy of argv[0] from main

        char *logname;  // log file name

        bool logempty;  // log file empty?

        // ------ methods --------

        static void logmsg(char*,...);

        RootBase();
        ~RootBase();

        void Prolog(
            int     argc,       // from main()
            char    *argv[],    // from main()
            char    *logname,   // log file name (""  = no log)
            int     modes=0     // DEBUG(1),MEMTRACK(2)
           );

        void Epilog();

        void setmode(int newmode)   {mode = (enum RootModes)newmode;}

        // Shell paramter parsing support:

        int HelpRequest();      // returns 1 if user wants help text

        enum OptionFormats {
            DASH=1,     // support "-opt" options
            SLASH=2,    // support "/opt" options
            KEYOPT=4    // support "opt x1 x2 x3" opts
            };

        rcode SplitOptParms(enum OptionFormats supoptfms);

        char* NextOption();

        char* NextParm();

        // log messages, errors, debug information:

        static sysint err(const char *str, ...); // print error
        static sysint msg(const char *str, ...); // print message
        static sysint deb(const char *str, ...); // debug message

        // create hexdump of a memory block:

        static void hexdump(

            void    *strptr,        // block adress
             size_t strsize,        // block size
            sysint(*prtfunc)(const char FAR *,...)=printf, // output function
            int     lterm = 0       // add linefeeds at every line (0/1) ?

                );

#       ifdef WIN16
        InstanceBase    ibase;      // this instance's global control
#       endif

        };

#   ifndef  EXPLIB_NOROOT
extern RootBase explib_root;    // the global RootBase instance
#   endif

long filesize(const char *filename);

const char* errstr(rcode r);    // returns error text

#ifndef NO_MEMTRACK

    //   _____________________________________________________________
    //  /                                                             \ 
    //  | memory allocation surveillance:                             |
    //  \_____________________________________________________________/

void *operator new(size_t size);
void *operator new(size_t size, char *src, int line);
#ifdef  AMIGA
void operator delete(void *x, size_t dummy);
#else
void operator delete(void *x);
#endif

    //  let every standard 'new' call become a new call with information
    //  about the source and the line in that source where it happened.
    //
    //  NOTE: if you define your own 'new' operator, this will cause trouble,
    //        so you first must '#undef new' at the beginning of the code
    //        section in which you use your own 'new'.

#   define  xxxtmssm01  new(__FILE__,__LINE__)
#   define  new         xxxtmssm01

    //  this macro returns non-zero if adress x does NOT belong to
    //  user-allocated memory, or if memblock x is faulty in other ways

#   define  testadr(x)  explib_root.MemTracker.TestAdr(x,__FILE__,__LINE__)

#else

#   define  testadr(x)  (0)

#endif

//   _____________________________________________________________
//  /                                                             \ 
//  | program counter tracking:                                   |
//  \_____________________________________________________________/

//  wherever a checkpoint _cp_ is set,
//  the current PC will be shown if compiled with TRACK_PC

#   ifdef   TRACK_PC
#define _cp_    explib_root.FloatTracker.logpc(__FILE__,__LINE__);
#define _cpb_(p0,p1,p2,p3)  \
    explib_root.FloatTracker.logpcbig(__FILE__,__LINE__, \
                    p0,p1,p2,p3);
#   else
#define _cp_    // if not selected, remove all _cp_'s from code
#define _cpb_(p0,p1,p2,p3)  // and so the big cp's
#   endif

#define _   _cp_    // shortcut for _cp_  : _
#define _p  _cpb_   // shortcut for _cpb_ : _p

//   _____________________________________________________________
//  /                                                             \ 
//  | Module initial checks support:
//  \_____________________________________________________________/

//  If placed at the beginning of a module (.cpp), this macro
//  checks if the module was compiled with the latest 'explib.h',
//  and if not, it printf's an error message (perr cannot be used here)

#define ModInit(name)   \
    \
    struct name { name() {_ \
        \
        if(   explib_root.size() != sizeof(RootBase)    \
           || explib_root.version() != EXPLIB_VERSION)  \
        printf("%s : INSUFFICIENT COMPILATION\n",__FILE__);\
        \
        } } name

#   endif   // not already included
