/*
 * pg_check.c
 *
 * A simple check syntax program for PostgreSQL
 * Originally written by Ranier Vilela and enhanced by many contributors.
 *
 * src/bin/pg_check/pg_check.c
 * Copyright (c) 2022, PostgreSQL Global Development Group
 * ALL RIGHTS RESERVED;
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without a written agreement
 * is hereby granted, provided that the above copyright notice and this
 * paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
 * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>


static const char * progname = "pg_check";


struct S_CTX
{
	int nline;
	int nargs;
	int nchars;
	int nbrackets;
	int nkeys;
	int nperc;
	int nsfmt;
	bool quote;
	bool finish;
};

typedef struct S_CTX s_ctx;

struct S_FUNC
{
	char * function;
	char * should_string;
	char * should_char;
};

typedef struct S_FUNC s_func;


static const s_func pg_functions[] = {
	{ "appendPQExpBuffer(", "should be appendPQExpBufferStr?", "should be appendPQExpBufferChar?" },
	{ "appendStringInfo(", "should be appendStringInfoString?", "should be appendStringInfoChar?" },
	NULL
};


void parser_function(s_ctx * ctx, const s_func * pg_function, const char * line)
{
	const char * ptr;
	size_t len;
	size_t i;
	char ch;

	ctx->finish = false;
	ptr = line;
	len = strlen(line);
	for(i = 0; i < len; i++)
	{
		ch = ptr[i];
		if (!ctx->quote)
		{
			if (ch == '(')
				ctx->nbrackets++;
			else if (ch == ')')
				ctx->nbrackets--;
			else if (ch == '{')
				ctx->nkeys++;
			else if (ch == '}')
				ctx->nkeys--;
			else if (ch == ';')
			{
				ctx->finish = true;
				break;
			}
			else if (ch == ',')
				ctx->nargs++;
		}
		if (ctx->nargs > 0)
			if (ch == '"')
			    ctx->quote = !ctx->quote;
		if (ctx->quote)
		{
			if (ch != '"')
				ctx->nchars++;
			if (ch == '%')
				ctx->nperc++;
			if (ch == 's' && ctx->nperc)
			{
				ctx->nsfmt++;
				ctx->nperc--;
			}
		}
	}

	/* Analyze */
	if (ctx->finish)
	{
		if (ctx->nargs < 2)
			if (ctx->nchars == 1)
				fprintf(stderr, "line (%d): %s\n", ctx->nline, pg_function->should_char);
			else
				fprintf(stderr, "line (%d): %s\n", ctx->nline, pg_function->should_string);
		else if (ctx->nargs == 2)
			if (ctx->nsfmt == 1)
				if (ctx->nchars == 1)
					fprintf(stderr, "line (%d): %s\n", ctx->nline, pg_function->should_char);
				else
					fprintf(stderr, "line (%d): %s\n", ctx->nline, pg_function->should_string);
	}
}


void parser_src(FILE * f)
{
	char line[512] = {'\0'};
	s_ctx ctx = {0};
	int size = (sizeof(pg_functions) / sizeof(s_func)) - 1;
	int nline;
	int i;

	ctx.finish = true;
	i = 0;
	nline = 0;
	while(fgets(line, sizeof(line) - 1, f) != NULL)
	{
		nline++;
		if (ctx.finish)
		{
			memset(&ctx, 0, sizeof(ctx));
			ctx.finish = true;
			ctx.nline = nline;

			for(i = 0; i < size; i++)
			{
				const char *function;

				function = pg_functions[i].function;
				if (strstr(line, function) != NULL)
				{
					parser_function(&ctx, &pg_functions[i], line);
					if (!ctx.finish)
						break;
				}
			}	
		}
		else
			parser_function(&ctx, &pg_functions[i], line);
	}
}


int main(int argc, char ** argv)
{
    FILE * f;

    if (argv[0] != NULL && *argv[0] != 0) {
		progname = argv[0];
	}
    if (argc != 2)  {
        fprintf(stderr, "usage: %s source.c\n", progname);
        exit(1);
	}
    f = fopen(argv[1], "r");
    if (f == NULL) {
        exit(2);
	}
	parser_src(f);
    fclose(f);

    exit(0);
}
