/***************************************************************************
 *   Copyright (C) 2020 by Kyle Hayes                                      *
 *   Author Kyle Hayes  kyle.hayes@gmail.com                               *
 *                                                                         *
 * This software is available under either the Mozilla Public License      *
 * version 2.0 or the GNU LGPL version 2 (or later) license, whichever     *
 * you choose.                                                             *
 *                                                                         *
 * MPL 2.0:                                                                *
 *                                                                         *
 *   This Source Code Form is subject to the terms of the Mozilla Public   *
 *   License, v. 2.0. If a copy of the MPL was not distributed with this   *
 *   file, You can obtain one at http://mozilla.org/MPL/2.0/.              *
 *                                                                         *
 *                                                                         *
 * LGPL 2:                                                                 *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 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 Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#ifndef __PLATFORM_H__
#define __PLATFORM_H__

#include <stddef.h>
#include <stdint.h>
#include <math.h>
#include <stdarg.h>

/* common definitions */
#define START_PACK
#define END_PACK __attribute__((__packed__))

#define ZLA_SIZE 0

#define USE_GNU_VARARG_MACROS 1

#ifndef COUNT_NARG
#define COUNT_NARG(...)                                                \
         COUNT_NARG_(__VA_ARGS__,COUNT_RSEQ_N())
#endif

#ifndef COUNT_NARG_
#define COUNT_NARG_(...)                                               \
         COUNT_ARG_N(__VA_ARGS__)
#endif

#ifndef COUNT_ARG_N
#define COUNT_ARG_N(                                                   \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N
#endif

#ifndef COUNT_RSEQ_N
#define COUNT_RSEQ_N()                                                 \
         63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0
#endif


/* memory functions/defs */
extern void *mem_alloc(int size);
extern void *mem_realloc(void *orig, int size);
extern void mem_free(const void *mem);
extern void mem_set(void *dest, int c, int size);
extern void mem_copy(void *dest, void *src, int size);
extern void mem_move(void *dest, void *src, int size);
extern int mem_cmp(void *src1, int src1_size, void *src2, int src2_size);

/* string functions/defs */
extern int str_cmp(const char *first, const char *second);
extern int str_cmp_i(const char *first, const char *second);
extern int str_cmp_i_n(const char *first, const char *second, int num_chars);
extern char *str_str_cmp_i(const char *haystack, const char *needle);
extern int str_copy(char *dst, int dst_size, const char *src);
extern int str_length(const char *str);
extern char *str_dup(const char *str);
extern int str_to_int(const char *str, int *val);
extern int str_to_float(const char *str, float *val);
extern char **str_split(const char *str, const char *sep);
#define str_concat(s1, ...) str_concat_impl(COUNT_NARG(__VA_ARGS__)+1, s1, __VA_ARGS__)
extern char *str_concat_impl(int num_args, ...);

/* mutex functions/defs */
typedef struct mutex_t *mutex_p;
extern int mutex_create(mutex_p *m);
// extern int mutex_lock(mutex_p m);
// extern int mutex_try_lock(mutex_p m);
// extern int mutex_unlock(mutex_p m);
extern int mutex_destroy(mutex_p *m);

extern int mutex_lock_impl(const char *func, int line_num, mutex_p m);
extern int mutex_try_lock_impl(const char *func, int line_num, mutex_p m);
extern int mutex_unlock_impl(const char *func, int line_num, mutex_p m);

#if defined(_WIN32) && defined(_MSC_VER)
    /* MinGW on Windows does not need this. */
    #define __func__ __FUNCTION__
#endif

#define mutex_lock(m) mutex_lock_impl(__func__, __LINE__, m)
#define mutex_try_lock(m) mutex_try_lock_impl(__func__, __LINE__, m)
#define mutex_unlock(m) mutex_unlock_impl(__func__, __LINE__, m)

/* macros are evil */

/*
 * Use this one like this:
 *
 *     critical_block(my_mutex) {
 *         locked_data++;
 *         foo(locked_data);
 *     }
 *
 * The macro locks and unlocks for you.  Derived from ideas/code on StackOverflow.com.
 *
 * Do not use break, return, goto or continue inside the synchronized block if
 * you intend to have them apply to a loop outside the synchronized block.
 *
 * You can use break, but it will drop out of the inner for loop and correctly
 * unlock the mutex.  It will NOT break out of any surrounding loop outside the
 * synchronized block.
 */
#define critical_block(lock) \
for(int __sync_flag_nargle_##__LINE__ = 1; __sync_flag_nargle_##__LINE__ ; __sync_flag_nargle_##__LINE__ = 0, mutex_unlock(lock))  for(int __sync_rc_nargle_##__LINE__ = mutex_lock(lock); __sync_rc_nargle_##__LINE__ == PLCTAG_STATUS_OK && __sync_flag_nargle_##__LINE__ ; __sync_flag_nargle_##__LINE__ = 0)

/* thread functions/defs */
typedef struct thread_t *thread_p;
typedef void *(*thread_func_t)(void *arg);
extern int thread_create(thread_p *t, thread_func_t func, int stacksize, void *arg);
extern void thread_stop(void) __attribute__((noreturn));
extern void thread_kill(thread_p t);
extern int thread_join(thread_p t);
extern int thread_detach();
extern int thread_destroy(thread_p *t);

#define THREAD_FUNC(func) void *func(void *arg)
#define THREAD_RETURN(val) return (void *)val;

#define THREAD_LOCAL __thread

/* atomic operations */
#define spin_block(lock) \
for(int __sync_flag_nargle_lock_##__LINE__ = 1; __sync_flag_nargle_lock_##__LINE__ ; __sync_flag_nargle_lock_##__LINE__ = 0, lock_release(lock))  for(int __sync_rc_nargle_lock_##__LINE__ = lock_acquire(lock); __sync_rc_nargle_lock_##__LINE__ && __sync_flag_nargle_lock_##__LINE__ ; __sync_flag_nargle_lock_##__LINE__ = 0)

typedef int lock_t;

#define LOCK_INIT (0)

/* returns non-zero when lock acquired, zero when lock operation failed */
extern int lock_acquire_try(lock_t *lock);
extern int lock_acquire(lock_t *lock);
extern void lock_release(lock_t *lock);


/* condition variables */
typedef struct cond_t *cond_p;
extern int cond_create(cond_p *c);
extern int cond_wait_impl(const char *func, int line_num, cond_p c, int timeout_ms);
extern int cond_signal_impl(const char *func, int line_num, cond_p c);
extern int cond_clear_impl(const char *func, int line_num, cond_p c);
extern int cond_destroy(cond_p *c);

#define cond_wait(c, t) cond_wait_impl(__func__, __LINE__, c, t)
#define cond_signal(c) cond_signal_impl(__func__, __LINE__, c)
#define cond_clear(c) cond_clear_impl(__func__, __LINE__, c)


/* socket functions */
typedef struct sock_t *sock_p;
typedef enum {
    SOCK_EVENT_NONE         = 0,
    SOCK_EVENT_TIMEOUT      = (1 << 0),
    SOCK_EVENT_DISCONNECT   = (1 << 1),
    SOCK_EVENT_ERROR        = (1 << 2),
    SOCK_EVENT_CAN_READ     = (1 << 3),
    SOCK_EVENT_CAN_WRITE    = (1 << 4),
    SOCK_EVENT_WAKE_UP      = (1 << 5),
    SOCK_EVENT_CONNECT      = (1 << 6),

    SOCK_EVENT_DEFAULT_MASK = (SOCK_EVENT_TIMEOUT | SOCK_EVENT_DISCONNECT | SOCK_EVENT_ERROR | SOCK_EVENT_WAKE_UP )
} sock_event_t;
extern int socket_create(sock_p *s);
extern int socket_connect_tcp_start(sock_p s, const char *host, int port);
extern int socket_connect_tcp_check(sock_p s, int timeout_ms);
extern int socket_wait_event(sock_p sock, int events, int timeout_ms);
extern int socket_wake(sock_p sock);
extern int socket_read(sock_p s, uint8_t *buf, int size, int timeout_ms);
extern int socket_write(sock_p s, uint8_t *buf, int size, int timeout_ms);
extern int socket_close(sock_p s);
extern int socket_destroy(sock_p *s);

/* serial handling */
/* FIXME - either implement this or remove it. */
typedef struct serial_port_t *serial_port_p;
#define PLC_SERIAL_PORT_NULL ((plc_serial_port)NULL)
extern serial_port_p plc_lib_open_serial_port(const char *path, int baud_rate, int data_bits, int stop_bits, int parity_type);
extern int plc_lib_close_serial_port(serial_port_p serial_port);
extern int plc_lib_serial_port_read(serial_port_p serial_port, uint8_t *data, int size);
extern int plc_lib_serial_port_write(serial_port_p serial_port, uint8_t *data, int size);

/* misc functions */
extern int sleep_ms(int ms);
extern int64_t time_ms(void);

#define snprintf_platform snprintf

#endif /* _PLATFORM_H_ */
