/* -*- c-file-style: "linux";indent-tabs-mode:t -*- */
/* Copyright (C) 2017 Jeremiah Orians
 * Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org>
 * This file is part of mescc-tools
 *
 * mescc-tools is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * mescc-tools is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with mescc-tools.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/stat.h>


#define max_string 4096
//CONSTANT max_string 4096
#define TRUE 1
//CONSTANT TRUE 1
#define FALSE 0
//CONSTANT FALSE 0
int BITSIZE;

int in_set(int c, char* s);
int match(char* a, char* b);
void file_print(char* s, FILE* f);
void require(int bool, char* error);

struct entry
{
	struct entry* next;
	char* name;
};

FILE* output;
struct entry* jump_table;

void consume_token(FILE* source_file, char* s)
{
	int i = 0;
	int c = fgetc(source_file);
	require(EOF != c, "Can not have an EOF token\n");
	do
	{
		s[i] = c;
		i = i + 1;
		require(max_string > i, "Token exceeds token length restriction\n");
		c = fgetc(source_file);
		if(EOF == c) break;
	} while(!in_set(c, " \t\n>"));
}

void storeLabel(FILE* source_file)
{
	struct entry* entry = calloc(1, sizeof(struct entry));

	/* Prepend to list */
	entry->next = jump_table;
	jump_table = entry;

	/* Store string */
	entry->name = calloc((max_string + 1), sizeof(char));
	consume_token(source_file, entry->name);

	/* Remove all entries that start with the forbidden char pattern :_ */
	if('_' == entry->name[0])
	{
		jump_table = jump_table->next;
	}
}

void line_Comment(FILE* source_file)
{
	int c = fgetc(source_file);
	while(!in_set(c, "\n\r"))
	{
		if(EOF == c) break;
		c = fgetc(source_file);
	}
}

void purge_string(FILE* source_file)
{
	int c = fgetc(source_file);
	while((EOF != c) && ('"' != c))
	{
		c = fgetc(source_file);
	}
}

void first_pass(struct entry* input)
{
	if(NULL == input) return;
	first_pass(input->next);

	FILE* source_file = fopen(input->name, "r");

	if(NULL == source_file)
	{
		file_print("The file: ", stderr);
		file_print(input->name, stderr);
		file_print(" can not be opened!\n", stderr);
		exit(EXIT_FAILURE);
	}

	int c;
	for(c = fgetc(source_file); EOF != c; c = fgetc(source_file))
	{
		/* Check for and deal with label */
		if(58 == c)
		{
			storeLabel(source_file);
		}
		/* Check for and deal with line comments */
		else if (c == '#' || c == ';')
		{
			line_Comment(source_file);
		}
		else if ('"' == c)
		{
			purge_string(source_file);
		}
	}
	fclose(source_file);
}

void output_debug(struct entry* node, int stage)
{
	struct entry* i;
	for(i = node; NULL != i; i = i->next)
	{
		if(stage)
		{
			file_print(":ELF_str_", output);
			file_print(i->name, output);
			file_print("\n\"", output);
			file_print(i->name, output);
			file_print("\"\n", output);
		}
		else if(64 == BITSIZE)
		{
			file_print("%ELF_str_", output);
			file_print(i->name, output);
			file_print(">ELF_str\n!2\n!0\n@1\n&", output);
			file_print(i->name, output);
			file_print(" %0\n%10000\n%0\n", output);
		}
		else
		{
			file_print("%ELF_str_", output);
			file_print(i->name, output);
			file_print(">ELF_str\n&", output);
			file_print(i->name, output);
			file_print("\n%10000\n!2\n!0\n@1\n", output);
		}
	}
}

struct entry* reverse_list(struct entry* head)
{
	struct entry* root = NULL;
	struct entry* next;
	while(NULL != head)
	{
		next = head->next;
		head->next = root;
		root = head;
		head = next;
	}
	return root;
}

/* Standard C main program */
int main(int argc, char **argv)
{
	jump_table = NULL;
	struct entry* input = NULL;
	output = stdout;
	char* output_file = "";
	BITSIZE = 32;

	int option_index = 1;
	while(option_index <= argc)
	{
		if(NULL == argv[option_index])
		{
			option_index = option_index + 1;
		}
		else if(match(argv[option_index], "-h") || match(argv[option_index], "--help"))
		{
			file_print("Usage: ", stderr);
			file_print(argv[0], stderr);
			file_print(" --file FILENAME1 {--file FILENAME2} --output FILENAME\n", stderr);
			exit(EXIT_SUCCESS);
		}
		else if(match(argv[option_index], "--64"))
		{
			BITSIZE = 64;
			option_index = option_index + 1;
		}
		else if(match(argv[option_index], "-f") || match(argv[option_index], "--file"))
		{
			struct entry* temp = calloc(1, sizeof(struct entry));
			temp->name = argv[option_index + 1];
			temp->next = input;
			input = temp;
			option_index = option_index + 2;
		}
		else if(match(argv[option_index], "-o") || match(argv[option_index], "--output"))
		{
			output_file = argv[option_index + 1];
			output = fopen(output_file, "w");

			if(NULL == output)
			{
				file_print("The file: ", stderr);
				file_print(input->name, stderr);
				file_print(" can not be opened!\n", stderr);
				exit(EXIT_FAILURE);
			}
			option_index = option_index + 2;
		}
		else if(match(argv[option_index], "-V") || match(argv[option_index], "--version"))
		{
			file_print("blood-elf 0.7.0\n(Basically Launches Odd Object Dump ExecutabLe Files\n", stdout);
			exit(EXIT_SUCCESS);
		}
		else
		{
			file_print("Unknown option\n", stderr);
			exit(EXIT_FAILURE);
		}
	}

	/* Make sure we have a program tape to run */
	if (NULL == input)
	{
		return EXIT_FAILURE;
	}

	/* Get all of the labels */
	first_pass(input);

	/* Reverse their order */
	jump_table = reverse_list(jump_table);

	file_print(":ELF_str\n!0\n", output);
	output_debug(jump_table, TRUE);
	if(64 == BITSIZE) file_print("%0\n:ELF_sym\n%0\n!0\n!0\n@1\n%0 %0\n%0 %0\n", output);
	else file_print("%0\n:ELF_sym\n%0\n%0\n%0\n!0\n!0\n@1\n", output);
	output_debug(jump_table, FALSE);
	file_print("\n:ELF_end\n", output);

	return EXIT_SUCCESS;
}
