# git $Id$
# svn $Id: roms_functions.cmake 1202 2023-10-24 15:36:07Z arango $
#:::::::::::::::::::::::::::::::::::::::::::::::::::::: David Robertson :::
# Copyright (c) 2002-2023 The ROMS/TOMS Group                           :::
#   Licensed under a MIT/X style license                                :::
#   See License_ROMS.md                                                 :::
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#
# Functions used in CMake to overcome its rudimentary capabilities.

find_program(
  GREP
  NAMES grep egrep
  DOC "Grep command"
  REQUIRED
)

find_program(
  PERL
  NAMES perl
  DOC "Perl command"
  REQUIRED
)

find_program(
  CPP_CLEAN
  NAMES cpp_clean
  HINTS "${CMAKE_CURRENT_SOURCE_DIR}/ROMS/Bin/"
  DOC "ROMS CPP Clean command"
  REQUIRED
)

find_program(
  CPP_EXECUTABLE
  NAMES cpp
  DOC "C-preprocessor command"
  REQUIRED
)

message( STATUS "CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}" )
message( STATUS "CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}" )
message( STATUS "CPP_CLEAN = ${CPP_CLEAN}" )

###########################################################################
# The "preprocess_fortran" function teaches CMake how to use CPP to
# create .f90 files from ROMS .F source files. It is imperative that
# we generate processed .f90 files BEFORE CMake attempts to determine
# Fortran dependencies because its Fortran dependency tracker is
# inconsistent and easily confused by C-preprocessor nested
# if-directives.
###########################################################################

# First, make the directory for the .f90 files.

file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/f90")

function(preprocess_fortran)

   # Determine include directories.

   get_directory_property(incdirs INCLUDE_DIRECTORIES)
   set(incflags)
   foreach(i ${incdirs})
     list(APPEND incflags "-I${i}")
   endforeach()

   # Set-up "defines" (not configuration specific).

   get_directory_property(defines COMPILE_DEFINITIONS)
   set(defflags)
   foreach(d ${defines})
     list(APPEND defflags "-D${d}")
   endforeach()

   # Loop over all arguments.

   set(fsrcs)
   foreach(f ${ARGV})

     # Is it a Fortran file?

     if(f MATCHES "\\.[Ff](9[05])?")
       message(STATUS "Got fortran file: ${f}")

       # Get special definitions for file, if any.

       set(fdefflags)
       if(${f} MATCHES ".*mod_strings.*")
         get_property(fdefines
                      SOURCE ${f}
                      PROPERTY COMPILE_DEFINITIONS
         )
         foreach(fd ${fdefines})
           list(APPEND fdefflags "-D${fd}")
         endforeach()
       endif()

       # Construct output filename.

       if(NOT IS_ABSOLUTE "${f}")
         get_filename_component(f "${f}" ABSOLUTE)
       endif()
       file(RELATIVE_PATH r "${CMAKE_CURRENT_SOURCE_DIR}" "${f}")
       get_filename_component(e "${r}" EXT)
       get_filename_component(n "${r}" NAME_WE)
       get_filename_component(p "${r}" PATH)
       set(of "${CMAKE_CURRENT_BINARY_DIR}/f90/${n}.f90")
       message(STATUS "Output name: ${of}")

       # Preprocess the thing.

       add_custom_command(OUTPUT "${of}"
         COMMAND ${CPP_EXECUTABLE} ${CPPFLAGS} ${defflags} ${fdefflags} ${incflags} "${f}" > "${of}"
         COMMAND ${CPP_CLEAN} "${of}"
         IMPLICIT_DEPENDS Fortran "${f}"
         COMMENT "Preprocessing ${f}"
         COMMAND_EXPAND_LISTS
         VERBATIM
       )
       list(APPEND fsrcs "${of}")
     else()
       list(APPEND fsrcs "${f}")
     endif()
   endforeach()

   # Return the (preprocessed) sources.

   set(f90srcs "${fsrcs}" PARENT_SCOPE)
endfunction()

###########################################################################
# The "get_options" function is used by roms_config.cmake to determine if
# the adjoint, tangent linear, and/or representer model(s) are needed
# and whether to add ARPACK/PARPACK and/or parallel I/O using SCORPIO
# to link into the executable.
###########################################################################

function(get_options roms_header)

  # Set ROMS_HEADER C-Preprocessor flag.

  set( APP_HEADER -DROMS_HEADER="${roms_header}" )

  # If MY_CPP_FLAGS was set and passed, cycle through the list and add the -D.

  set(defflags)
  foreach(d ${ARGN})
    list(APPEND defflags "-D${d}")
  endforeach()

  execute_process(
    COMMAND ${CPP_EXECUTABLE} ${CPPFLAGS} ${APP_HEADER} ${defflags} ${CMAKE_CURRENT_SOURCE_DIR}/Compilers/defs_cmake.h
    COMMAND ${GREP} -v ^[[:space:]]*\$
    COMMAND_ECHO STDOUT
    RESULT_VARIABLE status
    ERROR_VARIABLE err
    OUTPUT_VARIABLE models_libs
  )

  # Check whether the cpp command above produced an error and stop CMake if it did.

  if( status AND NOT status EQUAL 0 )
    message( FATAL_ERROR "get_options error: ${err}" )
  endif()

  # Return needed Models/Libraries.

  set(defs "${models_libs}" PARENT_SCOPE)
endfunction()

###########################################################################
# Determine how to link to NetCDF
###########################################################################

function(link_netcdf)
  if( DEFINED ENV{NF_CONFIG} )
    Message( STATUS "Using $ENV{NF_CONFIG}" )

    # Get link line to break down below

    execute_process( COMMAND $ENV{NF_CONFIG} --flibs
                     OUTPUT_VARIABLE tmp
    )

    if( DEFINED ENV{NETCDF_INCDIR} )
      set( idir "$ENV{NETCDF_INCDIR}" )                          # Set include directory
    else()

      # Retrieve include direcotry

      execute_process( COMMAND $ENV{NF_CONFIG} --includedir
                       OUTPUT_VARIABLE idir
      )
    endif()

    string( STRIP "${tmp}" linkline )                            # Strip trailing whitespace
    string( REGEX MATCHALL "-L[^ \t]*" ldirs ${linkline} )       # Create list of dirs
    string( REGEX MATCHALL "-l[^ \t]*" libs ${linkline} )        # Create list of libs

  elseif( DEFINED ENV{NETCDF_LIBS} )
    string( REGEX MATCHALL "-L[^ \t]*" ldirs $ENV{NETCDF_LIBS} ) # Create list of dirs
    string( REGEX MATCHALL "-l[^ \t]*" libs $ENV{NETCDF_LIBS} )  # Create list of libs
    if( DEFINED ENV{NETCDF_INCDIR} )
      set( idir  "$ENV{NETCDF_INCDIR}" )                         # Set include directory
    elseif( DEFINED ENV{NETCDF} )
      set( idir  "$ENV{NETCDF}/include" )                        # Construct include path
    else()
      Message( FATAL_ERROR "NetCDF includes not found!" )
    endif()
  else()
    if( DEFINED ENV{NETCDF_INCDIR} AND DEFINED ENV{NETCDF_LIBDIR} )
      set( ldirs "$ENV{NETCDF_LIBDIR}" )                         # Set lib directory
      set( libs  "netcdf" )                                      # Set NetCDF3 lib name
      set( idir  "$ENV{NETCDF_INCDIR}" )                         # Set include directory
    else()
      Message( FATAL_ERROR "No NetCDF found!" )
    endif()
  endif()

  list( TRANSFORM libs  REPLACE "-l([^ \t]*)" "\\1" )           # remove "-l" from libs
  list( TRANSFORM ldirs REPLACE "-L([^ \t]*)" "\\1" )           # remove "-L" from dirs

  if( "${libs}" STREQUAL "" )
    message( FATAL_ERROR "NetCDF library name not found" )
  endif()

  set( netcdf_ldirs "${ldirs}" PARENT_SCOPE )
  set( netcdf_libs "${libs}" PARENT_SCOPE )
  set( netcdf_idir "${idir}" PARENT_SCOPE )

  Message( STATUS " netcdf_libs = ${libs}" )
  Message( STATUS "netcdf_ldirs = ${ldirs}" )
  Message( STATUS " netcdf_idir = ${idir}" )
endfunction()