/*
 * types.h
 *
 * Copyright (c) 2018 Eric Vidal <eric@obarun.org>
 *
 * All rights reserved.
 *
 * This file is part of Obarun. It is subject to the license terms in
 * the LICENSE file found in the top-level directory of this
 * distribution.
 * This file may not be copied, modified, propagated, or distributed
 * except according to the terms contained in the LICENSE file.
 */

#ifndef OB_TYPES_H
#define OB_TYPES_H

#include <sys/types.h>
#include <stddef.h>
#include <stdint.h>
#include <errno.h>

/* ===========================================================================
 * Integer <-> string conversion, self-contained (no skalibs dependency).
 *
 * A single 64-bit engine performs all the work; smaller widths are macros that
 * widen losslessly to 64-bit. The behavior is identical, byte for byte, to the
 * skalibs uint*_fmt / uint*_scan family it replaces.
 *
 * Defined behavior requires base in [2,36]; base < 2 is undefined (as in skalibs).
 *
 * The conversion functions, the macro layout and the buffer-size constants are
 * derived from skalibs (headers uint64.h / uint32.h / uint16.h / types.h /
 * fmtscan.h), https://git.skarnet.org/cgi-bin/cgit.cgi/skalibs/ , under license
 * ISC. All credits go to Laurent Bercot, author of skalibs. Renamed and adapted
 * for oblibs by Eric Vidal <eric@obarun.org> to remove the skalibs dependency.
 * ===========================================================================*/

/* engine: number -> text */

/**
 * @brief Format an unsigned 64-bit integer into base-`base` text.
 * @param s     Destination buffer, or 0 to only compute the length.
 * @param x     Value to format.
 * @param base  Numeric base, in [2,36].
 * @return Number of characters the representation takes (written if s != 0).
 *         The output is NOT null-terminated.
 */
extern size_t u64_fmtbase(char *s, uint64_t x, uint8_t base) ;

/**
 * @brief Like u64_fmtbase, left-padded with '0' to at least `n` characters.
 * @param s     Destination buffer, or 0 to only compute the length.
 * @param x     Value to format.
 * @param n     Minimum field width.
 * @param base  Numeric base, in [2,36].
 * @return max(natural length, n).
 */
extern size_t u64_fmtpad(char *s, uint64_t x, size_t n, uint8_t base) ;

/**
 * @brief Format a signed 64-bit integer (leading '-' when negative).
 * @param s     Destination buffer, or 0 to only compute the length.
 * @param d     Value to format.
 * @param base  Numeric base, in [2,36].
 * @return Number of characters the representation takes.
 */
extern size_t i64_fmtbase(char *s, int64_t d, uint8_t base) ;

/* engine: text -> number */

/**
 * @brief Scan a base-`base` unsigned integer, capped at `max`.
 * @param s     Source text.
 * @param u     Result, written only if at least one digit is consumed.
 * @param base  Numeric base, in [2,36].
 * @param max   Maximum accepted value (scanning stops before overflowing it).
 * @return Number of characters consumed (0 if none).
 */
extern size_t u64_scan_base_max(char const *s, uint64_t *u, uint8_t base, uint64_t max) ;

/**
 * @brief Scan a base-`base` unsigned 64-bit integer (capped at UINT64_MAX).
 * @param s     Source text.
 * @param u     Result, written only if at least one digit is consumed.
 * @param base  Numeric base, in [2,36].
 * @return Number of characters consumed (0 if none).
 */
extern size_t u64_scan_base(char const *s, uint64_t *u, uint8_t base) ;

/**
 * @brief Scan a base-`base` signed integer (optional leading '-' or '+'), capped at `max`.
 * @param s     Source text.
 * @param d     Result, written only if at least one digit is consumed.
 * @param base  Numeric base, in [2,36].
 * @param max   Maximum accepted magnitude for positive values; negatives go down to -(max+1).
 * @return Number of characters consumed (0 if none).
 */
extern size_t i64_scan_base_max(char const *s, int64_t *d, uint8_t base, uint64_t max) ;

/**
 * @brief Scan a base-`base` signed 64-bit integer (range [INT64_MIN, INT64_MAX]).
 * @param s     Source text.
 * @param d     Result, written only if at least one digit is consumed.
 * @param base  Numeric base, in [2,36].
 * @return Number of characters consumed (0 if none).
 */
extern size_t i64_scan_base(char const *s, int64_t *d, uint8_t base) ;

/* digit helpers */

/**
 * @brief Map a digit value to its ASCII character (0->'0' .. 35->'z').
 * @param v Value in [0,35].
 * @return The matching character, or 0 if v >= 36.
 */
extern char fmt_digit(unsigned char v) ;

/**
 * @brief Map an ASCII character to its digit value for the given base.
 * @param c    The character.
 * @param base The base, in [0,36].
 * @return The digit value if valid for the base, otherwise `base` (out of range).
 */
extern unsigned char scan_digit(unsigned char c, unsigned char base) ;

/**
 * @brief Parse a signal specifier into its number. Native replacement for
 * skalibs sig0_scan.
 *
 * Accepts three forms: a signal name ("TERM", case-insensitive), a strict
 * decimal number ("15", the whole string must be the number), or either of
 * these prefixed with "SIG" ("SIGTERM", "SIG15").
 * @param s    Source text.
 * @param sig  Result, written only on success.
 * @return Number of characters consumed (the whole length on success), 0 if
 *         @s is not a valid signal specifier.
 */
extern size_t sig_parse(char const *s, int *sig) ;

/* byte packing: integer <-> fixed-width big/little-endian byte buffer.
 *
 * The byte order in the buffer is fixed (big- or little-endian), independent of
 * the host endianness — the on-disk/wire format stays identical on every target.
 * Done by explicit byte shifting (no host-endianness macro, no sysdeps). Native
 * replacement for skalibs uintN_pack_big / uintN_unpack_big and uint32_pack. */

static inline void u32_pack_big(char *s, uint32_t u)
{
    s[0] = (char)(u >> 24) ; s[1] = (char)(u >> 16) ;
    s[2] = (char)(u >> 8) ;  s[3] = (char)u ;
}

static inline void u32_unpack_big(char const *s, uint32_t *u)
{
    *u = ((uint32_t)(unsigned char)s[0] << 24) | ((uint32_t)(unsigned char)s[1] << 16)
       | ((uint32_t)(unsigned char)s[2] << 8)  |  (uint32_t)(unsigned char)s[3] ;
}

static inline void u64_pack_big(char *s, uint64_t u)
{
    s[0] = (char)(u >> 56) ; s[1] = (char)(u >> 48) ;
    s[2] = (char)(u >> 40) ; s[3] = (char)(u >> 32) ;
    s[4] = (char)(u >> 24) ; s[5] = (char)(u >> 16) ;
    s[6] = (char)(u >> 8) ;  s[7] = (char)u ;
}

static inline void u64_unpack_big(char const *s, uint64_t *u)
{
    *u = ((uint64_t)(unsigned char)s[0] << 56) | ((uint64_t)(unsigned char)s[1] << 48)
       | ((uint64_t)(unsigned char)s[2] << 40) | ((uint64_t)(unsigned char)s[3] << 32)
       | ((uint64_t)(unsigned char)s[4] << 24) | ((uint64_t)(unsigned char)s[5] << 16)
       | ((uint64_t)(unsigned char)s[6] << 8)  |  (uint64_t)(unsigned char)s[7] ;
}

/* little-endian (skalibs uint32_pack / uint32_unpack) */
static inline void u32_pack(char *s, uint32_t u)
{
    s[0] = (char)u ;        s[1] = (char)(u >> 8) ;
    s[2] = (char)(u >> 16) ; s[3] = (char)(u >> 24) ;
}

static inline void u32_unpack(char const *s, uint32_t *u)
{
    *u =  (uint32_t)(unsigned char)s[0]        | ((uint32_t)(unsigned char)s[1] << 8)
       | ((uint32_t)(unsigned char)s[2] << 16) | ((uint32_t)(unsigned char)s[3] << 24) ;
}

/* convenience macros: format in a given base */

#define u64_fmt(s, x)   u64_fmtbase((s), (x), 10)
#define u64_ofmt(s, x)  u64_fmtbase((s), (x), 8)
#define u64_xfmt(s, x)  u64_fmtbase((s), (x), 16)
#define u64_bfmt(s, x)  u64_fmtbase((s), (x), 2)

#define u32_fmt(s, x)   u64_fmtbase((s), (uint64_t)(x), 10)
#define u32_ofmt(s, x)  u64_fmtbase((s), (uint64_t)(x), 8)
#define u32_xfmt(s, x)  u64_fmtbase((s), (uint64_t)(x), 16)
#define u32_bfmt(s, x)  u64_fmtbase((s), (uint64_t)(x), 2)

#define u16_fmt(s, x)   u64_fmtbase((s), (uint64_t)(x), 10)
#define u16_ofmt(s, x)  u64_fmtbase((s), (uint64_t)(x), 8)
#define u16_xfmt(s, x)  u64_fmtbase((s), (uint64_t)(x), 16)
#define u16_bfmt(s, x)  u64_fmtbase((s), (uint64_t)(x), 2)

#define u8_fmt(s, x)    u64_fmtbase((s), (uint64_t)(x), 10)
#define u8_ofmt(s, x)   u64_fmtbase((s), (uint64_t)(x), 8)
#define u8_xfmt(s, x)   u64_fmtbase((s), (uint64_t)(x), 16)
#define u8_bfmt(s, x)   u64_fmtbase((s), (uint64_t)(x), 2)

#define i64_fmt(s, d)   i64_fmtbase((s), (d), 10)
#define i64_ofmt(s, d)  i64_fmtbase((s), (d), 8)
#define i64_xfmt(s, d)  i64_fmtbase((s), (d), 16)
#define i64_bfmt(s, d)  i64_fmtbase((s), (d), 2)

#define i32_fmt(s, d)   i64_fmtbase((s), (int64_t)(d), 10)
#define i32_ofmt(s, d)  i64_fmtbase((s), (int64_t)(d), 8)
#define i32_xfmt(s, d)  i64_fmtbase((s), (int64_t)(d), 16)
#define i32_bfmt(s, d)  i64_fmtbase((s), (int64_t)(d), 2)

#define i16_fmt(s, d)   i64_fmtbase((s), (int64_t)(d), 10)
#define i16_ofmt(s, d)  i64_fmtbase((s), (int64_t)(d), 8)
#define i16_xfmt(s, d)  i64_fmtbase((s), (int64_t)(d), 16)
#define i16_bfmt(s, d)  i64_fmtbase((s), (int64_t)(d), 2)

#define i8_fmt(s, d)    i64_fmtbase((s), (int64_t)(d), 10)
#define i8_ofmt(s, d)   i64_fmtbase((s), (int64_t)(d), 8)
#define i8_xfmt(s, d)   i64_fmtbase((s), (int64_t)(d), 16)
#define i8_bfmt(s, d)   i64_fmtbase((s), (int64_t)(d), 2)

/* convenience macros: scan in a given base (single 64-bit engine) */

#define u64_scan(s, u)   u64_scan_base((s), (u), 10)
#define u64_oscan(s, u)  u64_scan_base((s), (u), 8)
#define u64_xscan(s, u)  u64_scan_base((s), (u), 16)
#define u64_bscan(s, u)  u64_scan_base((s), (u), 2)

#define i64_scan(s, d)   i64_scan_base((s), (d), 10)
#define i64_oscan(s, d)  i64_scan_base((s), (d), 8)
#define i64_xscan(s, d)  i64_scan_base((s), (d), 16)
#define i64_bscan(s, d)  i64_scan_base((s), (d), 2)

/* scan into a narrower width.
 *
 * The 64-bit engine is run capped at the target type's max, so it stops exactly
 * where a native per-size scan would (same bytes consumed, same value — matches
 * skalibs uintN_scan_base) and the final cast is guaranteed lossless. Provided
 * as static inline so the output pointer keeps its real type (no 8-byte write
 * into a smaller object) without exporting an extra symbol; the temporary lives
 * in a register, so there is no resource cost. */

static inline size_t u32_scan_base(char const *s, uint32_t *u, uint8_t base)
{
    uint64_t tmp ;
    size_t n = u64_scan_base_max(s, &tmp, base, UINT32_MAX) ;
    if (n) *u = (uint32_t)tmp ;
    return n ;
}

static inline size_t u16_scan_base(char const *s, uint16_t *u, uint8_t base)
{
    uint64_t tmp ;
    size_t n = u64_scan_base_max(s, &tmp, base, UINT16_MAX) ;
    if (n) *u = (uint16_t)tmp ;
    return n ;
}

static inline size_t u8_scan_base(char const *s, uint8_t *u, uint8_t base)
{
    uint64_t tmp ;
    size_t n = u64_scan_base_max(s, &tmp, base, UINT8_MAX) ;
    if (n) *u = (uint8_t)tmp ;
    return n ;
}

#define u32_scan(s, u)   u32_scan_base((s), (u), 10)
#define u32_oscan(s, u)  u32_scan_base((s), (u), 8)
#define u32_xscan(s, u)  u32_scan_base((s), (u), 16)
#define u32_bscan(s, u)  u32_scan_base((s), (u), 2)

#define u16_scan(s, u)   u16_scan_base((s), (u), 10)
#define u16_oscan(s, u)  u16_scan_base((s), (u), 8)
#define u16_xscan(s, u)  u16_scan_base((s), (u), 16)
#define u16_bscan(s, u)  u16_scan_base((s), (u), 2)

#define u8_scan(s, u)    u8_scan_base((s), (u), 10)
#define u8_oscan(s, u)   u8_scan_base((s), (u), 8)
#define u8_xscan(s, u)   u8_scan_base((s), (u), 16)
#define u8_bscan(s, u)   u8_scan_base((s), (u), 2)

/* strict scan: the WHOLE string must be a valid base-`base` number.
 *
 * Unlike uN_scan (which stops at the first non-digit and reports a partial
 * success), these succeed only when the number is followed by the terminating
 * NUL. Use them to validate a complete token, e.g. an option argument ("3" ok,
 * "3x" rejected). Native replacement for skalibs uintN0_scan / uint0_scan,
 * SCANB0 semantics: on a trailing character return 0 with errno == ERANGE if it
 * is a valid digit that overflowed the type, ERANGE/EINVAL otherwise.
 * @return the consumed length (== the number's length) on success, 0 on failure. */

static inline size_t u64_scan_strict_base(char const *s, uint64_t *u, uint8_t base)
{
    size_t pos = u64_scan_base(s, u, base) ;
    if (!pos) return (errno = EINVAL, 0) ;
    if (!s[pos]) return pos ;
    errno = scan_digit((unsigned char)s[pos], base) < base ? ERANGE : EINVAL ;
    return 0 ;
}

static inline size_t u32_scan_strict_base(char const *s, uint32_t *u, uint8_t base)
{
    size_t pos = u32_scan_base(s, u, base) ;
    if (!pos) return (errno = EINVAL, 0) ;
    if (!s[pos]) return pos ;
    errno = scan_digit((unsigned char)s[pos], base) < base ? ERANGE : EINVAL ;
    return 0 ;
}

static inline size_t u16_scan_strict_base(char const *s, uint16_t *u, uint8_t base)
{
    size_t pos = u16_scan_base(s, u, base) ;
    if (!pos) return (errno = EINVAL, 0) ;
    if (!s[pos]) return pos ;
    errno = scan_digit((unsigned char)s[pos], base) < base ? ERANGE : EINVAL ;
    return 0 ;
}

static inline size_t u8_scan_strict_base(char const *s, uint8_t *u, uint8_t base)
{
    size_t pos = u8_scan_base(s, u, base) ;
    if (!pos) return (errno = EINVAL, 0) ;
    if (!s[pos]) return pos ;
    errno = scan_digit((unsigned char)s[pos], base) < base ? ERANGE : EINVAL ;
    return 0 ;
}

/* decimal convenience; for another base (e.g. octal) use the *_strict_base forms */
#define u64_scan_strict(s, u)  u64_scan_strict_base((s), (u), 10)
#define u32_scan_strict(s, u)  u32_scan_strict_base((s), (u), 10)
#define u16_scan_strict(s, u)  u16_scan_strict_base((s), (u), 10)
#define u8_scan_strict(s, u)   u8_scan_strict_base((s), (u), 10)

/* buffer sizes: maximum characters for a width, base (excludes terminator) */

#define U64_FMT 21
#define U64_OFMT 25
#define U64_XFMT 17
#define U64_BFMT 65

#define U32_FMT 11
#define U32_OFMT 13
#define U32_XFMT 9
#define U32_BFMT 33

#define U16_FMT 6
#define U16_OFMT 7
#define U16_XFMT 5
#define U16_BFMT 17

#define U8_FMT 4
#define U8_OFMT 4
#define U8_XFMT 3
#define U8_BFMT 9

/* signed: one extra char for the leading '-' (I*_FMT == 1 + U*_FMT) */

#define I64_FMT 22
#define I64_OFMT 26
#define I64_XFMT 18
#define I64_BFMT 66

#define I32_FMT 12
#define I32_OFMT 14
#define I32_XFMT 10
#define I32_BFMT 34

#define I16_FMT 7
#define I16_OFMT 8
#define I16_XFMT 6
#define I16_BFMT 18

#define I8_FMT 5
#define I8_OFMT 5
#define I8_XFMT 4
#define I8_BFMT 10

/* ===========================================================================
 * Conversion of the POSIX system types uid_t / gid_t / pid_t <-> string.
 *
 * Static inline so the output pointer keeps its real type (no fixed-size pointer
 * cast as skalibs does — robust to any width) and the temporary lives in a
 * register (no resource cost). uid_t / gid_t are taken as unsigned (true on all
 * real targets); pid_t is signed and scanned within its strict signed range.
 * ===========================================================================*/

/* uid_t : unsigned, capped at the type's max (robust to its width) */
static inline size_t uid_format_base(char *s, uid_t u, uint8_t base)
{
    return u64_fmtbase(s, (uint64_t)u, base) ;
}
static inline size_t uid_parse_base(char const *s, uid_t *u, uint8_t base)
{
    uint64_t t ;
    size_t n = u64_scan_base_max(s, &t, base, (uint64_t)(uid_t)-1) ;
    if (n) *u = (uid_t)t ;
    return n ;
}

/* gid_t : unsigned, capped at the type's max */
static inline size_t gid_format_base(char *s, gid_t g, uint8_t base)
{
    return u64_fmtbase(s, (uint64_t)g, base) ;
}
static inline size_t gid_parse_base(char const *s, gid_t *g, uint8_t base)
{
    uint64_t t ;
    size_t n = u64_scan_base_max(s, &t, base, (uint64_t)(gid_t)-1) ;
    if (n) *g = (gid_t)t ;
    return n ;
}

/* strict variants: the whole string must be the number (skalibs uid0_scan /
 * gid0_scan). @see u32_scan_strict_base for the semantics. */
static inline size_t uid_parse_strict_base(char const *s, uid_t *u, uint8_t base)
{
    size_t pos = uid_parse_base(s, u, base) ;
    if (!pos) return (errno = EINVAL, 0) ;
    if (!s[pos]) return pos ;
    errno = scan_digit((unsigned char)s[pos], base) < base ? ERANGE : EINVAL ;
    return 0 ;
}
static inline size_t gid_parse_strict_base(char const *s, gid_t *g, uint8_t base)
{
    size_t pos = gid_parse_base(s, g, base) ;
    if (!pos) return (errno = EINVAL, 0) ;
    if (!s[pos]) return pos ;
    errno = scan_digit((unsigned char)s[pos], base) < base ? ERANGE : EINVAL ;
    return 0 ;
}

/* pid_t : signed, scanned within the type's strict signed range [PID_MIN, PID_MAX] */
static inline size_t pid_format_base(char *s, pid_t p, uint8_t base)
{
    return i64_fmtbase(s, (int64_t)p, base) ;
}
static inline size_t pid_parse_base(char const *s, pid_t *p, uint8_t base)
{
    int64_t t ;
    size_t n = i64_scan_base_max(s, &t, base, ((uint64_t)1 << (sizeof(pid_t)*8 - 1)) - 1) ;
    if (n) *p = (pid_t)t ;
    return n ;
}

/* decimal convenience; for another base use the *_base forms above */
#define uid_format(s, u) uid_format_base((s), (u), 10)
#define uid_parse(s, u)  uid_parse_base((s), (u), 10)
#define uid_parse_strict(s, u) uid_parse_strict_base((s), (u), 10)
#define gid_format(s, g) gid_format_base((s), (g), 10)
#define gid_parse(s, g)  gid_parse_base((s), (g), 10)
#define gid_parse_strict(s, g) gid_parse_strict_base((s), (g), 10)
#define pid_format(s, p) pid_format_base((s), (p), 10)
#define pid_parse(s, p)  pid_parse_base((s), (p), 10)

/* buffer sizes (oversized but safe whatever the type width) */
#define UID_FMT U64_FMT
#define GID_FMT U64_FMT
#define PID_FMT I64_FMT

/* ===========================================================================
 * Bit flags helpers and mode_t comparison
 * ===========================================================================*/

// can be used like FLAGS_SET(has, want|want1|...)
#define FLAGS_SET(has, want) ((has) |= (want))
#define FLAGS_CLEAR(has, want) ((has) &= ~(want))
// return the interger of the flag if its set, 0 otherwise
#define FLAGS_ISSET(has, want) (((has) & (want)) == (want))

/** compare @mode with @stmode
 * @Return @stmode if @mode == @stmode
 * otherwise @Return 0 */
extern mode_t get_flags(mode_t mode, mode_t stmode) ;

#endif
