
#include "tags.h"
#include "tagprivate.h"

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

#include <XError.h>

#define MAXIMUM_TAG_DEPENDENCIES 50000

struct tag_dependency
{
	file_tag type;
	file_tag id;
	tag_entry *original;
	file_tag source_mesh;
	bool small_install;
};

static tag_dependency dependency_list[MAXIMUM_TAG_DEPENDENCIES];
static int dependency_list_count;

static FILE *dependLog;
static char strerr[1024];
static tag_dependency *source;
static file_tag current_source_mesh;

static tag_dependency hack_localized_list[] =
{
	{ '256.', 'MB01', NULL, -1, false },
	{ '256.', 'MB02', NULL, -1, false },
	{ '256.', 'MB03', NULL, -1, false },
	{ '256.', 'MB04', NULL, -1, false },
	{ '256.', 'MB05', NULL, -1, false },
	{ '256.', 'MB06', NULL, -1, false },
	{ '256.', 'MB07', NULL, -1, false },
	{ '256.', 'MTit', NULL, -1, false },
	{ '256.', 'NTit', NULL, -1, false },
	{ '256.', 'LTit', NULL, -1, false },
	{ '256.', 'FTit', NULL, -1, false },
	{ '256.', 'OLB1', NULL, -1, false },
	{ '256.', 'OLB2', NULL, -1, false },
	{ '256.', 'OLB3', NULL, -1, false },
	{ '256.', 'OLB4', NULL, -1, false },
	{ '256.', 'OLB5', NULL, -1, false },
	{ '256.', 'OLB6', NULL, -1, false },
	{ '256.', 'OLB7', NULL, -1, false },
	{ '256.', 'OTit', NULL, -1, false },
	{ '256.', 'PTit', NULL, -1, false },
	{ '256.', 'inmt', NULL, -1, false }
};

static const char *tagref( tag_entry *e )
{
	static int ref_cycle = 0;
	static char refbufs[8][256];
	
	if (!e)
	{
		return "NULL tag pointer";
	}
	
	ref_cycle = (ref_cycle + 1) & 7;
	char *s = refbufs[ref_cycle];
	
	sprintf( s, "'%s' (%s/%s) in ",
				e->name,
				tagstr(e->group->type),
				tagstr(e->id) );
	if (e->file)
	{
		strcat( s, "file " );
		e->file->filespec->GetName( s + strlen(s) );
	}
	else
	{
		strcat( s, "unsaved file" );
	}
	
	return s;
}

static void dependerr()
{
	if (!dependLog) return;
	fprintf( dependLog, "%s (referenced from %s)\n",
			strerr, source ? tagref(source->original) : "initial seeding" );
}

static void builderr()
{
	if (!dependLog) return;
	fprintf( dependLog, "%s\n", strerr );
}


void begin_tag_dependency_generation()
{
	if (dependLog) fclose( dependLog );
	source = 0;
	current_source_mesh = 0;
	dependency_list_count = 0;
	
	XGDirectory dir;
	dir.SetAppLocation();
	dir.ChangeDirectory( "foundation_build" );
	
	XGFileSpecifier spec;
	spec.SetFile( dir, "small.tag" );
	spec.DeleteFile();
	
	spec.SetFile( dir, "small_localized.tag" );
	spec.DeleteFile();
	
	spec.SetFile( dir, "large.tag" );
	spec.DeleteFile();
	
	spec.SetFile( dir, "large_localized.tag" );
	spec.DeleteFile();
	
	spec.SetFile( dir, "dependency_log.txt" );
	spec.DeleteFile();
	dependLog = spec.OpenFile( "w" );
}


void add_tag_dependency( tag type, tag id )
{
	if (type == -1 || type == 0 || id == -1 || id == 0) return;
	tag_type_info *info = typeinfo(type);
	if (!info)
	{
		sprintf( strerr, "unknown tag type %s of id %s", tagstr(type), tagstr(id) );
		dependerr();
		return;
	}
	
	tag_entry *found = 0;
	tag_dependency *d = 0;
	
	for (int i = 0; i < dependency_list_count; ++i)
	{
		if (dependency_list[i].type == type && dependency_list[i].id == id)
		{
			if (type != 'mesh' &&
				current_source_mesh != dependency_list[i].source_mesh &&
				!dependency_list[i].small_install)
			{
				dependency_list[i].small_install = true;
			}
			else
			{
				return;
			}
			d = dependency_list + i;
			found = dependency_list[i].original;
			break;
		}
	}
	
	if (!found)
	{
		bool duplicate_found = false;
		int found_file_type;
		
		for (tag_file *f = tag_file_list; f; f = f->next) {
			for (tag_group *g = f->groups; g; g = g->next) {
				if (g->type != type) continue;
				for (tag_entry *e = g->entries; e; e = e->next) {
					if (e->id != id) continue;
					if (found)
					{
						if (f->loaded_type == _tag_file_type_foundation &&
							found_file_type != _tag_file_type_foundation)
						{
							continue;
						}
						if (f->loaded_type != _tag_file_type_foundation &&
							found_file_type == _tag_file_type_foundation)
						{
							found_file_type = f->loaded_type;
							found = e;
							continue;
						}
						if (!duplicate_found)
						{
							sprintf( strerr, "duplicate tag %s", tagref( found ) );
							dependerr();
							duplicate_found = true;
						}
						sprintf( strerr, "duplicate tag %s", tagref( e ) );
						dependerr();
					}
					else
					{
						found_file_type = f->loaded_type;
						found = e;
					}
				}
			}
		}
		
		if (!found)
		{
			sprintf( strerr, "missing tag %s/%s", tagstr( type ), tagstr( id ) );
			dependerr();
			return;
		}
		
		if (found_file_type == _tag_file_type_foundation)
		{
			sprintf( strerr, "used foundation tag %s", tagref( found ) );
			dependerr();
		}
		
		if (dependency_list_count >= MAXIMUM_TAG_DEPENDENCIES)
		{
			if (dependency_list_count++ == MAXIMUM_TAG_DEPENDENCIES)
			{
				sprintf( strerr, "limit of %d tags exceeded", MAXIMUM_TAG_DEPENDENCIES );
				dependerr();
			}
			return;
		}
		
		d = dependency_list + dependency_list_count++;
		d->type = type;
		d->id = id;
		d->original = found;
		d->source_mesh = current_source_mesh;
		d->small_install = (type == '.256' || type == 'bina' || type == 'font' || (type != 'mesh' && current_source_mesh == 0));
	}
	
	if (info->dependencies)
	{
		file_tag save_source_mesh = current_source_mesh;
		if (type == 'mesh') current_source_mesh = id;
		
		tag_dependency *save_source = source;
		source = d;
		
		info->dependencies( found );
		
		current_source_mesh = save_source_mesh;
		source = save_source;
	}
}


void end_tag_dependency_generation()
{
	tag_file *large_file = new_empty_tag_file();
//	tag_file *large_localized_file = new_empty_tag_file();
	tag_file *small_file = new_empty_tag_file();
	tag_file *small_localized_file = new_empty_tag_file();
	
	for (int i = 0; i < dependency_list_count; ++i)
	{
		tag_file *dest;
		tag_dependency &d = dependency_list[i];
		tag_type_info *info = typeinfo( d.type );
		tag_entry *e = d.original;
		bool localized = (info->default_localization == _tag_localized);
		
		if( dependency_list[i].type == '.256' )
		{
			for( int ii = 0; ii < sizeof(hack_localized_list) / sizeof(tag_dependency); ii++ )
			{
				if( dependency_list[i].id == hack_localized_list[ii].id )
				{
					localized = true;
					break;
				}
			}
		}
		
		if (d.small_install)
		{
			dest = (localized ? small_localized_file : small_file);
		}
		else
		{
//			dest = (localized ? large_localized_file : large_file);
			dest = (localized ? small_localized_file : large_file);
		}
		
		const void *data = 0;
		unsigned length = 0;
		if (e->length > 0)
		{
			if (get_entry_data( e, &data, &length ) == 0)
			{
				sprintf( strerr, "out of memory building monolithic" );
				builderr();
				goto done;
			}
		}
		else
		{
			data = &data;
		}
		add_tag_entry( dest, d.type, d.id, e->version, e->name, 0, length, data, false );
		release_entry_data( e );
	}
	
	{
		XGDirectory dir;
		dir.SetAppLocation();
		dir.ChangeDirectory( "foundation_build" );
		
		XGFileSpecifier spec;
		spec.SetFile( dir, "small.tag" );
		if (!write_tags_to_file( spec, small_file, true, TAG_FILE_TYPE_MONOLITHIC ))
		{
			strcpy( strerr, "could not write small.tag" );
			builderr();
			goto done;
		}
		
		spec.SetFile( dir, "small_localized.tag" );
		if (!write_tags_to_file( spec, small_localized_file, true, TAG_FILE_TYPE_MONOLITHIC ))
		{
			strcpy( strerr, "could not write small_localized.tag" );
			builderr();
			goto done;
		}
		
		spec.SetFile( dir, "large.tag" );
		if (!write_tags_to_file( spec, large_file, true, TAG_FILE_TYPE_MONOLITHIC ))
		{
			strcpy( strerr, "could not write large.tag" );
			builderr();
			goto done;
		}
		
/*
		spec.SetFile( dir, "large_localized.tag" );
		if (!write_tags_to_file( spec, large_localized_file, true, TAG_FILE_TYPE_MONOLITHIC ))
		{
			strcpy( strerr, "could not write large_localized.tag" );
			builderr();
			goto done;
		}
*/
	}
	
done:
	close_tag_file( large_file );
//	close_tag_file( large_localized_file );
	close_tag_file( small_file );
	close_tag_file( small_localized_file );
	if (dependLog)
	{
		fclose( dependLog );
		dependLog = 0;
	}
}

bool update_entry_version( tag_entry *e )
{
	if (!e) return false;
	tag_group *g = get_entry_group( e );
	if (!g) return false;
	tag_type_info *info = typeinfo( g->type );
	if (!info) return false;
	int v = get_entry_version( e );
	
	if (v == 0)
	{
		set_entry_version( e, info->version );
	}
	else
	{
		if (v > info->version)
		{
			Assert( v <= info->version );
		}
		if (v < info->version)
		{
			if (!info->updater)
			{
				Assert( info->updater );
			}
			else
			{
				info->updater( e );
				return true;
			}
		}
	}
	
	return false;
}

