#!/usr/bin/env perl
#
# Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
# 
# This program 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.
# 
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
# ===
#
# make2nmake
#
# Parses the autoconf/automake artifacts throughout the source tree
# and creates a set of 'nmake' files.
#
# The generated nmake makefiles are created in the current working
# directory and sub-directories mirroring the source tree. This
# can be a completely separate from the source.
#
# usage: make2nmake [options]
#          --source <dir>
#          --no-mbedtls
#          --openssl
#          --no-gui
#          --qt6
#
# Run from a 'vcvars' "developer command prompt".
#
# When running "nmake" the third-party library paths have default
# values that can be overriden in via environment variables (using
# "nmake /e") or via an optional "nmake.cfg" file.
#
# All build artifacts will be statically-linked and the third-party
# libraries must be built accordingly.
#
# Windows:
#   $ PATH=%PATH%;c:\perl\bin
#   $ ...\vc\auxiliary\build\vcvarsall x64
#   $ perl .../emailrelay/libexec/make2nmake
#   $ set MBEDTLS_INC=.../include
#   $ set MBEDTLS_RLIB=.../lib
#   $ set QT_INC=.../include
#   $ set QT_LIB=.../lib
#   $ set QT_MOC=.../moc.exe
#   $ nmake /e
#

use strict ;
use FileHandle ;
use File::Spec ;
use File::Basename ;
use lib dirname($0) ;
use BuildInfo ;

package nmake ;

sub new
{
	my ( $classname , $source_dir ) = @_ ;
	$source_dir ||= "." ;
	return bless {
		m_config => "release" ,
		m_source_dir => $source_dir ,
		m_mbedtls_inc => "\$(EXT_ROOT)/mbedtls/include" , # see mbedtlsbuild.pl
		m_mbedtls_rlib => "\$(EXT_ROOT)/mbedtls-\$(ARCH)/library/release" ,
		m_mbedtls_dlib => "\$(EXT_ROOT)/mbedtls-\$(ARCH)/library/debug" ,
		m_openssl_inc => "\$(EXT_ROOT)/openssl/include" ,
		m_openssl_rlib => "\$(EXT_ROOT)/openssl-\$(ARCH)-release/library" ,
		m_openssl_dlib => "\$(EXT_ROOT)/openssl-\$(ARCH)-debug/library" ,
		m_qt_inc => "\$(EXT_ROOT)/qt-\$(ARCH)/include" , # see qtbuild.pl
		m_qt_lib => "\$(EXT_ROOT)/qt-\$(ARCH)/lib" ,
		m_qt_moc => "\$(EXT_ROOT)/qt-\$(ARCH)/bin/moc.exe" ,
	} , $classname ;
}

sub create_nmake_file
{
	my ( $this , $m ) = @_ ; # see BuildInfo.pm

	my $makefile_name = "makefile" ;
	_log( "nmake-file=[$$m{e_dir}/$makefile_name]" ) ;

	mkdir $m->{e_dir} if( $m->{e_dir} ne "." ) ;
	my $fh = new FileHandle( "$$m{e_dir}/$makefile_name" , "w" ) or die ;
	print $fh "# $$m{e_dir}/$makefile_name -- generated by $0\n" ;

	my $depth = $m->depth() ;
	my $ext_root = ( "../" x ($depth+1) ) ;
	$ext_root =~ s;\/$;; ;
	my $build_root = ( "../" x ($depth+0) ) ;
	$build_root =~ s;\/$;; ;
	$build_root ||= "." ;

	my $src_root = $this->{m_source_dir} ;
	$src_root =~ s;\\;/;g ;
	if( !File::Spec->file_name_is_absolute($src_root) )
	{
		$src_root = ( "../" x $depth ) . $src_root ;
	}

	my %cl_options = (
		common => "/nologo /DWIN32 /D_WINDOWS /DGCONFIG_NO_GCONFIG_DEFS /EHsc /I." ,
		debug => "/D_DEBUG /DG_WITH_DEBUG /DG_WITH_ASSERT /W4 /MTd /Zi /Ob0 /Od" ,
		release => "/DNDEBUG /W3 /MT /O2 /Ob2"
	) ;
	my %link_options = (
		debug => [ "/DEBUG" ] ,
		release => [ "/RELEASE" , "/DYNAMICBASE" , "/NXCOMPAT" , "/INCREMENTAL:no" , "/OPT:ref" ] ,
	) ;

	print $fh "\n" ;
	print $fh "ARCH = x64\n" ;
	print $fh "CONFIG = release\n" ;
	print $fh "SRC_ROOT = $src_root\n" ;
	print $fh "OBJ = \$(CONFIG)/\n" ;
	print $fh "EXE = \$(CONFIG)/\n" ;
	print $fh "EXT_ROOT = $ext_root\n" ;
	print $fh "MBEDTLS_INC = $$this{m_mbedtls_inc}\n" if $m->{e_with_mbedtls} ;
	print $fh "MBEDTLS_DLIB = $$this{m_mbedtls_dlib}\n" if $m->{e_with_mbedtls} ;
	print $fh "MBEDTLS_RLIB = $$this{m_mbedtls_rlib}\n" if $m->{e_with_mbedtls} ;
	print $fh "OPENSSL_INC = $$this{m_openssl_inc}\n" if $m->{e_with_openssl} ;
	print $fh "OPENSSL_DLIB = $$this{m_openssl_dlib}\n" if $m->{e_with_openssl} ;
	print $fh "OPENSSL_RLIB = $$this{m_openssl_rlib}\n" if $m->{e_with_openssl} ;
	print $fh "QT_INC = $$this{m_qt_inc}\n" if $m->{e_with_qt} ;
	print $fh "QT_LIB = $$this{m_qt_lib}\n" if $m->{e_with_qt} ;
	print $fh "QT_MOC = $$this{m_qt_moc}\n" if $m->{e_with_qt} ;
	print $fh "CPPFLAGS_EXTRA = \n" ;

	print $fh "\n" ;
	print $fh "!if exist($build_root/nmake.cfg)\n" ;
	print $fh "!include $build_root/nmake.cfg\n" ;
	print $fh "!elseif exist($ext_root/emailrelay-nmake.cfg)\n" ;
	print $fh "!include $ext_root/emailrelay-nmake.cfg\n" ;
	print $fh "!endif\n" ;

	my @includes = @{$m->{e_includes}} ;

	my %all_objects = () ;
	{
		for my $x ( @{$m->{e_libraries}} )
		{
			my $e = $m->{e_library}->{$x} ;
			my @objects = map {my $x=$_;$x=~ s/\.o$/.obj/ ;$x} @{$e->{objects}} ;
			map { $all_objects{$_} = $e->{libname} } @objects ;
		}
		for my $x ( @{$m->{e_programs}} )
		{
			my $e = $m->{e_program}->{$x} ;
			my @objects = map {my $x=$_;$x=~ s/\.o$/.obj/ ;$x} @{$e->{objects}} ;
			map { $all_objects{$_} = $e->{progname} } @objects ;
		}
	}

	if( scalar(%all_objects) )
	{
		my @cpp_includes =
			map {my $x=$_;$x =~ s;^[\./]*/(src/.*);\$(SRC_ROOT)/$1;g ;$x}
			grep {$_ ne "."}
			grep {$_ ne ".."}
			@includes ;

		push @cpp_includes , "\$(MBEDTLS_INC)" if $m->{e_need_mbedtls_inc} ;
		push @cpp_includes , "\$(OPENSSL_INC)" if $m->{e_need_openssl_inc} ;
		push @cpp_includes , "\$(QT_INC)" if $m->{e_need_qt_inc} ;

		my @cpp_definitions = @{$m->{e_definitions}} ;
		push @cpp_definitions , "G_QT_STATIC" if $m->{e_is_gui_dir} ;

		print $fh "\n" ;
		print $fh "CPPIFLAGS = " , join(" ",map {"/I$_"} @cpp_includes) , "\n" ;
		print $fh "CPPDFLAGS = " , join(" ",map {"/D$_"} @cpp_definitions) , "\n" ;
		print $fh "\n" ;
		print $fh "CPPFLAGS_COMMON = $cl_options{common} \$(CPPIFLAGS) \$(CPPDFLAGS) \$(CPPFLAGS_EXTRA)\n" ;
		print $fh "\n" ;
		print $fh "CPPFLAGS_DEBUG = \$(CPPFLAGS_COMMON) $cl_options{debug}\n" ;
		print $fh "\n" ;
		print $fh "CPPFLAGS_RELEASE = \$(CPPFLAGS_COMMON) $cl_options{release}\n" ;
		print $fh "\n" ;
	}
	if( scalar(@{$m->{e_programs}}) )
	{
		print $fh "LINKFLAGS_DEBUG = " , join(" ",@{$link_options{debug}}) , "\n" ;
		print $fh "LINKFLAGS_RELEASE = " , join(" ",@{$link_options{release}}) , "\n" ;
	}

	if( $m->{e_need_qt_libs} )
	{
		my @libs_debug = map { "\$(QT_LIB)/${_}.lib" }
			@{$m->{e_qt_libnames_debug}} ,
			@{$m->{e_qt_static_libnames_debug}} ;
		my @libs_release = map { "\$(QT_LIB)/${_}.lib" }
			@{$m->{e_qt_libnames_release}} , @{$m->{e_qt_static_libnames_release}} ;
		print $fh "\n" ;
		print $fh "QT_LIBS_DEBUG = " , join(" ",@libs_debug) , "\n\n" ;
		print $fh "QT_LIBS_RELEASE = " , join(" ",@libs_release) , "\n" ;
	}

	if( scalar(@{$m->{e_programs}}) || scalar(%all_objects) )
	{
		print $fh "\n" ;
		print $fh "!if \"\$(CONFIG)\" == \"release\"\n" ;
		print $fh "MBEDTLS_LIB = \$(MBEDTLS_RLIB)\n" if $m->{e_need_mbedtls_libs} ;
		print $fh "OPENSSL_LIB = \$(OPENSSL_RLIB)\n" if $m->{e_need_openssl_libs} ;
		print $fh "QT_LIBS = \$(QT_LIBS_RELEASE)\n" if $m->{e_need_qt_libs} ;
		print $fh "CPPFLAGS = \$(CPPFLAGS_RELEASE)\n" if scalar(%all_objects) ;
		print $fh "LINKFLAGS = \$(LINKFLAGS_RELEASE)\n" if scalar(%all_objects) ;
		print $fh "!else\n" ;
		print $fh "MBEDTLS_LIB = \$(MBEDTLS_DLIB)\n" if $m->{e_need_mbedtls_libs} ;
		print $fh "OPENSSL_LIB = \$(OPENSSL_DLIB)\n" if $m->{e_need_openssl_libs} ;
		print $fh "QT_LIBS = \$(QT_LIBS_DEBUG)\n" if $m->{e_need_qt_libs} ;
		print $fh "CPPFLAGS = \$(CPPFLAGS_DEBUG)\n" if scalar(%all_objects) ;
		print $fh "LINKFLAGS = \$(LINKFLAGS_DEBUG)\n" if scalar(%all_objects) ;
		print $fh "!endif\n" ;
	}

	print $fh "\n" ;
	print $fh "# DEFAULT TARGET...\n\n" ;
	print $fh "all: init subdirs libs programs\n" ;

	print $fh "init:\n" ;
	if( scalar(%all_objects) )
	{
		print $fh "    -mkdir \"\$(OBJ)\" 2>NUL:\n" ;
	}

	print $fh "\n" ;
	print $fh "# SUBDIRS...\n\n" ;
	print $fh "subdirs:\n" ;
	for my $subdir ( @{$m->{e_subdirs}} )
	{
		print $fh "    cd $subdir && \$(MAKE) /\$(MAKEFLAGS) -f $makefile_name\n" ;
	}

	print $fh "\n" ;
	print $fh "# LIBRARIES...\n\n" ;
	print $fh "libs: " , join(" ",map {"\$(OBJ)$_.lib"} grep {$_ !~ m/extra$/} @{$m->{e_libraries}}) , "\n" ;
	for my $libname ( @{$m->{e_libraries}} )
	{
		my $e = $m->{e_library}->{$libname} ;
		my $c = scalar(@{$e->{sources}}) == 0 ? "#" : "" ;
		my @objects = map { "\$(OBJ)$_" } map {my $x=$_;$x=~ s/\.o$/.obj/ ;$x} @{$e->{objects}} ;
		print $fh "\n" ;
		print $fh "\$(OBJ)$$e{libname}.lib: " , join(" ",@objects) , "\n" ;
		print $fh "    link /lib /nologo /out:\$(OBJ)$$e{libname}.lib " , join(" ",@objects) , "\n" ;
	}

	print $fh "\n" ;
	print $fh "# PROGRAMS...\n\n" ;
	print $fh "programs: " , join(" ",map {"\$(EXE)$_.exe"} @{$m->{e_programs}}) , "\n" ;
	for my $progname ( @{$m->{e_programs}} )
	{
		my $e = $m->{e_program}->{$progname} ;

		my $rcfile = $e->{rcfile} ? "\$(SRC_ROOT)/$$m{e_dir}/$$e{rcfile}" : "" ;
		my $mcfile = $e->{messages_mc} ? "\$(SRC_ROOT)/$$m{e_dir}/$$e{messages_mc}" : "" ;
		( my $resfile = $$e{rcfile} ) =~ s/\.rc$/.res/ ;
		if( $e->{rcfile} )
		{
			my @rc_includes =
				map {"-i $_"}
				map {my $x=$_;$x =~ s;^[\./]*/(src/.*);\$(SRC_ROOT)/$1;g ;$x}
				grep {$_ ne "."}
				grep {$_ ne ".."}
				@includes ;

			my @rc_options = ( "/nologo" , @rc_includes , "-fo" , $resfile ) ;
			my $bin = "msg00001.bin" if $mcfile ;
			print $fh "\n" ;
			print $fh "$resfile: $rcfile $bin\n" ;
			print $fh "    \$(RC) " , join(" ",@rc_options) , " $rcfile\n" ;
		}
		if( $mcfile )
		{
			print $fh "\n" ;
			print $fh "msg00001.bin: $mcfile\n" ;
			print $fh "    mc $mcfile\n" ;
		}
		if( scalar(@{$e->{moc_in}}) )
		{
			for my $i ( 0 .. (scalar(@{$e->{moc_in}})-1) )
			{
				my $moc_in_name = @{$e->{moc_in}}[$i] ;
				my $moc_in_file = "\$(SRC_ROOT)/$$m{e_dir}/$moc_in_name" ;
				my $moc_out = @{$e->{moc_out}}[$i] ;
				( my $moc_user = $moc_in_file ) =~ s/\.h$/.cpp/ ;
				print $fh "\n" ;
				print $fh "$moc_out: $moc_in_file\n" ;
				print $fh "    \$(QT_MOC:/=\\) -o $moc_out $moc_in_file\n" ;
				print $fh "\n" ;
				print $fh "$moc_user: $moc_out\n" ;
			}
		}

		my @objects = map { "\$(OBJ)$_" } map {my $x=$_;$x=~ s/\.o$/.obj/ ;$x} @{$e->{objects}} ;
		my @our_libs = map {my ($d,$n)=@$_;"$d/\$(OBJ)$n.lib"} @{$e->{our_libpairs}} ;
		my @mbedtls_libs = map { "\$(MBEDTLS_LIB)/$_.lib" } @{$m->{e_mbedtls_libnames}} ;
		my @openssl_libs = map { "\$(OPENSSL_LIB)/$_.lib" } @{$m->{e_openssl_libnames}} ;
		my @libs = @our_libs ;
		push @libs , @mbedtls_libs if $e->{need_mbedtls_libs} ;
		push @libs , @openssl_libs if $e->{need_openssl_libs} ;
		push @libs , '$(QT_LIBS)' if $e->{need_qt_libs} ;
		my @rhs = ( @objects , $resfile ) ; # maybe with @libs
		my @linkables = @objects ;
		push @linkables , $resfile if $resfile ;
		push @linkables , @libs ;
		push @linkables , map { "$_.lib" } @{$e->{qt_static_sys_libnames}} if $e->{need_qt_libs} ;
		push @linkables , map {"$_.lib"} @{$e->{sys_libnames}} ;
		my @link_options = "\$(LINKFLAGS)" ;
		( my $manifest = "\$(SRC_ROOT)/$$m{e_dir}/$$e{manifest}" ) =~ s;/;\\;g ;
		push @link_options , "/MANIFEST:embed" ;
		push @link_options , "/MANIFESTINPUT:$manifest" if $e->{manifest} ; # see also $$e{uac_option}
		push @link_options , "/MANIFESTUAC:no" if $e->{manifest} ; # (our manifests have a UAC stanza)
		push @link_options , "/SUBSYSTEM:$$e{subsystem}" if $e->{subsystem} ;
		push @link_options , $e->{commoncontrols_option} if $e->{uses_commoncontrols} ;

		print $fh "\n" ;
		print $fh "\$(EXE)$$e{progfile}: " , join(" ",@rhs) , "\n" ;
		print $fh "    link /nologo " ,
			join(" ",(@link_options,"")) ,
			"/out:\$(EXE)$$e{progfile} " ,
			join(" ",@linkables) , "\n" ;
		print $fh "    certutil -hashfile \$(EXE)$$e{progfile} SHA256 > \$(EXE)$$e{progfile}.sha256\n" ;
	}

	print $fh "\n" ;
	if( scalar(%all_objects) )
	{
		print $fh "# COMPILATION...\n\n" ;
	}
	my $our_src_dir = "\$(SRC_ROOT)/$$m{e_dir}" ;
	for my $object ( sort keys %all_objects )
	{
		( my $srcname = $object ) =~ s/\.obj$/.cpp/ ;
		print $fh "\$(OBJ)$object: $our_src_dir/$srcname\n" ;
		print $fh "    \$(CPP) \$(CPPFLAGS) /Fo:\$(OBJ)$object /c \$?\n" ;
		print $fh "\n" ;
	}

	print $fh "# CLEAN...\n\n" ;
	print $fh "clean:\n" ;
	for my $obj ( sort keys %all_objects )
	{
		print $fh "    -del /Q \$(OBJ:/=\\)$obj\n" ;
	}
	for my $libname ( @{$m->{e_libraries}} )
	{
		my $e = $m->{e_library}->{$libname} ;
		print $fh "    -del /Q \$(OBJ:/=\\)$$e{libname}.lib\n" ;
	}
	for my $progname ( @{$m->{e_programs}} )
	{
		my $e = $m->{e_program}->{$progname} ;
		print $fh "    -del /Q \$(EXE:/=\\)$$e{progfile}\n" ;
	}
	for my $subdir ( @{$m->{e_subdirs}} )
	{
		print $fh "    cd $subdir && \$(MAKE) /\$(MAKEFLAGS) -f $makefile_name clean\n" ;
	}

	print $fh "\n" ;
	$fh->close() or die ;
}

sub _log
{
	print File::Basename::basename($0) , ": " , @_ , "\n" ;
}

1 ;

# ==

package main ;
use Getopt::Long ;
use Cwd ;

if( basename($0) eq "make2nmake" )
{
	my %opt = () ;
	if( !GetOptions( \%opt , "help|h" ,
		"source=s" , "no-mbedtls" , "openssl" , "no-gui" , "qt6" ,
		"for-windows|w" , "debug-dump" , "debug-dump-all" ) || $opt{help} )
	{
		my $error = $opt{help} ? "" : "error: " ;
		print "${error}usage: make2nmake [<options>]\n" ;
		print "          --source=<dir>   emailrelay root directory\n" ;
		print "          --no-mbedtls     without mbedtls\n" ;
		print "          --openssl        with openssl\n" ;
		print "          --no-gui         without qt\n" ;
		print "          --qt6            qt version 6\n" ;
		print "          --for-windows\n" if( $^O eq "linux" ) ;
		print "          --debug-dump\n" ;
		print "          --debug-dump-all\n" ;
		exit( $error ? 1 : 0 ) ;
	}

	my $opt_source = $opt{source} ;
	if( !$opt_source && ( basename(dirname($0)) eq "libexec" ) )
	{
		$opt_source = dirname( dirname($0) ) ;
	}
	elsif( !$opt_source )
	{
		$opt_source = "." ;
	}

	my $opt_for_windows = $opt{'for-windows'} ;
	if( !exists($opt{'for-windows'}) )
	{
		$opt_for_windows = ( $^O ne "linux" ) ;
	}

	my $opt_qt_version = $opt{qt6} ? 6 : 5 ; # see also winbuild::qt_version()

	my $cwd = cwd() ;
	chdir( $opt_source ) or die "cannot cd to [$opt_source]" ;
	my @m = BuildInfo::read_makefiles( "." , "make2nmake: " ,
		{
			windows => $opt_for_windows ,
			windows_mbedtls => !$opt{'no-mbedtls'} ,
			windows_openssl => $opt{openssl} ,
			windows_gui => !$opt{'no-gui'} ,
			qt_version => $opt_qt_version ,
			verbose => 0 , # AutoMakeParser::readall()
		} ) ;
	chdir( $cwd ) or die ;
	if( $opt{'debug-dump'} ) { BuildInfo::dump( @m ) ; exit(0) }
	if( $opt{'debug-dump-all'} ) { BuildInfo::dumpall( @m ) ; exit(0) }

	my $nmake = new nmake( $opt_source ) ;
	for my $m ( @m )
	{
		$nmake->create_nmake_file( $m ) ;
	}
}
else
{
	1 ;
}

