/************************************************************************
 *
 * opt_lines.cpp - definitions of line option handlers
 *
 * $Id: opt_lines.cpp 290026 2005-09-19 00:12:38Z sebor $
 *
 ************************************************************************
 *
 * Copyright (c) 1994-2005 Quovadx,  Inc., acting through its  Rogue Wave
 * Software division. Licensed under the Apache License, Version 2.0 (the
 * "License");  you may  not use this file except  in compliance with the
 * License.    You    may   obtain   a   copy   of    the   License    at
 * http://www.apache.org/licenses/LICENSE-2.0.    Unless   required    by
 * applicable law  or agreed to  in writing,  software  distributed under
 * the License is distributed on an "AS IS" BASIS,  WITHOUT WARRANTIES OR
 * CONDITIONS OF  ANY KIND, either  express or implied.  See  the License
 * for the specific language governing permissions  and limitations under
 * the License.
 *
 **************************************************************************/

// expand _TEST_EXPORT macros
#define _RWSTD_TEST_SRC

#include "opt_lines.h"

#include <cmdopt.h>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


enum {
    l_enabled  = 1,   // line is enabled
    l_disabled = 2,   // line is disabled
    l_expected = 4    // diagnostic on line is expected to be active
};

struct linerange_t {
    int      first;
    int      last;
    unsigned flags: 8;
    char     id [80];
};

static size_t nlineranges;
static linerange_t *lineranges;
static size_t rangebufsize;

/**************************************************************************/

static int
_rw_enable_lines (int first, int last, int flags)
{
    if (nlineranges == rangebufsize) {
        const size_t newbufsize = 2 * nlineranges + 1;
            
        linerange_t* const newranges =
            (linerange_t*)malloc (newbufsize * sizeof (linerange_t));

        if (0 == newranges) {
            abort ();
        }

        memcpy (newranges, lineranges, nlineranges * sizeof (linerange_t));

        free (lineranges);

        lineranges   = newranges;
        rangebufsize = newbufsize;
    }

    lineranges [nlineranges].first = first;
    lineranges [nlineranges].last  = last;

    lineranges [nlineranges].flags = flags;

    ++nlineranges;

    return 0;
}


static int
_rw_enable_line (int argc, char *argv[], int flags)
{
    char *parg = strchr (argv [0], '=');
    assert (0 != parg);

    const char* const argbeg = ++parg;

    // the lower bound of a range of lines to be enabled or disabled
    // negative values are not valid and denote an implicit lower bound
    // of 1 (such as in "-3" which is a shorthand for "1-3")
    long first = -1;

    for ( ; '\0' != *parg ; ) {

        // skip any leading whitespace
        for ( ; ' ' == *parg; ++parg);

        if ('-' == *parg) {
            if (first < 0) {
                first = 0;
                ++parg;
            }
            else {
                fprintf (stderr,
                         "invalid character '%c' at position %d: \"%s\"\n",
                         *parg, int (parg - argbeg), argv [0]);
                return 2;
            }
        }

        // parse a numeric argument
        char *end;
        long line = strtol (parg, &end, 0);

        // skip any trailing whitespace
        for ( ; ' ' == *end; ++end);

        if (end == parg || '-' != *end && ',' != *end && '\0' != *end) {
            fprintf (stderr,
                     "invalid character '%c' at position %d: \"%s\"\n",
                     *end, int (parg - argbeg), argv [0]);
            return 2;
        }

        if (0 <= first) {
            if (line < 0) {
                fprintf (stderr,
                         "invalid value %ld at position %d: \"%s\"\n",
                         line, int (parg - argbeg), argv [0]);
                return 2;
            }

            ++line;

            if ((',' == *end || '-' == *end) && end [1])
                ++end;
        }
        else if (',' == *end) {
            first = line++;
            if ('\0' == end [1]) {
                fprintf (stderr,
                         "invalid character '%c' at position %d: \"%s\"\n",
                         *end, int (parg - argbeg), argv [0]);
                return 2;
            }

            ++end;
        }
        else if ('-' == *end) {
            first = line;
            while (' ' == *++end);
            if ('\0' == *end) {
                line = _RWSTD_INT_MAX;
            }
            else if  (',' == *end) {
                line = _RWSTD_INT_MAX;
                ++end;
            }
            else
                line = -1;
        }
        else if ('\0' == *end) {
            first = line++;
        }
        else {
            fprintf (stderr,
                     "invalid character '%c' at position %d: \"%s\"\n",
                     *end, int (parg - argbeg), argv [0]);
            return 2;
        }

        parg = end;

        if (0 <= first && first < line) {
            _rw_enable_lines (first, line, flags);
            first = -1;
        }
    }

    return 0;
}

/**************************************************************************/

static int
_rw_opt_enable_line (int argc, char *argv[])
{
    if (1 == argc && argv && 0 == argv [0]) {

        static const char helpstr[] = {
            "Enables the line or lines specified by <arg>.\n"
            "The syntax of <arg> is as follows: \n"
            "<arg>   ::= <range> [ , <range> ]\n"
            "<range> ::= [ - ] <number> | <number> - [ <number> ]\n"
        };

        argv [0] = _RWSTD_CONST_CAST (char*, helpstr);

        return 0;
    }

    return _rw_enable_line (argc, argv, l_enabled);
}

/**************************************************************************/

static int
_rw_opt_no_line (int argc, char *argv[])
{
    if (1 == argc && argv && 0 == argv [0]) {

        static const char helpstr[] = {
            "Disables the line or lines specified by <arg>.\n"
            "The syntax of <arg> is as follows: \n"
            "<arg>   ::= <range> [ , <range> ]\n"
            "<range> ::= [ - ] <number> | <number> - [ <number> ]\n"
        };

        argv [0] = _RWSTD_CONST_CAST (char*, helpstr);

        return 0;
    }

    return _rw_enable_line (argc, argv, l_disabled);
}

/**************************************************************************/

static int
_rw_opt_expect_line (int argc, char *argv[])
{
    if (1 == argc && argv && 0 == argv [0]) {

        static const char helpstr[] = {
            "Marks the line or lines specified by <arg> as \"expected\".\n"
            "Inactive diagnostics on such lines will be issued as unexpected.\n"
            "The syntax of <arg> is as follows: \n"
            "<arg>   ::= <range> [ , <range> ]\n"
            "<range> ::= [ - ] <number> | <number> - [ <number> ]\n"
        };

        argv [0] = _RWSTD_CONST_CAST (char*, helpstr);

        return 0;
    }

    return _rw_enable_line (argc, argv, l_expected);
}

/************************************************************************/

/* extern */ int
_rw_setopts_lines ()
{
    const int result =
        rw_setopts ("|-enable-line="   // argument required
                    "|-expect= "       // argument required
                    "|-no-line= ",     // argument required
                    _rw_opt_enable_line,
                    _rw_opt_expect_line,
                    _rw_opt_no_line);

    return result;
}

/************************************************************************/

/* extern */ int
_rw_expected (int line)
{
    int line_expected = 0;

    for (size_t i = 0; i != nlineranges; ++i) {

        const int first = lineranges [i].first;
        const int last  = lineranges [i].last;

        if (lineranges [i].flags & (l_disabled | l_enabled)) {
            continue;
        }

        if (first <= line && line < last)
            line_expected = 0 != (lineranges [i].flags & l_expected);
    }

    return line_expected;
}

/************************************************************************/

_TEST_EXPORT int
rw_enabled (int line)
{
    int nenabled = 0;
    int ndisabled = 0;

    int line_enabled = -1;

    for (size_t i = 0; i != nlineranges; ++i) {

        const int first = lineranges [i].first;
        const int last  = lineranges [i].last;

        if (lineranges [i].flags & l_disabled) {
            ++ndisabled;
        }
        else if (lineranges [i].flags & l_enabled) {
            ++nenabled;
        }
        else {
            continue;
        }

        if (first <= line && line < last)
            line_enabled = 0 != (lineranges [i].flags & l_enabled);
    }

    if (nenabled && -1 == line_enabled)
        line_enabled = 0;

    return line_enabled;
}
