################################################################################
#
# Copyright 2016 Intel Corporation
#
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

##
# 'libcoap' script to check if LibCoAP library is installed.  If not,
# get it and install it
#
##

import os
import glob
import datetime
import subprocess

Import('env')

libcoap_env = env.Clone()
target_os = libcoap_env.get('TARGET_OS')
src_dir = libcoap_env.get('SRC_DIR')
ca_transport = libcoap_env.get('TARGET_TRANSPORT')
with_tcp = libcoap_env.get('WITH_TCP')
with_upstream_libcoap = libcoap_env.get('WITH_UPSTREAM_LIBCOAP')

# Temporary LibCoAP URL is a fork of the original.
# Once a pull request is merged, change this back to the obgm original below.
libcoap_repo_url    = 'https://github.com/dthaler/libcoap'
#libcoap_repo_url    = 'https://github.com/obgm/libcoap'

######################################################################
# Download libCoAP
######################################################################
# Right now this script assumes the revision is a tag, and not a branch or an arbitrary
# commit. If this changes, update the check below, or else the script will always conclude
# the repo is not up to date because a tag with that name doesn't exist.
libcoap_version          = 'IoTivity-1.2.1d'
libcoap_dir              = os.path.join(src_dir, 'extlibs', 'libcoap', 'libcoap')
libcoap_checkout_command = 'git clone ' + libcoap_repo_url + '.git extlibs/libcoap/libcoap -b ' + libcoap_version
libcoap_update_command = 'git fetch --tags && git checkout -f ' + libcoap_version
libcoap_change_dir_command = 'cd ' + libcoap_dir

if with_upstream_libcoap == '1':
    print '*** Checking for installation of libCoAP ***'
    if not os.path.exists(libcoap_dir):
        print '''
*********************************** Error: ****************************************
* Please download libcoap using the following command:                            *
*     $ %s
***********************************************************************************
 ''' % libcoap_checkout_command
        Exit(1)

    os.chdir(libcoap_dir)
    out = subprocess.check_output('git log --format=%d -n 1', shell = True)
    if libcoap_version not in out:
        print '''
*********************************** Info: *****************************************
* Your libCoAP repo is not up to date with the latest version we require (%s).
***********************************************************************************
 ''' %(libcoap_version)
        if libcoap_env.get('AUTOMATIC_UPDATE'):
            print '''
*********************************** Info: *****************************************
* Automatically updating libcoap to version %s.
***********************************************************************************
 ''' %(libcoap_version)
            subprocess.check_output(libcoap_update_command, shell = True)
        else:
            print '''
*********************************** Info: *****************************************
* Please update using the following commands:
*   %s
*   %s
***********************************************************************************
 ''' %(libcoap_change_dir_command, libcoap_update_command)
            Exit(1)

else:
    print '''
*********************************** Info: *****************************************
* Using FORKED copy of libCoap located in:                                        *
* resource/csdk/connectivity/lib/libcoap-4.1.1                                    *
***********************************************************************************'''

######################################################################
# Build libCoAP
######################################################################
# As in the source code(C) includes arduino Time library head file(C++)
# It requires compile the .c with g++
if target_os == 'arduino':
    libcoap_env.Replace(CFLAGS = libcoap_env.get('CXXFLAGS'))
    libcoap_env.PrependUnique(CPPPATH = [
        './',
        env.get('ARDUINO_HOME') + '/',
        env.get('ARDUINO_HOME') + '/hardware/arduino/avr/cores/arduino',
        env.get('ARDUINO_HOME') + '/hardware/tools/avr/avr/include/',
        env.get('ARDUINO_HOME') + '/hardware/arduino/avr/variants/mega',
        env.get('ARDUINO_HOME') + '/libraries/Time/Time',
        env.get('ARDUINO_HOME') + '/libraries/TimedAction',
        env.get('ARDUINO_HOME') + '/hardware/arduino/avr/libraries/SPI',
        env.get('ARDUINO_HOME') + '/libraries/Ethernet/src/utility',
        ])

# Build flags
if target_os not in ['arduino', 'windows', 'winrt', 'msys_nt']:
    libcoap_env.AppendUnique(CPPDEFINES = ['WITH_POSIX', '_DEFAULT_SOURCE'])
    libcoap_env.AppendUnique(CFLAGS = ['-std=gnu99','-fPIC'])

if target_os not in ['windows', 'winrt']:
    libcoap_env.AppendUnique(CFLAGS = ['-Wall', '-ffunction-sections',
            '-fdata-sections', '-fno-exceptions'])

if target_os == 'msys_nt':
    libcoap_env.AppendUnique(CPPDEFINES = ['_DEFAULT_SOURCE'])
    libcoap_env.AppendUnique(CFLAGS = ['-std=c99'])

if target_os in ['linux', 'tizen', 'android', 'ios', 'arduino', 'windows']:
    if with_tcp == True:
        libcoap_env.AppendUnique(CPPDEFINES = ['WITH_TCP'])

if target_os in ['linux', 'tizen', 'android', 'arduino']:
    libcoap_env.AppendUnique(LIBS = ['log'])
    if (('BLE' in ca_transport) or ('BT' in ca_transport) or ('ALL' in ca_transport)):
        libcoap_env.AppendUnique(CPPDEFINES = ['WITH_TCP'])

if target_os == 'arduino':
    libcoap_env.AppendUnique(CPPDEFINES = ['NDEBUG', 'WITH_ARDUINO'])

if target_os in ['darwin', 'ios']:
    libcoap_env.AppendUnique(CPPDEFINES = ['_DARWIN_C_SOURCE'])

if env.get('LOGGING') == '1':
    libcoap_env.AppendUnique(CPPDEFINES = ['TB_LOG'])

######################################################################
# Source files and Target(s)
######################################################################
with_upstream_libcoap = libcoap_env.get('WITH_UPSTREAM_LIBCOAP')
config_h_file_path = os.path.join(src_dir, 'extlibs', 'libcoap', 'libcoap', 'include', 'coap', 'coap_config.h')
if with_upstream_libcoap == '1':
    libcoap_env.PrependUnique(CPPPATH = ['libcoap/include/coap'])
    libcoap_env.AppendUnique(CPPDEFINES = ['WITH_UPSTREAM_LIBCOAP'])
    libcoap_src_root = os.path.join(libcoap_dir, 'src')

    # We need to generate coap.h from coap.h.in
    coap_h_pc_file = os.path.join(libcoap_dir, 'include', 'coap', 'coap.h.in')
    coap_h_output  = os.path.join(libcoap_dir, 'include', 'coap', 'coap.h')

    # Generate coap_config.h
    target_arch = env.get('TARGET_ARCH')
    lib_prefix = '' + str(libcoap_env.get('PREFIX'))

    if (not os.path.isfile(config_h_file_path)) and (not env.GetOption('clean')):
        conf = Configure(libcoap_env.Clone(LIBS = []))

        config_h_header = '''
/* ****************************************************************************
 * coap_config.h - libcoap platform-specific configuration header.
 *
 * Auto-generated code for the %s %s platform.
 *
 *************************************************************************** */

#ifndef _COAP_CONFIG_H_
#define _COAP_CONFIG_H_

''' % (str(target_os), str(target_arch))

        config_h_footer = '''

/* Define to the full name of this package. */
#define PACKAGE_NAME "%s"

/* Define to the full name and version of this package. */
#define PACKAGE_STRING "%s"

#ifndef COAP_STATIC_INLINE
#  if defined(__cplusplus)
#    define COAP_STATIC_INLINE inline
#  else
#    ifdef _MSC_VER
#      define COAP_STATIC_INLINE static __inline
#    else
#      define COAP_STATIC_INLINE static inline
#    endif
#  endif
#endif

#endif // _COAP_CONFIG_H_

    ''' % (str(lib_prefix + 'coap'), str(lib_prefix + 'coap ' + libcoap_version))

        config_h_body = ''

        if ((target_os == 'windows') and (libcoap_env.get('UWP_APP') == '1')):
            # Workaround for libcoap config [Investigation in IOT-2234]:
            # libcoap builds its config file by trying to create a small program to see if an API is
            # available. However, when building with store libraries on windows, it doesn't seem
            # to find malloc or strnlen APIs. On Windows, those APIs are guaranteed to be there,
            # therefore, create the libcoap config_h_body with what is needed and expected to be
            # there.
            config_h_body = '''
#define HAVE_ASSERT_H 1

#define HAVE_LIMITS_H 1

#define HAVE_STDIO_H 1

#define HAVE_SYS_TYPES_H 1

#define HAVE_TIME_H 1

#define HAVE_WINSOCK2_H 1

#define HAVE_WS2TCPIP_H 1

#define HAVE_MALLOC 1

#define HAVE_STRNLEN 1

#define ssize_t SSIZE_T

#define in_port_t uint16_t
'''
        else:
            cxx_headers = ['arpa/inet.h',
                           'assert.h',
                           'limits.h',
                           'netinet/in.h',
                           'stdio.h',
                           'strings.h',
                           'sys/select.h',
                           'sys/socket.h',
                           'sys/time.h',
                           'sys/types.h',
                           'sys/uio.h',
                           'sys/unistd.h',
                           'syslog.h',
                           'time.h',
                           'unistd.h',
                           'winsock2.h',
                           'ws2tcpip.h']

            cxx_functions = ['malloc',
                             'snprintf',
                             'strnlen',
                             'vprintf']

            if target_os == 'arduino':
                # Detection of headers on the Arduino platform is currently broken.
                cxx_headers = []

            def get_define_from_string(string):
                string_converted = string.replace("/","_").replace(".","_").upper()
                return "HAVE_" + string_converted

            for header_file_name in cxx_headers:
                if conf.CheckCXXHeader(header_file_name):
                    config_h_body += "#define %s 1\n\n" % get_define_from_string(header_file_name)

            for function_name in cxx_functions:
                if conf.CheckFunc(function_name):
                    config_h_body += "#define %s 1\n\n" % get_define_from_string(function_name)

            if conf.CheckCXXHeader('windows.h'):
                config_h_body += "#define ssize_t SSIZE_T\n\n"
                config_h_body += "#define in_port_t uint16_t\n\n"

            conf.Finish()

            # Autoconf feature doesn't work with Jenkins' arduino toolchain, so hardcode it here.
            if target_os == 'arduino':
                config_h_body += "#define HAVE_ARDUINO_TIME_H\n\n"

        # Generate the file
        if os.path.exists(config_h_file_path):
            os.remove(config_h_file_path)
        config_h_file = open(config_h_file_path, "w")
        config_h_file.write(config_h_header + config_h_body + config_h_footer)
        config_h_file.close()

    # Sanity check to ensure that the above block created the file.
    if (not os.path.exists(config_h_file_path)) and (not env.GetOption('clean')):
        print "Error: coap_config.h file not created!"
        Exit(1)

    pc_vars = {
        '\@LIBCOAP_PACKAGE_NAME\@'          : lib_prefix + 'coap',
        '\@LIBCOAP_PACKAGE_STRING\@'        : lib_prefix + 'coap-' + libcoap_version,
        '\@LIBCOAP_PACKAGE_URL\@'           : libcoap_repo_url,
        '\@LIBCOAP_PACKAGE_BUGREPORT\@'     : libcoap_repo_url + '/issues',
        '\@LIBCOAP_PACKAGE_VERSION\@'       : libcoap_version
        }
    libcoap_env.Substfile(coap_h_pc_file, SUBST_DICT = pc_vars)

    libcoap_src = glob.glob(os.path.join(libcoap_src_root, '*.c'))
    libcoap_src.remove(os.path.join(libcoap_src_root, 'coap_io_lwip.c'))
else:
    # For bring up purposes only, the forked version will live here.
    libcoap_src_root = src_dir + '/resource/csdk/connectivity/lib/libcoap-4.1.1'
    libcoap_src = glob.glob(os.path.join(libcoap_src_root, '*.c'))

if libcoap_env['CC'] == 'cl':
    libcoap_env.AppendUnique(CCFLAGS = ['/W4', '/WX'])

libcoap = libcoap_env.StaticLibrary('coap', libcoap_src, OBJPREFIX='libcoap_')

Clean(libcoap, config_h_file_path)
libcoap_env.InstallTarget([libcoap], 'coap')

