/*
 * lib_util - calc library utility routines
 *
 * Copyright (C) 1999-2006,2021-2023  Landon Curt Noll
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
 * Public License for more details.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Under source code control:   1997/04/19 21:38:30
 * File existed as early as:    1997
 *
 * chongo <was here> /\oo/\     http://www.isthe.com/chongo/
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */

/*
 * These routines are here to support users of libcalc.a.  These routines
 * are not directly used by calc itself, however.
 */


#include "zmath.h"
#include "alloc.h"
#include "lib_util.h"


#include "errtbl.h"
#include "banned.h"     /* include after system header <> includes */


/*
 * lowhex2bin - quick low order ASCII hex to binary conversion
 *
 * We just use mod 16 for non-hex ASCII chars.  We use just mod 128
 * for non-ASCII to ASCII conversion.
 *
 *   | 00 nul | 01 soh | 02 stx | 03 etx | 04 eot | 05 enq | 06 ack | 07 bel |
 *   | 08 bs  | 09 ht  | 0a nl  | 0b vt  | 0c np  | 0d cr  | 0e so  | 0f si  |
 *   | 10 dle | 11 dc1 | 12 dc2 | 13 dc3 | 14 dc4 | 15 nak | 16 syn | 17 etb |
 *   | 18 can | 19 em  | 1a sub | 1b esc | 1c fs  | 1d gs  | 1e rs  | 1f us  |
 *   | 20 sp  | 21 !   | 22 "   | 23 #   | 24 $   | 25 %   | 26 &   | 27 '   |
 *   | 28 (   | 29 )   | 2a *   | 2b +   | 2c ,   | 2d -   | 2e .   | 2f /   |
 *   | 30 0   | 31 1   | 32 2   | 33 3   | 34 4   | 35 5   | 36 6   | 37 7   |
 *   | 38 8   | 39 9   | 3a :   | 3b ;   | 3c <   | 3d =   | 3e >   | 3f ?   |
 *   | 40 @   | 41 A   | 42 B   | 43 C   | 44 D   | 45 E   | 46 F   | 47 G   |
 *   | 48 H   | 49 I   | 4a J   | 4b K   | 4c L   | 4d M   | 4e N   | 4f O   |
 *   | 50 P   | 51 Q   | 52 R   | 53 S   | 54 T   | 55 U   | 56 V   | 57 W   |
 *   | 58 X   | 59 Y   | 5a Z   | 5b [   | 5c \   | 5d ]   | 5e ^   | 5f _   |
 *   | 60 `   | 61 a   | 62 b   | 63 c   | 64 d   | 65 e   | 66 f   | 67 g   |
 *   | 68 h   | 69 i   | 6a j   | 6b k   | 6c l   | 6d m   | 6e n   | 6f o   |
 *   | 70 p   | 71 q   | 72 r   | 73 s   | 74 t   | 75 u   | 76 v   | 77 w   |
 *   | 78 x   | 79 y   | 7a z   | 7b {   | 7c |   | 7d }   | 7e ~   | 7f del |
 */
int lowhex2bin[256] = {
  /* 0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f  */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* 0 */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* 1 */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* 2 */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* 3 */
    0x0,0xa,0xb,0xc,0xd,0xe,0xf,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* 4 */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* 5 */
    0x0,0xa,0xb,0xc,0xd,0xe,0xf,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* 6 */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* 7 */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* 8 */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* 9 */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* a */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* b */
    0x0,0xa,0xb,0xc,0xd,0xe,0xf,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* c */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* d */
    0x0,0xa,0xb,0xc,0xd,0xe,0xf,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,    /* e */
    0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf     /* f */
};


/*
 * hex2bin - macro to convert two ASCII hex chars into binary value
 *
 * given:
 *      high - high order hex ASCII char
 *      low - low order hex ASCII char
 *
 * returns:
 *      numeric equivalent to 0x{high}{low} as an int
 */
#define hex2bin(high,low) \
        (lowhex2bin[(int)((char)(high))]<<4 | lowhex2bin[((int)(char)(low))])

/*
 * lowbin2hex - quick low order binary conversion to ASCII hex
 */
char lowbin2hex[256] = {
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
};


/*
 * convstr2z - convert a string into a ZVALUE
 *
 * NOTE: No attempt is make to deal with byte order.
 *
 * given:
 *      str     string to convert
 *
 * returns:
 *      ZVALUE
 */
ZVALUE
convstr2z(char *str)
{
        HALF *v;        /* storage for string as HALFs */
        ZVALUE ret;     /* return value */
        size_t len;     /* length in HALFs of our string rounded up */

        /*
         * firewall
         */
        if (str == NULL || *str == '\0') {
                /* NULL or empty strings return 0 */
                return _zero_;
        }

        /*
         * allocate HALF storage
         */
        len = (strlen(str)+sizeof(HALF)-1)/sizeof(HALF);
        v = (HALF *)malloc(len * sizeof(HALF));
        if (v == NULL) {
                math_error("convstr2z bad malloc");
                not_reached();
        }
        v[len-1] = 0;   /* deal with possible partial end of string HALF */

        /*
         * initialize HALF array with string value
         */
        memcpy((void *)v, (void *)str, strlen(str));

        /*
         * setup the rest of the ZVALUE
         */
        ret.v = v;
        ret.len = len;
        ret.sign = 0;
        ztrim(&ret);

        /*
         * return our result
         */
        return ret;
}


/*
 * convhex2z - convert hex string to ZVALUE
 *
 * usage:
 *      str     hex ASCII string with optional leading 0x
 *
 * returns:
 *      ZVALUE
 */
ZVALUE
convhex2z(char *hex)
{
        HALF *v;        /* storage for string as HALFs */
        HALF *hp;       /* HALF pointer */
        char *sp;       /* string pointer */
        ZVALUE ret;     /* return value */
        int len;        /* length in HALFs of our string rounded up */
        size_t slen;    /* hex string length */
        int i;

        /*
         * firewall
         */
        if (hex == NULL || hex[0] == '\0') {
                /* NULL or empty strings return 0 */
                return _zero_;
        }

        /*
         * skip leading 0X or 0x if needed
         */
        if (hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) {
                hex += 2;
        }
        if (hex[0] == '\0') {
                /* just 0X or 0x returns 0 */
                return _zero_;
        }

        /*
         * allocate HALF storage
         */
        slen = strlen(hex);
        len = ((slen*4)+BASEB-1)/BASEB;
        v = (HALF *)malloc(len * sizeof(HALF));
        if (v == NULL) {
                math_error("convhex2z bad malloc");
                not_reached();
        }
        v[len-1] = 0;   /* deal with possible partial end of string HALF */

        /*
         * deal with the upper partial HALF value
         */
        hp = v+len-1;
        sp = hex;
        if ((slen % (BASEB/4)) != 0) {

                /* deal with a odd length hex string first */
                if (slen % 2 == 1) {
                        *hp = hex2bin('0', *sp++);
                        --slen;

                /* even length - top top hex char to process */
                } else {
                        *hp = 0;
                }
                /* slen is even now */

                /* eat two hex chars at a time until the HALF is full */
                for (; (slen % (BASEB/4)) != 0; slen -= 2, sp += 2) {
                        *hp = ((*hp<<8) | hex2bin(sp[0], sp[1]));
                }

                /* move on to the next HALF */
                --hp;
        }
        /* slen is now a multiple of BASEB/4 */

        /*
         * deal with full HALFs
         */
        for (; slen > 0; slen -= (BASEB/4), --hp) {

                /* clear HALF */
                *hp = 0;

                /* eat two hex chars at a time until the HALF is full */
                for (i=0; i < (BASEB/4); i += 2) {
                        *hp = ((*hp<<8) | hex2bin(sp[i], sp[i+1]));
                }
        }

        /*
         * setup the rest of the ZVALUE
         */
        ret.v = v;
        ret.len = len;
        ret.sign = 0;
        ztrim(&ret);

        /*
         * return our result
         */
        return ret;
}


/*
 * convz2hex - convert ZVALUE to hex string
 *
 * We will ignore the sign of the value.
 *
 * usage:
 *      z       ZVALUE
 *
 * returns:
 *      str     hex ASCII malloced string (without a leading 0x)
 */
char *
convz2hex(ZVALUE z)
{
        char *ret;      /* string to return */
        int slen;       /* string length (not counting \0) */
        HALF half;      /* HALF value to convert */
        int seen_nz;    /* 0 => we have not seen a non-zero hex char (yet) */
        char *p;
        int i;
        int j;

        /*
         * firewall
         */
        if (z.v == NULL || ziszero(z)) {
                /* malloc and return "0" */
                ret = (char *)malloc(sizeof("0"));
                if (ret == NULL) {
                        math_error("convz2hex bad malloc of 0 value");
                        not_reached();
                }
                ret[0] = '0';
                ret[1] = '\0';
                return ret;
        }

        /*
         * malloc string storage
         */
        slen = (z.len * BASEB/4);
        ret = (char *)calloc(slen+1+1, sizeof(char));
        if (ret == NULL) {
                math_error("convz2hex bad malloc of string");
                not_reached();
        }

        /*
         * load in hex ASCII chars for each HALF
         *
         * We will not write leading '0' hex chars into the string.
         */
        seen_nz = 0;
        for (p=ret, i=z.len-1; i >= 0; --i) {

                /*
                 * load in ASCII hex by ASCII hex
                 */
                for (half=z.v[i], j=BASEB-4; j >= 0; j-=4) {
                        if (seen_nz) {
                                /* we saw a non-zero, just load the rest */
                                *p++ = lowbin2hex[(half >> j) & 0xff];
                        } else {
                                /* all zeros so far */
                                *p = lowbin2hex[(half >> j) & 0xff];
                                if (*p != '0') {
                                        /* we found our first non-zero char */
                                        ++p;
                                        seen_nz = 1;
                                }
                        }
                }
        }
        if (seen_nz) {
                *p = '\0';
        } else {
                /* saw nothing but 0's, so just return 0 */
                *ret = '0';
                *(ret+1) = '\0';
        }

        /*
         * return the new string
         */
        return ret;
}
