# CMakeList.txt : CMake project for OpenDSSC, include source and define
# project specific logic here.
#
cmake_minimum_required (VERSION 3.15)

# Update the version in Current_ver.txt to reflect it in the code (after a rebuild, e.g. "make clean" and "make" on Unix)
file(STRINGS "Current_ver.txt" OPENDSSC_PROJECT_VERSION)
string(REGEX REPLACE "myDSSversion=" "" OPENDSSC_PROJECT_VERSION ${OPENDSSC_PROJECT_VERSION})
project("OpenDSSC" VERSION ${OPENDSSC_PROJECT_VERSION} DESCRIPTION "OpenDSS-C")

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD_REQUIRED ON)

if(MSVC)
	message("Platform assumption: Microsoft Visual C++ (or compatible)")
else()
	message("Platform assumption: Linux or UNIX")
endif()

add_definitions(-DOPENDSSC_PROJECT_VERSION=\"${OPENDSSC_PROJECT_VERSION}\")

# Note: users can run ccmake or cmake-gui to better interact with the options below

# Use the "MyOutputType" variable to RESTRICT what type of output will be generated by the compiler
# By default, the current script will build both the EXE and LIB
# if(NOT DEFINED MyOutputType)
# 	set(MyOutputType "EXE") # Output type, DLL or EXE
# endif()
set(OPENDSSC_EXPERIMENTAL OFF CACHE BOOL "Enable experimental/incomplete code.")

set(OPENDSSC_KLUSOLVEX OFF CACHE BOOL "Enable using KLUSolveX (from DSS-Extensions.org). This enables related code in OpenDSS-C, including incremental sparse matrices and refactorization.")

# BUILD_SHARED_LIBS is not prefixed with OPENDSSC to simplify integration with other tools and libraries
# See https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html
option(BUILD_SHARED_LIBS "Build using shared libraries (DLL); default is ON for OpenDSSC. Set it to OFF to generate a static library." ON)

if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
	# Added by Paulo Meira to avoid issues in complex environments; dedicated executables that
	# don't load libraries from various sources and compilers do not need to enable this.
    set(OPENDSSC_VERSION_SCRIPT OFF CACHE BOOL "Use version script (Linux-only). This avoids exposing symbols that potentially conflict with other libs.")
endif ()

find_package(Threads)

if (NOT OPENDSSC_KLUSOLVEX)
	add_subdirectory(klusolve EXCLUDE_FROM_ALL)
	set(KLUSOLVE_LIB klusolve_all)
	set_target_properties(klusolve_all PROPERTIES
		RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
		ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
		LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
	)
else()
	# If OPENDSSC_KLUSOLVEX is ON and we have a local copy, use it.
	# Search order:
	# 1. Inside the source dir.
	# 2. In the parent of the source dir.
	# 3. Inside the binary dir.
	if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/klusolvex")
		# First check inside the current directory
		add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/klusolvex" "${CMAKE_CURRENT_BINARY_DIR}/klusolvex-build" EXCLUDE_FROM_ALL)
	elseif (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../klusolvex")
		# Then check in the parent directory
		add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../klusolvex" "${CMAKE_CURRENT_BINARY_DIR}/klusolvex-build" EXCLUDE_FROM_ALL)
	elseif (EXISTS "${CMAKE_CURRENT_BINARY_DIR}/klusolvex")
		# Then check in the parent directory
		add_subdirectory("${CMAKE_CURRENT_BINARY_DIR}/klusolvex" "${CMAKE_CURRENT_BINARY_DIR}/klusolvex-build" EXCLUDE_FROM_ALL)
	else() 
		# Otherwise, grab it from GitHub
		# FetchContent is used to download the project from a specific tag
		include(FetchContent)

		FetchContent_Declare(
			klusolvex
			GIT_REPOSITORY git@github.com:dss-extensions/klusolve.git
			GIT_TAG cfec146d3cd6363f25cedccb89c0a1b1a45e995e
		)

		FetchContent_MakeAvailable(klusolvex)
	endif()
	set(KLUSOLVE_LIB klusolvex)
	set_target_properties(klusolvex PROPERTIES
		RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
		ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
		LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
	)
	add_definitions(-DOPENDSSC_KLUSOLVEX)
	add_definitions(-DDSS_EXTENSIONS_INCREMENTAL_Y)
endif()

set(OPENDSSC_COMMON_FILES
	Support/d2c_system.cpp
	Support/d2c_syscurr.cpp
	Support/d2c_sysstring.cpp
	Support/d2c_sysmath.cpp
	Support/d2c_sysdate.cpp
	Support/d2c_sysexcept.cpp
	Support/d2c_sysfile.cpp
	Support/Sysutils.cpp
	Support/d2c_structures.cpp
	Shared/Arraydef.cpp
	Shared/CktTree.cpp
	Shared/Command.cpp
	Shared/Dynamics.cpp
	Shared/InvDynamics.cpp
	Shared/HashList.cpp
	Shared/IniRegSave.cpp
	Shared/LineUnits.cpp
	Shared/mathutil.cpp
	Shared/PointerList.cpp
	Shared/Pstcalc.cpp
	Shared/StackDef.cpp
	Shared/Ucmatrix.cpp
	Shared/Ucomplex.cpp
	Parser/RPN.cpp
	Parser/ParserDel.cpp
	Common/AutoAdd.cpp
	Common/Bus.cpp
	Common/Circuit.cpp
	Common/CktElement.cpp
	Common/Conductor.cpp
	Common/CktElementClass.cpp
	Common/ControlQueue.cpp
	Common/Diakoptics.cpp
	Common/DSSCallBackRoutines.cpp
	Common/DSSClass.cpp
	Common/DSSClassDefs.cpp
	Common/DSSGlobals.cpp
	Common/ExportCIMXML.cpp
	Common/ExportResults.cpp
	Common/Feeder.cpp
	Common/MeTIS_Exec.cpp
	Common/ShowResults.cpp
	Common/Solution.cpp
	Common/SolutionAlgs.cpp
	Common/Sparse_Math.cpp
	Common/Terminal.cpp
	Common/Utilities.cpp
	Common/YMatrix.cpp
	Forms/CmdForms.cpp
	GISCommands/GISCommands.cpp
	Executive/ConnectOptions.cpp
	Executive/ExecCommands.cpp
	Executive/ExecHelper.cpp
	Executive/ExecOptions.cpp
	Executive/Executive.cpp
	Executive/ExportOptions.cpp
	Executive/ShowOptions.cpp
	Executive/PlotOptions.cpp
	Meters/MeterClass.cpp
	Meters/EnergyMeter.cpp
	Meters/fMonitor.cpp
	Meters/MemoryMap_lib.cpp
	Meters/MeterElement.cpp
	Meters/Monitor.cpp
	Meters/ReduceAlgs.cpp
	Meters/Sensor.cpp
	Meters/VLNodeVars.cpp
	Controls/CapControl.cpp
	Controls/CapControlVars.cpp
	Controls/CapUserControl.cpp
	Controls/ControlClass.cpp
	Controls/ControlElem.cpp
	Controls/ESPVLControl.cpp
	Controls/ExpControl.cpp
	Controls/GenDispatcher.cpp
	Controls/InvControl.cpp
	Controls/pyControl.cpp
	Controls/Recloser.cpp
	Controls/RegControl.cpp
	Controls/Relay.cpp
	Controls/StorageController.cpp
	Controls/SwtControl.cpp
	Controls/UPFCControl.cpp
	PCElements/Equivalent.cpp
	PCElements/generator.cpp
	PCElements/GeneratorVars.cpp
	PCElements/Generic5OrderMach.cpp
	PCElements/GenUserModel.cpp
	PCElements/GICLine.cpp
	PCElements/GICsource.cpp
	PCElements/IndMach012.cpp
	PCElements/Isource.cpp
	PCElements/Load.cpp
	PCElements/PCClass.cpp
	PCElements/PCElement.cpp
	PCElements/PVsystem.cpp
	PCElements/PVSystemUserModel.cpp
	PCElements/Storage.cpp
	PCElements/StorageVars.cpp
	PCElements/StoreUserModel.cpp
	PCElements/UPFC.cpp
	PCElements/vccs.cpp
	PCElements/VSConverter.cpp
	PCElements/VSource.cpp
	PCElements/WindGen.cpp
	PCElements/WindGenUserModel.cpp
	PCElements/WindGenVars.cpp
	PCElements/WTG3_Model.cpp
	PDElements/AutoTrans.cpp
	PDElements/Capacitor.cpp
	PDElements/Fault.cpp
	PDElements/fuse.cpp
	PDElements/GICTransformer.cpp
	PDElements/Line.cpp
	PDElements/PDClass.cpp
	PDElements/PDElement.cpp
	PDElements/Reactor.cpp
	PDElements/Transformer.cpp
	General/CableConstants.cpp
	General/CableData.cpp
	General/CNData.cpp
	General/CNLineConstants.cpp
	General/ConductorData.cpp
	General/DSSObject.cpp
	General/DynamicExp.cpp
	General/GrowthShape.cpp
	General/LineCode.cpp
	General/LineConstants.cpp
	General/LineGeometry.cpp
	General/LineSpacing.cpp
	General/LoadShape.cpp
	General/NamedObject.cpp
	General/OHLineConstants.cpp
	General/PriceShape.cpp
	General/Spectrum.cpp
	General/TCC_Curve.cpp
	General/TempShape.cpp
	General/TSData.cpp
	General/TSLineConstants.cpp
	General/WireData.cpp
	General/XfmrCode.cpp
	General/XYcurve.cpp
	General/CNTSLineConstants.cpp
	PCElements/PCElement.cpp
	MyOpenDSS/MyDSSClassDefs.cpp 
	Plot/DSSPlot.cpp
	Plot/DSSGraph.cpp
	PCElements/WTG3_Model.h
)

if (OPENDSSC_EXPERIMENTAL)
	# Add experimental/incomplete files here
	# This avoids including them in the default builds
	set(OPENDSSC_EXPERIMENTAL_FILES
		GenModels/gencls.cpp
		GenModels/genrou.cpp
		GenControls/Exciter/ExcSexs.cpp
		GenControls/Governor/tgov.cpp
		# Shared/IntegrationMethods.cpp
	)
	add_definitions(-DOPENDSSC_EXPERIMENTAL)
else()
	set(OPENDSSC_EXPERIMENTAL_FILES "")
endif()

add_library(OpenDSSC_Common OBJECT ${OPENDSSC_COMMON_FILES} ${OPENDSSC_EXPERIMENTAL_FILES})

if(MyOutputType STREQUAL "EXE")
	SET(OPENDSSC_BUILD_EXE ON)
	SET(OPENDSSC_BUILD_LIB OFF)
	SET(OPENDSSC_TARGETS OpenDSSC_Common OpenDSSC_EXE)
elseif(MyOutputType STREQUAL "DLL" OR MyOutputType STREQUAL "SHARED")
	SET(OPENDSSC_BUILD_EXE OFF)
	SET(OPENDSSC_BUILD_LIB ON)
	SET(OPENDSSC_TARGETS OpenDSSC_Common OpenDSSC_LIB)
else()
	SET(OPENDSSC_BUILD_EXE ON)
	SET(OPENDSSC_BUILD_LIB ON)
	SET(OPENDSSC_TARGETS OpenDSSC_Common OpenDSSC_EXE OpenDSSC_LIB)
endif()

set(OPENDSSC_COMMON_INC_DIRS
	Common
	Controls
	Executive
	Forms
	General
	GISCommands
	Meters
	MyOpenDSS
	Parser
	PCElements
	PDElements
	Shared
	Support
	MyOpenDSS
	GenModels
	GenControls/Exciter
	GenControls/Governor
	Plot
)
if((MSVC) OR (MINGW))
	# Windows
	set(OPENDSSC_COMMON_LIBS
		${CMAKE_THREAD_LIBS_INIT}
		${KLUSOLVE_LIB}
	)
else()
	# Linux
	# NOTE: iconv seems to be required on some recent compilers.
	#       From the GCC bugtracker and tickets from other projects, 
	#       it could be a bug in the compiler setup, but linking iconv
	#		is enough to work around it.
	set(OPENDSSC_COMMON_LIBS
		${CMAKE_THREAD_LIBS_INIT}
		${KLUSOLVE_LIB}
		stdc++fs
		dl
		uuid
		# iconv
	)
endif()


set(OPENDSSC_COMPILE_OPTIONS
	$<$<CXX_COMPILER_ID:MSVC>:
		# Options for MSVC:
		/permissive-
	>
	$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:GNU>>:
		# Options for Clang or GCC:
		# -fms-extensions
		$<$<CONFIG:Debug>:
			-Wall
			-Wextra
			# These are temporarilly disabled:
			-Wno-unused-variable
			-Wno-unused-parameter
			-Wno-sign-compare
			-Wno-unused-private-field
			-Wno-unused-const-variable

			# Options for Clang-only:
			$<$<CXX_COMPILER_ID:Clang>:
			# Purposefully fill uninitialized variables with a pattern, to help catch errors faster:
			-ftrivial-auto-var-init=pattern
			# Enable Clang's Address Sanitizer: https://clang.llvm.org/docs/AddressSanitizer.html
			-fsanitize=address
			$<$<VERSION_GREATER_EQUAL:${CMAKE_CXX_COMPILER_VERSION},13.0.0>:
			-fsanitize-address-use-after-return=always
			>
			-fsanitize-address-use-after-scope
			# Strive for understandable stack traces by disabling a few optimizations:
			-fno-omit-frame-pointer
			-fno-inline-functions
			-fno-optimize-sibling-calls
			>
		>
	>
)

if(OPENDSSC_BUILD_EXE)
	add_executable(OpenDSSC_EXE
		CMD/OpenDSSC.cpp
		$<TARGET_OBJECTS:OpenDSSC_Common>
	)
	add_dependencies(OpenDSSC_EXE OpenDSSC_Common)
	# Name the EXE as "OpenDSSCcmd" to mirror the Delphi EXE name (OpenDSScmd.exe)
	set_target_properties(OpenDSSC_EXE PROPERTIES OUTPUT_NAME OpenDSSCcmd)
endif()

if(OPENDSSC_BUILD_LIB)
	if(NOT MSVC)
		add_definitions(-fPIC)
	endif()

	add_library(OpenDSSC_LIB
		DLL/OpenDSSCDLL.cpp
		$<TARGET_OBJECTS:OpenDSSC_Common>
	)
	add_dependencies(OpenDSSC_LIB OpenDSSC_Common)
	# Keep the library/DLL name as OpenDSSC
	set_target_properties(OpenDSSC_LIB PROPERTIES OUTPUT_NAME OpenDSSC)

	if(OPENDSSC_VERSION_SCRIPT AND BUILD_SHARED_LIBS)
		# The version script in "opendssc.map" restricts the exported symbols and provides a version to the shared object
		target_link_options(OpenDSSC_LIB PRIVATE "-Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/opendssc.map")
	endif()
endif()

foreach(_tgt ${OPENDSSC_TARGETS})
	if(MSVC)
		# target_compile_options(${_tgt} PRIVATE /fsanitize=address)

		# Use the static MSVC runtime, to avoid requiring the installation of the runtime binary
		set_target_properties(${_tgt} PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
	else()
		# Define _D2C_SYSFILE_H_LONG_IS_INT64 if long is the same as int64_t for d2c_sysfile.{h,cpp}
		# TODO: How may _D2C_SYSFILE_H_LONG_IS_INT64 be checked at compile time?  The
		# cleanest way would be to rewrite that mess with a few C++ templates.
		#
		# _COMPLEX_DEFINED is to prevent klusolve.h from defining complex with a
		# conflicting set of names.
		target_compile_definitions(${_tgt} PRIVATE
			_D2C_SYSFILE_H_LONG_IS_INT64
			_COMPLEX_DEFINED
		)
	endif()

	target_compile_options(${_tgt} PRIVATE ${OPENDSSC_COMPILE_OPTIONS})
	target_include_directories(${_tgt} PRIVATE ${OPENDSSC_COMMON_INC_DIRS})
	target_link_libraries(${_tgt} ${OPENDSSC_COMMON_LIBS})
	install(TARGETS ${_tgt} DESTINATION "openDSSC/bin")

#	target_link_options(OpenDSSC PRIVATE
#		$<$<CXX_COMPILER_ID:Clang>:
#			$<$<CONFIG:Debug>:
#				# Clang's Address Sanitizer also needs this link switch: https://clang.llvm.org/docs/AddressSanitizer.html
#				# -fsanitize=address
#			>
#		>
#	)
endforeach()
