/************************************************************\
 * Copyright (C) 2001 The Regents of the University of California.
 * (c.f. DISCLAIMER, COPYING)
 *
 * This file is part of PowerMan, a remote power management program.
 * For details, see https://github.com/chaos/powerman.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
\************************************************************/

#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <stdio.h>
#include <sys/types.h>
#include <regex.h>

#include "error.h"
#include "xregex.h"
#include "xmalloc.h"

struct xregex_struct {
    int         xr_cflags;
    regex_t    *xr_regex;
};
struct xregex_match_struct {
    int         xm_nmatch;
    regmatch_t *xm_pmatch;
    char       *xm_str;
    int         xm_result;
    bool        xm_used;
};

xregex_t
xregex_create(void)
{
    xregex_t xrp = (xregex_t)xmalloc(sizeof(struct xregex_struct));

    xrp->xr_regex = NULL;

    return xrp;
}

void
xregex_destroy(xregex_t xrp)
{
    if (xrp->xr_regex) {
        regfree(xrp->xr_regex);
        xfree(xrp->xr_regex);
    }
    xfree(xrp);
}

/* Substitute all occurrences of s2 with s3 in s1,
 * e.g. _str_subst(str, "\\r", "\r")
 */
static void
_str_subst(char *s1, int len, const char *s2, const char *s3)
{
    int s2len = strlen(s2);
    int s3len = strlen(s3);
    char *p;

    while ((p = strstr(s1, s2)) != NULL) {
        assert(strlen(s1) + (s3len - s2len) + 1 <= len);
        memmove(p + s3len, p + s2len, strlen(p + s2len) + 1);
        memcpy(p, s3, s3len);
    }
}

void
xregex_compile(xregex_t xrp, const char *regex, bool withsub)
{
    char tmpstr[256];
    char *cpy;
    int n;

    assert(regex != NULL);
    assert(xrp->xr_regex == NULL);

/* No particular limit is imposed  on  the  length  of  REs(!).   Programs
 * intended to be portable should not employ REs longer than 256 bytes, as
 * an implementation can refuse to accept such REs and  remain  POSIX-com-
 * pliant.  -- regex(7) on RHEL 5
 */
    if (strlen(regex) > 256)
        err_exit(false, "refusing to compile regex > 256 bytes");

    xrp->xr_regex = (regex_t *)xmalloc(sizeof(regex_t));
    xrp->xr_cflags = REG_EXTENDED;
    if (!withsub)
        xrp->xr_cflags |= REG_NOSUB;

    cpy = xstrdup(regex);
    _str_subst(cpy, strlen(cpy) + 1, "\\r", "\r");
    _str_subst(cpy, strlen(cpy) + 1, "\\n", "\n");
    n = regcomp(xrp->xr_regex, cpy, xrp->xr_cflags);
    xfree(cpy);

    if (n != 0) {
        regerror(n, xrp->xr_regex, tmpstr, sizeof(tmpstr));
        err_exit(false, "regcomp failed: %s", tmpstr);
    }
}

bool
xregex_exec(xregex_t xrp, const char *s, xregex_match_t xm)
{
    int eflags = REG_NOTEOL;
    int res;

    assert(xrp->xr_regex != NULL);
    if (xm != NULL) {
        assert(xm->xm_used == false);
    }

    res = regexec(xrp->xr_regex, s, xm ? xm->xm_nmatch : 0,
                                    xm ? xm->xm_pmatch : NULL, eflags);
    if (xm != NULL) {
        xm->xm_result = res;
        xm->xm_used = true;
        if (res == 0) {
            if (xm->xm_str)
                xfree(xm->xm_str);
            xm->xm_str = xstrdup(s);
        }
    }
    return res == 0 ? true : false;
}

xregex_match_t
xregex_match_create(int nmatch)
{
    xregex_match_t xm;

    xm = (xregex_match_t)xmalloc(sizeof(struct xregex_match_struct));
    xm->xm_nmatch = nmatch + 1;
    xm->xm_pmatch = (regmatch_t *)xmalloc(sizeof(regmatch_t) * (nmatch + 1));
    xm->xm_str = NULL;
    xm->xm_result = -1;
    xm->xm_used = false;
    return xm;
}

void
xregex_match_destroy(xregex_match_t xm)
{

    xfree(xm->xm_pmatch);
    if (xm->xm_str)
        xfree(xm->xm_str);
    xfree(xm);
}

void
xregex_match_recycle(xregex_match_t xm)
{
    if (xm->xm_str) {
        xfree(xm->xm_str);
        xm->xm_str = NULL;
    }
    xm->xm_result = -1;
    xm->xm_used = false;
}

char *
xregex_match_strdup(xregex_match_t xm)
{
    char *s = NULL;

    assert(xm->xm_used);

    if (xm->xm_result == 0) {
        s = (char *)xmalloc(xm->xm_pmatch[0].rm_eo + 1);
        assert(xm->xm_str != NULL);
        memcpy(s, xm->xm_str, xm->xm_pmatch[0].rm_eo);
        s[xm->xm_pmatch[0].rm_eo] = '\0';
    }
    return s;
}

int
xregex_match_strlen(xregex_match_t xm)
{
    assert(xm->xm_used);

    return xm->xm_pmatch[0].rm_eo;
}

char *
xregex_match_sub_strdup(xregex_match_t xm, int i)
{
    char *s = NULL;

    assert(xm->xm_used);

    if (xm->xm_result == 0 && i >= 0 && i < xm->xm_nmatch
                           && xm->xm_pmatch[i].rm_so != -1) {
        regmatch_t m = xm->xm_pmatch[i];

        assert(xm->xm_str != NULL);
        assert(m.rm_so < m.rm_eo);
        s = xmalloc(m.rm_eo - m.rm_so + 1);
        memcpy(s, xm->xm_str + m.rm_so, m.rm_eo - m.rm_so);
        s[m.rm_eo - m.rm_so] = '\0';
    }
    return s;
}

/*
 * vi:tabstop=4 shiftwidth=4 expandtab
 */
