/************************************************************************
 *********           "C" PRINTF exported to AutoLISP.             *******
 **                                                                    **
 *  Created (C.) by Vladimir Nesterovsky <vnestr@netvision.net.il> 1996.*
 *             All Rights Reserved                                      *
 *  You may use this function for any non-commercial purpose            *
 *  provided that you keep this notice complete and unaltered           *
 *        here and in all your derived works.                           *
 *  For any commercial use you MUST first contact me to get a           *
 *  permission (basically speaking some support fee will be required).  *
 *  USE IT AT YOUR OWN RISK. NO WARRANTIES ARE GIVEN WHATSOEVER.        *
 *                                                                      *
 ************************************************************************/

//
// The basic idea is to set up some memory block on heap
// and fill it up with actual arguments like compiler
// does while passing args on stack, then to call _vbprintf() --
// which expects them to be in va_list form, so we need to mimic it.
// (actually, it's about default integer promotion of arguments in C).
//
//
// To be called from LISP as:
// (vpn_printf format values ... )
//      format := format string for "C" printf()
//      val := number | str | [3d]point | nil | T
//      point -> X and Y will be processed as doubles
//      3Dpoint -> X Y and Z will be processed as doubles
//
// All other types encountered will be skipped (ignored).
// For nil or T you must specify "%s" in format string,
// as it'll be converted to string, " NIL " or " T ".
// Other values and format string are passed to C "AS IS"
// without any evaluation or analysis, so you must know
// what you are doing (and what your compiler will).
//
// CAUTION -- if (vpn_printf ... '(1 2) 2.3 4 "s") is called, then a 2-int
//     pair is translated here to 2DPOINT automatically by ADS thus
//     creating 2 doubles, but user may think of it as of 2 integers
//     and specify "%d %d" in format_str -- leading to error.
//
// e-mail me with any questions/comments/suggestions you may have.
//

// Known bugs and limitations:
//
// Can't use %n (of course).
// Also note that all INTs are passed as SHORTs to ADS (i.e. truncated).
//
// Don't forget to use "%f %f %f" for point and "%hd" for short int,
// {  (vpn_printf "%x %hx" -1 -1) returns "ffffffff ffff"  }
// as all shorts are (signed) promoted back to int inside -- so
// '0x00ab0123' is passed as '0x0123' to ADS and then promoted to
// '0x00000123' inside this function, and '0xabcda123' becomes
// '0xa123' and then '0x1111a123'
//
// {{ actually all types used here -- short(converted to int automatically),
// long, double and char* -- are all multiples of sizeof(int)==4 in size,
// so all this rounding macros (see later) are not needed
// except for to be sure it's portable (?..) }}
//
//
// If you find [and fix] any bug, please send me your code.
//

// Bug fixes and enhancements:
//
// 1. fixed signed int promotion of short { (vpn_printf "%d" -1)
//    would return "65535" -- now both "%d" and %hd" return "-1" }
// 2. More codes to be processed (you may add yours more easily).
//    If you do add, send'em to me also, please.
// 3. Added support for MSVC++ compilation         - 02/05/97 -
//

/* Build it (in Watcom C/C++ 10.5) with this makefile:
c_printf.exp : c_printf.obj
 wlink system ads libfile adsstart lib wcads file c_printf

c_printf.obj : c_printf.c
 wcc386 /3s /fpi87 c_printf.c

 Or use it in your MSVC++ project
*/


#include <adslib.h>
#include <stdio.h>
#include <stdarg.h>


// va_list is memory block on stack for retrieving args
// define heap_va_list to set'em up on heap
typedef char *heap_va_list[2]; // use the second to keep starting pointer

//round up to {sizeof(int)=WORD} boundary
#define va_round_up(a) \
    (((a)+(sizeof(int)-1))&~(sizeof(int)-1))

#define va_sizeof(type) \
    va_round_up(sizeof(type))

#define heap_va_start(ap,p) \
    (ap)[1]=(ap)[0]=(char*)(void*)va_round_up((unsigned)p)

#define heap_va_arg(ap,type,val) \
     *(type*)((ap)[0])=val; (ap)[0]+=va_sizeof(type)

#ifndef _WIN32
#define heap_va_end(ap,va) \
    (va)[0]=(ap)[1]; (ap)[1]=(ap)[0]=0
#else
#define heap_va_end(ap,va) \
    (va)=(ap)[1]; (ap)[1]=(ap)[0]=0
#endif

/*  -- to be used as: --
  heap_va_list vaheap;
  va_list vaargs;
  void* p0 = calloc(...);
  heap_va_start (vaheap, p0);
  while(...)
  {
      heap_va_arg(vaheap, int, 3);
      heap_va_arg(vaheap, double, 3.3);
      // etc.
  }
  heap_va_end(vaheap,vaargs);                 // set vaargs and end vaheap
  _vbprintf(sbuf, sbuf_size, format, vaargs); // use vaargs
  ads_retstr( sbuf );
  free(p0);
*/


// Define here translation macros for types to be processed.
// Add more here ...

#define STR_CASE      \
 case RTSTR:          \
 case 0:              \
 case 2:              \
 case 1:              \
 case 1000:           \
 case 1002:           \
 case 8:              \
 case 5:              \
 case 7


#define REAL_CASE     \
 case RTREAL:         \
 case RTANG:          \
 case RTORINT:        \
 case 50:             \
 case 40:             \
 case 1040


#define SHORT_CASE    \
 case RTSHORT:        \
 case 1070


#define LONG_CASE     \
 case RTLONG:         \
 case RTENAME:        \
 case RTPICKS:        \
 case 1071


#define _2DPOINT_CASE \
 case RTPOINT


#define _3DPOINT_CASE \
 case RT3DPOINT:      \
 case 10:             \
 case 11:             \
 case 12:             \
 case 13:             \
 case 210:            \
 case 1010
// add more codes as you wish ...

/*-----------------03-15-96 09:31pm-----------------
 int vpn_printf()
 "C" printf exported to AutoLISP
 arguments expected:
 FORMAT string
 [any arguments]
--------------------------------------------------*/
int vpn_printf()
{
    struct resbuf *rbp, *args;
    char* format;                   // format for _vbprintf()

    void* p0;                       // buffer to mimic va_list on heap
    heap_va_list vaheap;            // heap_va_list to set it up
    unsigned len=4*sizeof(int);     // initial extra space
    va_list vaargs;                 // to be passed to _vbprintf()

                                    // buffer to get the result
    unsigned const sbuf_size = 512; // it's 508 max for ads_retstr()
    static char sbuf[512];          // in my R12 version (undocumented !?).

    ads_retnil();
    args = ads_getargs();

    // analyze args list and extract from it format and arguments
    if ( !args || args->restype != RTSTR )
    {
        ads_printf(" to be called with  FORMAT  [ARGS ...]");
        return RSRSLT;
    }
    format = args->resval.rstring;
    rbp = args;                 // iterator pointer
    while ( rbp = rbp->rbnext ) // find out how much to allocate
    {
        switch ( rbp->restype )
        {
            case RTT:           // NB! to print as string: " T"
            case RTNIL:         //     or " nil "
            STR_CASE:
                len+=va_sizeof(char*);
                break;
            SHORT_CASE:
                len+=va_sizeof(/*short*/int); // promote short to int
                break;
            LONG_CASE:
                len+=va_sizeof(long);
                break;
            REAL_CASE:
                len+=va_sizeof(double);
                break;
            _2DPOINT_CASE:       // 2 doubles
                len+=2*va_sizeof(double);
                break;
            _3DPOINT_CASE:       // 3 doubles
                len+=3*va_sizeof(double);
                break;
            default:
                break;          // ignore all other types( parens etc. )
        }
    }
    // go back
    rbp = args->rbnext;
    if ( !rbp )   // format was the only argument
    {
        ads_retstr(format);
        return RSRSLT;
    }

    // allocate buffer to be filled by arguments
    // and used as va_arg stack  by _vbprintf()
    p0 = calloc( len+256, sizeof(char)); // some more extra space for errors
                                         // to be forgiven at run time
    if ( !p0 )
    {
        ads_fail( "VPN_PRINTF: NO MEMORY" );
        return RSRSLT;
    }


    // fill in buffer with actuall arguments.
    // I need to place them on heap like they would be
    // on stack -- WORD aligned (heap_va_arg macro will do this).
    heap_va_start( vaheap, p0 );

    while ( rbp )
    {
        switch ( rbp->restype )
        {

            case RTT:    // !! must be printed out as STRING: %s
                heap_va_arg(vaheap, char*, " T ");
                break;
            case RTNIL:  // !! must be printed out as STRING: %s
                heap_va_arg(vaheap, char*, " NIL ");
                break;
            STR_CASE:
                heap_va_arg(vaheap, char*, rbp->resval.rstring);
                break;
            REAL_CASE:
                heap_va_arg(vaheap, double, rbp->resval.rreal);
                break;
            SHORT_CASE:  // short->int *signed* promotion [by compiler]
                heap_va_arg(vaheap, /*short*/int, (int)(rbp->resval.rint));
                break;
            LONG_CASE:
                heap_va_arg(vaheap, long, rbp->resval.rlong);
                break;
            _2DPOINT_CASE:
                heap_va_arg(vaheap, double, rbp->resval.rpoint[0]);
                heap_va_arg(vaheap, double, rbp->resval.rpoint[1]);
                break;
            _3DPOINT_CASE:
                heap_va_arg(vaheap, double, rbp->resval.rpoint[0]);
                heap_va_arg(vaheap, double, rbp->resval.rpoint[1]);
                heap_va_arg(vaheap, double, rbp->resval.rpoint[2]);
                break;
            default:
                break; // from switch; continue while.
        }
        rbp=rbp->rbnext;
    }
    // simulated stack is built

    heap_va_end(vaheap,vaargs);


    // HERE ALL THE WORK IS DONE.
    // CHECK YOUR COMPILER DOCUMENTATION FOR ALL THE MEANINGS
    // OF FORMATTING CODES.
#ifndef _WIN32
    _vbprintf( sbuf, sbuf_size, format, vaargs );
#else
    _vsnprintf( sbuf, sbuf_size, format, vaargs );
#endif

    va_end(vaargs);  // like stdargs.h do
    free(p0);        // free this 'simulated stack' buffer

    ads_retstr( sbuf ); // return the result

    // for really large formatted strings, the other trick may be done --
    // store result directly in symbols by ads_putsym().

    return RSRSLT;
}

// That's it!
//



///// Standard ADS interface /////

// Table of exported ADS functions

struct ftblentry
{
    char *name;
    int (*fptr)();
}
exfuncs[] = {

        { "vpn_printf", vpn_printf },

        {   0,  0  },  //THE LAST - THE MUST
        { "\n==================================================",0 },
        { "\n====  (C.) by Vladimir  Nesterovsky, 1996.  ======",0 },
        { "\n========  email: vnestr@netvision.net.il   =======",0 },
        { "\n=============== All rights reserved ==============",0 },
        { "\n==================================================",0 }
    };


int loadfuncs() // LOADFUNCS  --  Define external functions with AutoLISP.
{
    int i;

    for (i = 0; exfuncs[i].name != 0; i++)
    {
    if ( ads_defun( exfuncs[i].name, i) != RTNORM )
        return RTERROR;
    if ( ads_regfunc( exfuncs[i].fptr, i) != RTNORM )
        return RTERROR;
    }

    return RTNORM;
}


void main( int argc, char *argv[])
{
    int rqst;

    short rscode = RSRSLT;            // This is the default result code
    ads_init(argc, argv);             // Initialize the interface
    for ( ;; ) {

        if ((rqst = ads_link(rscode)) < 0) {
            printf( "C_PRINTF: bad status from ads_link() = %d\n", rqst);
            exit(1);
        }

        rscode = RSRSLT;               // Default return value

        // Check for the following cases here
        switch (rqst) {

        case RQXLOAD:
            ads_printf( "\nADS Application C_PRINTF "
                        "(C.) by Vladimir Nesterovsky ..");
            rscode = (loadfuncs()==RTNORM) ? RSRSLT : RSERR;
            if( rscode == RSRSLT)
            {
                static already_loaded = 0;

                if( !already_loaded )
                    ads_printf(". loaded.\n");
                else
                    ads_printf(". reloaded.\n");

                already_loaded = 1;
                ads_printf(
                  "\n=================================================="
                  "\n====  (C.) by Vladimir  Nesterovsky, 1996.  ======"
                  "\n========  email: vnestr@netvision.net.il   ======="
                  "\n=============== All rights reserved =============="
                  "\n==================================================");
            }
            else
                ads_printf(". failed to load.\n");
            break;

        case RQSUBR:
            break;

        case RQXUNLD:
            ads_printf( "ADS Application C_PRINTF "
                        "(C.) by Vladimir Nesterovsky unloaded.\n");
            break;

        case RQSAVE:
            break;

        case RQQUIT:
            break;

        case RQEND:
            break;

        default:
            break;
        }
    }
}