#!/usr/bin/env perl

use strict;
use warnings;
use POSIX qw(strftime);
use JSON;
use File::Basename;

#my $cmake = "/home/pboettch/devel/upstream/cmake/build/bin/cmake";
my $cmake = "cmake";

my @variables;
my @commands;
my @properties;
my @modules;
my %keywords; # command => keyword-list

# find cmake/Modules/ | sed -rn 's/.*CMakeDetermine(.+)Compiler.cmake/\1/p' | sort
my @languages = qw(ASM ASM_MASM ASM_NASM C CSharp CUDA CXX Fortran Java RC Swift HIP);

# unwanted upper-cases
my %unwanted = map { $_ => 1 } qw(VS CXX IDE NOTFOUND NO_ DFOO DBAR NEW GNU);
	# cannot remove ALL - exists for add_custom_command

# control-statements
my %conditional = map { $_ => 1 } qw(if else elseif endif);
my %loopetc = map { $_ => 1 } qw(block foreach function macro while endblock endfunction endforeach endmacro endwhile);

# decrecated
my %deprecated = map { $_ => 1 } qw(build_name exec_program export_library_dependencies install_files install_programs install_targets link_libraries make_directory output_required_files remove subdir_depends subdirs use_mangled_mesa utility_source variable_requires write_file);

# add some (popular) modules
push @modules, "ExternalProject", "FetchContent";

# variables
open(CMAKE, "$cmake --help-variable-list|") or die "could not run cmake";
while (<CMAKE>) {
	chomp;

	if (/<(.*?)>/) {
		if ($1 eq 'LANG') {
			foreach my $lang (@languages) {
			(my $V = $_) =~ s/<.*>/$lang/;
				push @variables, $V;
			}

			next
		} else {
			next; # skip if containing < or >
		}
	}

	push @variables, $_;
}
close(CMAKE);

# transform all variables in a hash - to be able to use exists later on
my %variables = map { $_ => 1 } @variables;

# commands
open(CMAKE, "$cmake --help-command-list|");
while (my $cmd = <CMAKE>) {
	chomp $cmd;
	push @commands, $cmd;
}
close(CMAKE);

# now generate a keyword-list per command
foreach my $cmd (@commands) {
	my @word = extract_upper("$cmake --help-command $cmd|");

	next if scalar @word == 0;

	$keywords{$cmd} = [ sort keys %{ { map { $_ => 1 } @word } } ];
}

# and now for modules
foreach my $mod (@modules) {
	my @word = extract_upper("$cmake --help-module $mod|");

	next if scalar @word == 0;

	$keywords{$mod} = [ sort keys %{ { map { $_ => 1 } @word } } ];
}

# and now for generator-expressions
my @generator_expr = extract_upper("$cmake --help-manual cmake-generator-expressions |");

# properties
open(CMAKE, "$cmake --help-property-list|");
while (<CMAKE>) {
	next if /\</; # skip if containing < or >
	chomp;
	push @properties, $_;
}
close(CMAKE);

# transform all properties in a hash
my %properties = map { $_ => 1 } @properties;

# read in manually written files
my $modules_dir =  dirname(__FILE__) . "/modules";
opendir(DIR, $modules_dir) || die "can't opendir $modules_dir: $!";
my @json_files = grep { /\.json$/ && -f "$modules_dir/$_" } readdir(DIR);
closedir DIR;

foreach my $file (@json_files) {
	local $/; # Enable 'slurp' mode
	open my $fh, "<", $modules_dir."/".$file;
	my $json = <$fh>;
	close $fh;

	my $mod = decode_json($json);
	foreach my $var (@{$mod->{variables}}) {
		$variables{$var} = 1;
	}

	while (my ($cmd, $keywords) = each %{$mod->{commands}}) {
		$keywords{$cmd} = [ sort @{$keywords} ];
	}
}

# version
open(CMAKE, "$cmake --version|");
my $version = 'unknown';
while (<CMAKE>) {
	chomp;
	$version = $_ if /cmake version/;
}
close(CMAKE);

# generate cmake.vim
open(IN,  "<cmake.vim.in") or die "could not read cmake.vim.in";
open(OUT, ">syntax/cmake.vim") or die "could not write to syntax/cmake.vim";

my @keyword_hi;

while(<IN>)
{
	if (m/\@([A-Z0-9_]+)\@/) { # match for @SOMETHING@
		if ($1 eq "COMMAND_LIST") {
			# do not include "special" commands in this list
			my @tmp = grep { ! exists $conditional{$_} and
			                 ! exists $loopetc{$_} and
			                 ! exists $deprecated{$_} } @commands;
			print_list(\*OUT, @tmp);
		} elsif ($1 eq "VARIABLE_LIST") {
			print_list(\*OUT, keys %variables);
		} elsif ($1 eq "MODULES") {
			print_list(\*OUT, @modules);
		} elsif ($1 eq "GENERATOR_EXPRESSIONS") {
			print_list(\*OUT, @generator_expr);
		} elsif ($1 eq "DEPRECATED") {
			print_list(\*OUT, keys %deprecated);
		} elsif ($1 eq "PROPERTIES") {
			print_list(\*OUT, keys %properties);
		} elsif ($1 eq "KEYWORDS") {
			foreach my $k (sort keys %keywords) {
				print OUT "syn keyword cmakeKW$k contained\n";
				print_list(\*OUT, @{$keywords{$k}});
				print OUT "\n";
				push @keyword_hi, "hi def link cmakeKW$k ModeMsg";
			}
		} elsif ($1 eq "KEYWORDS_HIGHLIGHT") {
			print OUT join("\n", @keyword_hi), "\n";
		} elsif ($1 eq "VERSION") {
			$_ =~ s/\@VERSION\@/$version/;
			print OUT $_;
		} elsif ($1 eq "DATE") {
			my $date = strftime "%Y %b %d", localtime;
			$_ =~ s/\@DATE\@/$date/;
			print OUT $_;
		} else {
			print "ERROR do not know how to replace $1\n";
		}
	} else {
		print OUT $_;
	}
}
close(IN);
close(OUT);

sub extract_upper
{
	my $input = shift;
	my @word;

	open(KW, $input);
	while (<KW>) {
		foreach my $w (m/\b([A-Z_]{2,})\b/g) {
			next
				if exists $variables{$w} or  # skip if it is a variable
				   exists $unwanted{$w} or   # skip if not wanted
				   grep(/$w/, @word);     # skip if already in array

			push @word, $w;
		}
	}
	close(KW);

	return @word;
}

sub print_list
{
	my $O = shift;
	my $indent = " " x 12 . "\\ ";
	print $O $indent, join("\n" . $indent, sort @_), "\n";
}
