/*
  +----------------------------------------------------------------------+
  | Swoole                                                               |
  +----------------------------------------------------------------------+
  | This source file is subject to version 2.0 of the Apache license,    |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.apache.org/licenses/LICENSE-2.0.html                      |
  | If you did not receive a copy of the Apache2.0 license and are unable|
  | to obtain it through the world-wide-web, please send a note to       |
  | license@swoole.com so we can mail you a copy immediately.            |
  +----------------------------------------------------------------------+
  | Author: Tianfeng Han  <mikan.tenny@gmail.com>                        |
  +----------------------------------------------------------------------+
*/

#pragma once

#include "php_swoole.h"
#include "php_swoole_coroutine.h"
#include "swoole_util.h"

#include <string>

// clang-format off
//----------------------------------Swoole known string------------------------------------

#define SW_ZEND_KNOWN_STRINGS(_) \
    _(SW_ZEND_STR_TYPE,                     "type") \
    _(SW_ZEND_STR_HOST,                     "host") \
    _(SW_ZEND_STR_PORT,                     "port") \
    _(SW_ZEND_STR_SETTING,                  "setting") \
    _(SW_ZEND_STR_ID,                       "id") \
    _(SW_ZEND_STR_FD,                       "fd") \
    _(SW_ZEND_STR_SOCK,                     "sock") \
    _(SW_ZEND_STR_PIPE,                     "pipe") \
    _(SW_ZEND_STR_HEADERS,                  "headers") \
    _(SW_ZEND_STR_SET_COOKIE_HEADERS,       "set_cookie_headers") \
    _(SW_ZEND_STR_REQUEST_METHOD,           "requestMethod") \
    _(SW_ZEND_STR_REQUEST_HEADERS,          "requestHeaders") \
    _(SW_ZEND_STR_REQUEST_BODY,             "requestBody") \
    _(SW_ZEND_STR_UPLOAD_FILES,             "uploadFiles") \
    _(SW_ZEND_STR_COOKIES,                  "cookies") \
    _(SW_ZEND_STR_DOWNLOAD_FILE,            "downloadFile") \
    _(SW_ZEND_STR_DOWNLOAD_OFFSET,          "downloadOffset") \
    _(SW_ZEND_STR_TMPFILES,                 "tmpfiles") \
    _(SW_ZEND_STR_HEADER,                   "header") \
    _(SW_ZEND_STR_COOKIE,                   "cookie") \
    _(SW_ZEND_STR_METHOD,                   "method") \
    _(SW_ZEND_STR_PATH,                     "path") \
    _(SW_ZEND_STR_DATA,                     "data") \
    _(SW_ZEND_STR_PIPELINE,                 "pipeline") \
    _(SW_ZEND_STR_USE_PIPELINE_READ,        "usePipelineRead") \
    _(SW_ZEND_STR_TRAILER,                  "trailer") \
    _(SW_ZEND_STR_MASTER_PID,               "master_pid") \
    _(SW_ZEND_STR_CALLBACK,                 "callback") \
    _(SW_ZEND_STR_VALUE,                    "value") \
    _(SW_ZEND_STR_KEY,                      "key") \
    _(SW_ZEND_STR_OPCODE,                   "opcode") \
    _(SW_ZEND_STR_CODE,                     "code") \
    _(SW_ZEND_STR_REASON,                   "reason") \
    _(SW_ZEND_STR_FLAGS,                    "flags") \
    _(SW_ZEND_STR_FINISH,                   "finish") \

typedef enum sw_zend_known_string_id {
#define _SW_ZEND_STR_ID(id, str) id,
SW_ZEND_KNOWN_STRINGS(_SW_ZEND_STR_ID)
#undef _SW_ZEND_STR_ID
    SW_ZEND_STR_LAST_KNOWN
} sw_zend_known_string_id;

// clang-format on

#define SW_ZSTR_KNOWN(idx) sw_zend_known_strings[idx]
extern zend_string **sw_zend_known_strings;

//----------------------------------Swoole known string------------------------------------

#define SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(module)                                                              \
    module##_ce->create_object = [](zend_class_entry *ce) { return sw_zend_create_object(ce, &module##_handlers); }


SW_API bool php_swoole_is_enable_coroutine();
SW_API zend_object *php_swoole_create_socket_from_fd(int fd, enum swSocket_type type);
SW_API bool php_swoole_export_socket(zval *zobject, swoole::coroutine::Socket *_socket);
SW_API zend_object *php_swoole_dup_socket(int fd, enum swSocket_type type);
SW_API void php_swoole_init_socket_object(zval *zobject, swoole::coroutine::Socket *socket);
SW_API swoole::coroutine::Socket *php_swoole_get_socket(zval *zobject);
#ifdef SW_USE_OPENSSL
SW_API bool php_swoole_socket_set_ssl(swoole::coroutine::Socket *sock, zval *zset);
#endif
SW_API bool php_swoole_socket_set_protocol(swoole::coroutine::Socket *sock, zval *zset);
SW_API bool php_swoole_client_set(swoole::coroutine::Socket *cli, zval *zset);
SW_API php_stream *php_swoole_create_stream_from_socket(php_socket_t _fd, int domain, int type, int protocol STREAMS_DC);
SW_API php_stream_ops *php_swoole_get_ori_php_stream_stdio_ops();
SW_API void php_swoole_register_rshutdown_callback(swoole::Callback cb, void *private_data);

// timer
SW_API bool php_swoole_timer_clear(swoole::TimerNode *tnode);
SW_API bool php_swoole_timer_clear_all();

static inline bool php_swoole_is_fatal_error() {
    if (PG(last_error_message)) {
        switch (PG(last_error_type)) {
        case E_ERROR:
        case E_CORE_ERROR:
        case E_USER_ERROR:
        case E_COMPILE_ERROR:
            return true;
        default:
            break;
        }
    }
    return false;
}

ssize_t php_swoole_length_func(swoole::Protocol *protocol, swoole::network::Socket *_socket, const char *data, uint32_t length);

#ifdef SW_HAVE_ZLIB
#define php_swoole_websocket_frame_pack        php_swoole_websocket_frame_pack_ex
#define php_swoole_websocket_frame_object_pack php_swoole_websocket_frame_object_pack_ex
#else
#define php_swoole_websocket_frame_pack(buffer, zdata, opcode, flags, mask, allow_compress) \
        php_swoole_websocket_frame_pack_ex(buffer, zdata, opcode, flags, mask, 0)
#define php_swoole_websocket_frame_object_pack(buffer, zdata, mask, allow_compress) \
        php_swoole_websocket_frame_object_pack_ex(buffer, zdata, mask, 0)
#endif
int php_swoole_websocket_frame_pack_ex(swoole::String *buffer, zval *zdata, zend_long opcode, uint8_t flags, zend_bool mask, zend_bool allow_compress);
int php_swoole_websocket_frame_object_pack_ex(swoole::String *buffer, zval *zdata, zend_bool mask, zend_bool allow_compress);
void php_swoole_websocket_frame_unpack(swoole::String *data, zval *zframe);
void php_swoole_websocket_frame_unpack_ex(swoole::String *data, zval *zframe, uchar allow_uncompress);

swoole::TaskId php_swoole_task_pack(swoole::EventData *task, zval *data);
zval* php_swoole_task_unpack(swoole::EventData *task_result);

#ifdef SW_HAVE_ZLIB
int php_swoole_zlib_decompress(z_stream *stream, swoole::String *buffer, char *body, int length);
#endif

const swoole::Allocator *sw_php_allocator();
const swoole::Allocator *sw_zend_string_allocator();

namespace zend {
//-----------------------------------namespace begin--------------------------------------------
class String {
  public:
    String() {
        str = nullptr;
    }

    String(const char *_str, size_t len) {
        str = zend_string_init(_str, len, 0);
    }

    String(const std::string &_str) {
        str = zend_string_init(_str.c_str(), _str.length(), 0);
    }

    String(zval *v) {
        str = zval_get_string(v);
    }

    String(zend_string *v, bool copy) {
        if (copy) {
            str = zend_string_copy(v);
        } else {
            str = v;
        }
    }

    String(const String &o) {
        str = zend_string_copy(o.str);
    }

    String(String &&o) {
        str = o.str;
        o.str = nullptr;
    }

    void operator=(zval *v) {
        if (str) {
            zend_string_release(str);
        }
        str = zval_get_string(v);
    }

    String &operator=(String &&o) {
        str = o.str;
        o.str = nullptr;
        return *this;
    }

    String &operator=(const String &o) {
        str = zend_string_copy(o.str);
        return *this;
    }

    inline char *val() {
        return ZSTR_VAL(str);
    }

    inline size_t len() {
        return ZSTR_LEN(str);
    }

    inline zend_string *get() {
        return str;
    }

    void rtrim() {
        ZSTR_LEN(str) = swoole::rtrim(val(), len());
    }

    inline const std::string to_std_string() {
        return std::string(val(), len());
    }

    inline char *dup() {
        return sw_likely(len() > 0) ? sw_strndup(val(), len()) : nullptr;
    }

    inline char *edup() {
        return sw_likely(len() > 0) ? estrndup(val(), len()) : nullptr;
    }

    inline void release() {
        if (str) {
            zend_string_release(str);
            str = nullptr;
        }
    }

    ~String() {
        release();
    }

  private:
    zend_string *str;
};

class KeyValue {
  public:
    zend_ulong index;
    zend_string *key;
    zval zvalue;

    KeyValue(zend_ulong _index, zend_string *_key, zval *_zvalue) {
        index = _index;
        key = _key ? zend_string_copy(_key) : nullptr;
        ZVAL_DEREF(_zvalue);
        zvalue = *_zvalue;
        Z_TRY_ADDREF(zvalue);
    }

    inline void add_to(zval *zarray) {
        HashTable *ht = Z_ARRVAL_P(zarray);
        zval *dest_elem = !key ? zend_hash_index_update(ht, index, &zvalue) : zend_hash_update(ht, key, &zvalue);
        Z_TRY_ADDREF_P(dest_elem);
    }

    ~KeyValue() {
        if (key) {
            zend_string_release(key);
        }
        zval_ptr_dtor(&zvalue);
    }
};

class ArrayIterator {
  public:
    ArrayIterator(Bucket *p) {
        _ptr = p;
        _key = _ptr->key;
        _val = &_ptr->val;
        _index = _ptr->h;
        pe = p;
    }
    ArrayIterator(Bucket *p, Bucket *_pe) {
        _ptr = p;
        _key = _ptr->key;
        _val = &_ptr->val;
        _index = _ptr->h;
        pe = _pe;
        skipUndefBucket();
    }
    void operator++(int i) {
        ++_ptr;
        skipUndefBucket();
    }
    bool operator!=(ArrayIterator b) {
        return b.ptr() != _ptr;
    }
    std::string key() {
        return std::string(_key->val, _key->len);
    }
    zend_ulong index() {
        return _index;
    }
    zval *value() {
        return _val;
    }
    Bucket *ptr() {
        return _ptr;
    }

  private:
    void skipUndefBucket() {
        while (_ptr != pe) {
            _val = &_ptr->val;
            if (_val && Z_TYPE_P(_val) == IS_INDIRECT) {
                _val = Z_INDIRECT_P(_val);
            }
            if (UNEXPECTED(Z_TYPE_P(_val) == IS_UNDEF)) {
                ++_ptr;
                continue;
            }
            if (_ptr->key) {
                _key = _ptr->key;
                _index = 0;
            } else {
                _index = _ptr->h;
                _key = nullptr;
            }
            break;
        }
    }

    zval *_val;
    zend_string *_key;
    Bucket *_ptr;
    Bucket *pe;
    zend_ulong _index;
};

class Array {
  public:
    zval *arr;

    Array(zval *_arr) {
        assert(Z_TYPE_P(_arr) == IS_ARRAY);
        arr = _arr;
    }

    inline size_t count() {
        return zend_hash_num_elements(Z_ARRVAL_P(arr));
    }

    inline bool set(zend_ulong index, zval *value) {
        return add_index_zval(arr, index, value) == SUCCESS;
    }

    inline bool append(zval *value) {
        return add_next_index_zval(arr, value) == SUCCESS;
    }

    inline bool set(zend_ulong index, zend_resource *res) {
        zval tmp;
        ZVAL_RES(&tmp, res);
        return set(index, &tmp);
    }

    ArrayIterator begin() {
        return ArrayIterator(Z_ARRVAL_P(arr)->arData, Z_ARRVAL_P(arr)->arData + Z_ARRVAL_P(arr)->nNumUsed);
    }

    ArrayIterator end() {
        return ArrayIterator(Z_ARRVAL_P(arr)->arData + Z_ARRVAL_P(arr)->nNumUsed);
    }
};

enum PipeType {
    PIPE_TYPE_NONE = 0,
    PIPE_TYPE_STREAM = 1,
    PIPE_TYPE_DGRAM = 2,
};

class Process {
  public:
    zend_object *zsocket = nullptr;
    enum PipeType pipe_type;
    bool enable_coroutine;

    Process(enum PipeType pipe_type, bool enable_coroutine)
        : pipe_type(pipe_type), enable_coroutine(enable_coroutine) {}

    ~Process() {
        if (zsocket) {
            OBJ_RELEASE(zsocket);
        }
    }
};

namespace function {
/* must use this API to call event callbacks to ensure that exceptions are handled correctly */
bool call(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *retval, const bool enable_coroutine);

class ReturnValue {
  public:
    zval value;
    ReturnValue() {
        value = {};
    }
    ~ReturnValue() {
        zval_dtor(&value);
    }
};

ReturnValue call(const std::string &func_name, int argc, zval *argv);
}  // namespace function

struct Function {
    zend_fcall_info fci;
    zend_fcall_info_cache fci_cache;

    inline bool call(zval *retval, const bool enable_coroutine) {
        return function::call(&fci_cache, fci.param_count, fci.params, retval, enable_coroutine);
    }
};

bool include(const std::string &file);
bool eval(const std::string &code, const std::string &filename = "");
void known_strings_init(void);
void known_strings_dtor(void);
void unserialize(zval *return_value, const char *buf, size_t buf_len, HashTable *options);
void json_decode(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long zend_long);
zend_string *fetch_zend_string_by_val(char *val);

#if PHP_VERSION_ID < 80000
#define ZEND_STR_CONST
#else
#define ZEND_STR_CONST const
#endif

//-----------------------------------namespace end--------------------------------------------
}  // namespace zend
