#!/usr/bin/perl -w

use strict;

my $Outfile;
my $Listfile;

my @Filelist;
my %Drivers;
my @Driverlist;

# initial values based on column labels length
my @MaxLength = (6,4,9,7,7,8,4,6);

# Process command line arguments
for ( my $i = 0; $i < scalar @ARGV; $i++ ) {{

	# Output file
	if ( $ARGV[$i] =~ /^-o/i ) {
		if ( $ARGV[$i] =~ /^-o$/i ) {
			$i++;
			if ( $i < scalar @ARGV ) {
				$Outfile = $ARGV[$i];
			}
		} else {
			$ARGV[$i] =~ /(?<=-o)(.*)/i;
			$Outfile = $1;
		}
		next;
	}

	# gamelist.txt
	if ( $ARGV[$i] =~ /^-l/i ) {
		if ( $ARGV[$i] =~ /^-l$/i ) {
			$i++;
			if ( $i < scalar @ARGV ) {
				$Listfile = $ARGV[$i];
			}
		} else {
			$ARGV[$i] =~ /(?<=-l)(.*)/i;
			$Listfile = $1;
		}
		next;
	}

	if ( opendir( INDIR, $ARGV[$i] ) ) {;
		# Argument is a dir
		my @dirlist = readdir( INDIR ) or die "\nError: Couldn't read directory $ARGV[$i] $!";
		foreach my $filename ( @dirlist ) {
			if ( $filename =~ /d\w?_.+\.cpp/ ) {
				push( @Filelist, "$ARGV[$i]/$filename" );
			}
		}
		closedir( INDIR );
		next;
	} else {
		# Argument is a file
		push( @Filelist, $ARGV[$i] );
		next;
	}
}}

unless ( $Outfile and scalar @Filelist ) {
	die "Usage: $0 -o <output file> [-l <gamelist>] <input file|dir> [<input file|dir>...]\n\n";
}

open( OUTFILE, ">$Outfile" ) or die "\nError: Couldn't open OUTPUT file $Outfile $!";

print "Generating gamelist in $Outfile...\n";

# Build a list of all drivers
foreach my $filename ( @Filelist ) {
	open( INFILE, $filename ) or die "\nError: Couldn't read $filename $!";
	while ( my $line = <INFILE> ) {

		# Strip C++ style // comments
		if ( $line =~ /(.*?)\/\// ) {
			$line = $1;
		}

		# Strip C style /* comments */
		if ( $line =~ /(.*)\/\*/ ) {
			my $temp = $1;
			while ( $line and !($line =~ /\*\/(.*)/) ) {
				$line = <INFILE>;
			}
			$line = $temp;
			substr( $line, length( $line ) ) = $1;
		}

		# get included .cpp.h files in the same directory
		if ( $line =~ /^\s*#include "d_\w*.h"/ ) {
			$line =~ /^\s*#include "(.*)"/;
			my $newfile = $1;
			$filename =~ /(.*[\/\\])[^\/\\]/;
			push( @Filelist, "$1$newfile" );
		}

		if ( $line =~ /struct\s+BurnDriver([D|X]?)\s+(\w+)(.*)/ ) {

			# We're at the start of a BurnDriver declaration

			# Create a hash key with the name of the structure,
			# Fill the first element of the array with the driver status
			my $name = $2;
			$Drivers{$name}[0] = "$1";

			# Read the Burndriver structure into a variable
			my $struct = $3;
			do {
				$line = <INFILE>;

				# Strip C++ style // comments
				if ( $line =~ /(.*?)\/\// ) {
					$line = $1;
				}

				substr( $struct, length( $struct ) ) = $line;
			} until $line =~ /;/;

			# Strip anything after the ;
			$struct =~ /(.*;)/s;
			$struct = $1;

			# Strip C style /* comments */
			while ( $struct =~ /(.*)\/\*.*?\*\/(.*)/s ) {
				$struct = $1;
				substr( $struct, length( $struct ) ) = $2;
			}

			# We now have the complete Burndriver structure without comments

			# Extract the strings from the variable
			$struct =~ /\s*(".*"|null|0)\s*,\s*(".*"|null|0)\s*,\s*(".*"|null|0)\s*,\s*(".*"|null|0)\s*,\s*(".*"|null|0)\s*,\s*(".*"|null|0)\s*,\s*(".*"|null|0)\s*,\s*(".*"|null|0)\s*,\s*(".*"|null|0),\s*(L".*"|null|0)\s*,\s*(L".*"|null|0)\s*,\s*(L".*"|null|0)\s*,\s*(L".*"|null)\s*,\s*(\d|[^,]*)/si;

			$Drivers{$name}[1] = $1;						# Name
			$Drivers{$name}[2] = $6;						# Full name
			$Drivers{$name}[3] = $7;						# Remarks
			$Drivers{$name}[4] = $8;						# Company
			$Drivers{$name}[5] = $9;						# Hardware
			$Drivers{$name}[6] = $5;						# Year of release
			$Drivers{$name}[7] = $2;						# Parent

			if ( $14 ne "1" && ($14 =~ /BDF_GAME_WORKING/) == 0 ) {			# Working status
				$Drivers{$name}[8] = "NOT WORKING";
			} else {
				$Drivers{$name}[8] = "";
			}

			my @parts = split /\//, $filename;
			my $size_of_parts = @parts;
			my $sourcefile = "$parts[$size_of_parts-2]/$parts[$size_of_parts-1]";
			# we still want to remove burn/ from burn/d_parent.cpp
			$sourcefile =~ s/drv\///;
			$Drivers{$name}[9] = $sourcefile;				# Filename

			# Convert NULL/null/0 to empty string or remove quotes
			foreach $line ( @{$Drivers{$name}} ) {
				if ( $line =~ /^(null|NULL|0)/ ) {
					$line = "";
				} else {
					$line =~ /(^"?)(.*)\1/;
					$line = $2;
				}
			}

			# We only want the 1st name
			$Drivers{$name}[2] =~ /(.*)\\0.*/;
			$Drivers{$name}[2] = $1;

			$Drivers{$name}[10] = $Drivers{$name}[2];
			if ( $struct =~ /BDF_PROTOTYPE/ || $struct =~ /BDF_BOOTLEG/ || $Drivers{$name}[3] ne "" ) {
				$Drivers{$name}[10] = "$Drivers{$name}[10]" . " [";
			}
			if ( $struct =~ /BDF_PROTOTYPE/ ) {
				$Drivers{$name}[10] = "$Drivers{$name}[10]" . "Prototype";
				if ( $struct =~ /BDF_BOOTLEG/ || $Drivers{$name}[3] ne "" ) {
					$Drivers{$name}[10] = "$Drivers{$name}[10]" . ", ";
				}
			}
			if ( $struct =~ /BDF_BOOTLEG/ ) {
				$Drivers{$name}[10] = "$Drivers{$name}[10]" . "Bootleg";
				if ( $Drivers{$name}[3] ne "" ) {
					$Drivers{$name}[10] = "$Drivers{$name}[10]" . ", ";
				}
			}
			if ( $Drivers{$name}[3] ne "" ) {
				$Drivers{$name}[10] = "$Drivers{$name}[10]" . "$Drivers{$name}[3]";
			}
			if ( $struct =~ /BDF_PROTOTYPE/ || $struct =~ /BDF_BOOTLEG/ || $Drivers{$name}[3] ne "" ) {
				$Drivers{$name}[10] = "$Drivers{$name}[10]" . "]";
			}

			# Store max length
			if (length($Drivers{$name}[1]) > $MaxLength[1]) { $MaxLength[1] = length($Drivers{$name}[1]); }
			if (length($Drivers{$name}[2]) > $MaxLength[2]) { $MaxLength[2] = length($Drivers{$name}[2]); }
			if (length($Drivers{$name}[3]) > $MaxLength[3]) { $MaxLength[3] = length($Drivers{$name}[3]); }
			if (length($Drivers{$name}[4]) > $MaxLength[4]) { $MaxLength[4] = length($Drivers{$name}[4]); }
			if (length($Drivers{$name}[5]) > $MaxLength[5]) { $MaxLength[5] = length($Drivers{$name}[5]); }
			if (length($Drivers{$name}[6]) > $MaxLength[6]) { $MaxLength[6] = length($Drivers{$name}[6]); }
			if (length($Drivers{$name}[7]) > $MaxLength[7]) { $MaxLength[7] = length($Drivers{$name}[7]); }
		}
	}
	close( INFILE );
}

# Sort the list of drivers using the decorated game name as the key
# Make the sort order more predictive by adding the driver name
@Driverlist = sort {
	lc( $Drivers{$a}[10] . $Drivers{$a}[1] ) cmp lc( $Drivers{$b}[10] . $Drivers{$b}[1] );
} keys %Drivers;

# first define needed constants and include needed files
print OUTFILE << "CPPEND";
// This file was generated by $0 (perl $])

// Declaration of all drivers
#define DRV extern struct BurnDriver
CPPEND

my $Debug = 0;
foreach my $name ( @Driverlist ) {

	if ( $Drivers{$name}[0] eq "X" ) {
		print OUTFILE "//";
	} else {
		if ( $Drivers{$name}[0] eq "D" ) {
			unless ( $Debug == 1) {
				$Debug = 1;
				print OUTFILE "#if defined FBNEO_DEBUG\n";
			}
		} else {
			if ( $Debug == 1 ) {
				$Debug = 0;
				print OUTFILE "#endif\n";
			}
		}
		print OUTFILE "DRV";
	}
	print OUTFILE "\t\t$name;";
	if ( $Drivers{$name}[0] eq "D" or $Drivers{$name}[0] eq "X" ) {
		my $column = int( (9 + length( $name )) / 4 );
		while ( $column < 8 ) {
			print OUTFILE "\t";
			$column ++;
		}
		print OUTFILE "// ";
		if ( $Drivers{$name}[3] ) {
		print OUTFILE "$Drivers{$name}[3]";
		} else {
			print OUTFILE "no comment";
		}
		if ( $Drivers{$name}[8] ) {
			print OUTFILE " [$Drivers{$name}[8]]";
		}
		print OUTFILE "\n";
	} else {
		print OUTFILE "\n";
	}
}
if ( $Debug == 1 ) {
	print OUTFILE "#endif\n";
}

print OUTFILE << "CPPEND";
#undef DRV

// Structure containing addresses of all drivers
// Needs to be kept sorted (using the full game name as the key) to prevent problems with the gamelist in Kaillera
static struct BurnDriver* pDriver[] = {
CPPEND

$Debug = 0;
foreach my $name ( @Driverlist ) {
	if ( $Drivers{$name}[0] eq "X" ) {
		print OUTFILE "//";
	} else {
		if ( $Drivers{$name}[0] eq "D" ) {
			unless ( $Debug == 1) {
				$Debug = 1;
				print OUTFILE "#if defined FBNEO_DEBUG\n";
			}
		} else {
			if ( $Debug == 1 ) {
				$Debug = 0;
				print OUTFILE "#endif\n";
			}
		}
	}
	print OUTFILE "\t&$name,";
	my $column = int( (6 + length( $name )) / 4 );
	while ( $column < 8 ) {
		print OUTFILE "\t";
		$column++;
	}
	print OUTFILE "// $Drivers{$name}[2]";
	if ( $Drivers{$name}[0] eq "D" or $Drivers{$name}[0] eq "X" ) {
		print OUTFILE " [";
		if ( $Drivers{$name}[3] ) {
			print OUTFILE "$Drivers{$name}[3]";
		} else {
			print OUTFILE "no comment";
		}
		if ( $Drivers{$name}[8] ) {
			print OUTFILE ", $Drivers{$name}[8]";
		}
		print OUTFILE "]\n";
	} else {
		if ( $Drivers{$name}[8] ) {
			print OUTFILE " [";
			if ( $Drivers{$name}[3] ) {
				print OUTFILE "$Drivers{$name}[3]";
			} else {
				print OUTFILE "no comment";
			}
			print OUTFILE ", $Drivers{$name}[8]]";
		}
		print OUTFILE "\n";
	}
}
if ( $Debug == 1 ) {
	print OUTFILE "#endif\n";
}

print OUTFILE "};\n";

print OUTFILE << "CPPEND";

// Lookup table containing sourcefiles of all drivers
struct game_sourcefile_entry {
	char game_name[32];
	char sourcefile[32];
};

static game_sourcefile_entry sourcefile_table[] = {
CPPEND


foreach my $name ( @Driverlist ) {
	if ( $Drivers{$name}[1] ne "" ) {
		print OUTFILE "	{ \"$Drivers{$name}[1]\", \"$Drivers{$name}[9]\"},\n";
	}
}

print OUTFILE << "CPPEND";
	{ "\\0", "\\0"}
};
CPPEND


close( OUTFILE );

if ( $Listfile ) {

	open( OUTFILE, ">$Listfile" ) or die "\nError: Couldn't open OUTPUT file $Listfile $!";

	print "Generating gamelist.txt...\n";

	print OUTFILE "This list contains all games supported by FinalBurn Neo.\x0d\x0a\x0d\x0a";

	print OUTFILE scalar @Driverlist . " games supported in total; ";
	print OUTFILE "X = excluded from build; ";
	print OUTFILE "D = included in debug build only; ";
	print OUTFILE "NW = not working\x0d\x0a\x0d\x0a";

	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[1]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[0]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[2]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[7]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[6]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[4]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[5]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[3]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+\x0d\x0a";

	print OUTFILE "| name";
	for (my $i = 0; $i <= $MaxLength[1]-4; $i++) { print OUTFILE " "; }
	print OUTFILE "| status";
	for (my $i = 0; $i <= $MaxLength[0]-6; $i++) { print OUTFILE " "; }
	print OUTFILE "| full name";
	for (my $i = 0; $i <= $MaxLength[2]-9; $i++) { print OUTFILE " "; }
	print OUTFILE "| parent";
	for (my $i = 0; $i <= $MaxLength[7]-6; $i++) { print OUTFILE " "; }
	print OUTFILE "| year";
	for (my $i = 0; $i <= $MaxLength[6]-4; $i++) { print OUTFILE " "; }
	print OUTFILE "| company";
	for (my $i = 0; $i <= $MaxLength[4]-7; $i++) { print OUTFILE " "; }
	print OUTFILE "| hardware";
	for (my $i = 0; $i <= $MaxLength[5]-8; $i++) { print OUTFILE " "; }
	print OUTFILE "| remarks";
	for (my $i = 0; $i <= $MaxLength[3]-7; $i++) { print OUTFILE " "; }
	print OUTFILE "|\x0d\x0a";

	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[1]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[0]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[2]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[7]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[6]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[4]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[5]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[3]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+\x0d\x0a";

	my $Text;

	foreach my $name ( @Driverlist ) {
		my $NeededSpaces;

		print OUTFILE "| $Drivers{$name}[1]";
		$NeededSpaces = int( $MaxLength[1] - length($Drivers{$name}[1]) );
		while ( $NeededSpaces >= 0 ) {
			print OUTFILE " ";
			$NeededSpaces--;
		}

		print OUTFILE "| ";
		$NeededSpaces = int( $MaxLength[0] - 1 );
		if ( $Drivers{$name}[0] ) {
			print OUTFILE "$Drivers{$name}[0]";
		} else {
			print OUTFILE " ";
		}
		if ( $Drivers{$name}[8] ) {
			print OUTFILE " NW";
			$NeededSpaces -= 3;
		}
		while ( $NeededSpaces >= 0 ) {
			print OUTFILE " ";
			$NeededSpaces--;
		}

		print OUTFILE "| $Drivers{$name}[2]";
		$NeededSpaces = int( $MaxLength[2] - length($Drivers{$name}[2]) );
		while ( $NeededSpaces >= 0 ) {
			print OUTFILE " ";
			$NeededSpaces--;
		}

		print OUTFILE "| $Drivers{$name}[7]";
		$NeededSpaces = int( $MaxLength[7] - length($Drivers{$name}[7]) );
		while ( $NeededSpaces >= 0 ) {
			print OUTFILE " ";
			$NeededSpaces--;
		}

		print OUTFILE "| $Drivers{$name}[6]";
		$NeededSpaces = int( $MaxLength[6] - length($Drivers{$name}[6]) );
		while ( $NeededSpaces >= 0 ) {
			print OUTFILE " ";
			$NeededSpaces--;
		}

		print OUTFILE "| $Drivers{$name}[4]";
		$NeededSpaces = int( $MaxLength[4] - length($Drivers{$name}[4]) );
		while ( $NeededSpaces >= 0 ) {
			print OUTFILE " ";
			$NeededSpaces--;
		}

		print OUTFILE "| $Drivers{$name}[5]";
		$NeededSpaces = int( $MaxLength[5] - length($Drivers{$name}[5]) );
		while ( $NeededSpaces >= 0 ) {
			print OUTFILE " ";
			$NeededSpaces--;
		}

		print OUTFILE "| $Drivers{$name}[3]";
		$NeededSpaces = int( $MaxLength[3] - length($Drivers{$name}[3]) );
		while ( $NeededSpaces >= 0 ) {
			print OUTFILE " ";
			$NeededSpaces--;
		}

		print OUTFILE "|\x0d\x0a";
	}

	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[1]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[0]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[2]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[7]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[6]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[4]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[5]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+";
	for (my $i = 0; $i < $MaxLength[3]+2; $i++) { print OUTFILE "-"; }
	print OUTFILE "+\x0d\x0a";

	close( OUTFILE );
}
