#include <stdio.h>
#include <sys/file.h>
#ifdef SDBM
#include "EXTERN.h"
#include "sdbm.h"
#else
#include <ndbm.h>
#endif
#include <string.h>

extern int	getopt();
extern void	oops();

char *progname;

static int rflag;
static char *usage = "%s [-R] cat | look |... dbmname";

#define DERROR	0
#define DLOOK	1
#define DINSERT	2
#define DDELETE 3
#define DCAT    4
#define DBUILD	5
#define DPRESS	6
#define DCREAT	7

#define LINEMAX	8192

typedef struct {
        char *sname;
        int scode;
        int flags;
} cmd;

static cmd cmds[] = {

        "fetch", DLOOK, 	O_RDONLY,
        "get", DLOOK,		O_RDONLY,
        "look", DLOOK,		O_RDONLY,
        "add", DINSERT,		O_RDWR,
        "insert", DINSERT,	O_RDWR,
        "store", DINSERT,	O_RDWR,
        "delete", DDELETE,	O_RDWR,
        "remove", DDELETE,	O_RDWR,
        "dump", DCAT,		O_RDONLY,
        "list", DCAT, 		O_RDONLY,
        "cat", DCAT,		O_RDONLY,
        "creat", DCREAT,	O_RDWR | O_CREAT | O_TRUNC,
        "new", DCREAT,		O_RDWR | O_CREAT | O_TRUNC,
        "build", DBUILD,	O_RDWR | O_CREAT,
        "squash", DPRESS,	O_RDWR,
        "compact", DPRESS,	O_RDWR,
        "compress", DPRESS,	O_RDWR
};

#define CTABSIZ (sizeof (cmds)/sizeof (cmd))

static cmd *parse();
static void badk(), doit(), prdatum();

int
main(int argc, char **argv)
{
        int c;
        cmd *act;
        extern int optind;
        extern char *optarg;

        progname = argv[0];

        while ((c = getopt(argc, argv, "R")) != EOF)
                switch (c) {
                case 'R':	       /* raw processing  */
                        rflag++;
                        break;

                default:
                        oops("usage: %s", usage);
                        break;
                }

        if ((argc -= optind) < 2)
                oops("usage: %s", usage);

        if ((act = parse(argv[optind])) == NULL)
                badk(argv[optind]);
        optind++;
        doit(act, argv[optind]);
        return 0;
}

static void
doit(cmd *act, char *file)
{
        datum key;
        datum val;
        DBM *db;
        char *op;
        int n;
        char *line;
#ifdef TIME
        long start;
        extern long time();
#endif

        if ((db = dbm_open(file, act->flags, 0644)) == NULL)
                oops("cannot open: %s", file);

        if ((line = (char *) malloc(LINEMAX)) == NULL)
                oops("%s: cannot get memory", "line alloc");

        switch (act->scode) {

        case DLOOK:
                while (fgets(line, LINEMAX, stdin) != NULL) {
                        n = strlen(line) - 1;
                        line[n] = 0;
                        key.dptr = line;
                        key.dsize = n;
                        val = dbm_fetch(db, key);
                        if (val.dptr != NULL) {
                                prdatum(stdout, val);
                                putchar('\n');
                                continue;
                        }
                        prdatum(stderr, key);
                        fprintf(stderr, ": not found.\n");
                }
                break;
        case DINSERT:
                break;
        case DDELETE:
                while (fgets(line, LINEMAX, stdin) != NULL) {
                        n = strlen(line) - 1;
                        line[n] = 0;
                        key.dptr = line;
                        key.dsize = n;
                        if (dbm_delete(db, key) == -1) {
                                prdatum(stderr, key);
                                fprintf(stderr, ": not found.\n");
                        }
                }
                break;
        case DCAT:
                for (key = dbm_firstkey(db); key.dptr != 0; 
                     key = dbm_nextkey(db)) {
                        prdatum(stdout, key);
                        putchar('\t');
                        prdatum(stdout, dbm_fetch(db, key));
                        putchar('\n');
                }
                break;
        case DBUILD:
#ifdef TIME
                start = time(0);
#endif
                while (fgets(line, LINEMAX, stdin) != NULL) {
                        n = strlen(line) - 1;
                        line[n] = 0;
                        key.dptr = line;
                        if ((op = strchr(line, '\t')) != 0) {
                                key.dsize = op - line;
                                *op++ = 0;
                                val.dptr = op;
                                val.dsize = line + n - op;
                        }
                        else
                                oops("bad input; %s", line);
        
                        if (dbm_store(db, key, val, DBM_REPLACE) < 0) {
                                prdatum(stderr, key);
                                fprintf(stderr, ": ");
                                oops("store: %s", "failed");
                        }
                }
#ifdef TIME
                printf("done: %d seconds.\n", time(0) - start);
#endif
                break;
        case DPRESS:
                break;
        case DCREAT:
                break;
        }

        dbm_close(db);
}

static void
badk(char *word)
{
        int i;

        if (progname)
                fprintf(stderr, "%s: ", progname);
        fprintf(stderr, "bad keywd %s. use one of\n", word);
        for (i = 0; i < (int)CTABSIZ; i++)
                fprintf(stderr, "%-8s%c", cmds[i].sname,
                        ((i + 1) % 6 == 0) ? '\n' : ' ');
        fprintf(stderr, "\n");
        exit(1);
        /*NOTREACHED*/
}

static cmd *
parse(char *str)
{
        int i = CTABSIZ;
        cmd *p;
        
        for (p = cmds; i--; p++)
                if (strcmp(p->sname, str) == 0)
                        return p;
        return NULL;
}

static void
prdatum(FILE *stream, datum d)
{
        int c;
        U8 *p = (U8 *) d.dptr;
        int n = d.dsize;

        while (n--) {
                c = *p++;
#ifndef EBCDIC /* Meta notation doesn't make sense on EBCDIC systems*/
                if (c & 0200) {
                    fprintf(stream, "M-");
                    c &= 0177;
                }
#endif
                /* \c notation applies for \0 . \x1f, plus \c? */
                if (c <= 0x1F || c == QUESTION_MARK_CTRL) {
                    fprintf(stream, "^%c", toCTRL(c));
                }
#ifdef EBCDIC   /* Instead of meta, use \x{} for non-printables */
                else if (! isPRINT_A(c)) {
                    fprintf(stream, "\\x{%02x}", c);
                }
#endif
                else { /* must be an ASCII printable */
                    putc(c, stream);
                }
        }
}