/**
 * Tencent is pleased to support the open source community by making MSEC available.
 *
 * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
 *
 * Licensed under the GNU General Public 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
 *
 *     https://opensource.org/licenses/GPL-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.
 */


/**
 *  @file mt_net.h
 **/

#ifndef __MT_NET_H__
#define __MT_NET_H__

#include "micro_thread.h"
#include "hash_list.h"
#include "mt_api.h"
#include "mt_cache.h"
#include "mt_net_api.h"

namespace NS_MICRO_THREAD {

enum MT_CONN_TYPE 
{
    TYPE_CONN_UNKNOWN   = 0,
    TYPE_CONN_SHORT     = 0x1,
    TYPE_CONN_POOL      = 0x2,
    TYPE_CONN_SESSION   = 0x4,
    TYPE_CONN_SENDONLY  = 0x8,
};

class CSockLink;

template <typename List, typename Type>
class CRecyclePool
{
public:

    CRecyclePool() {
        _expired = 60 * 1000;
        _count = 0;
        TAILQ_INIT(&_free_list);
    };

    ~CRecyclePool() {
        Type* item = NULL;
        Type* tmp = NULL;
        TAILQ_FOREACH_SAFE(item, &_free_list, _link_entry, tmp)
        {
            TAILQ_REMOVE(&_free_list, item, _link_entry);
            delete item;
        }
        _count = 0;
    };

    Type* AllocItem() {
        Type* item = TAILQ_FIRST(&_free_list);
        if (item != NULL)
        {
            TAILQ_REMOVE(&_free_list, item, _link_entry);
            _count--;
            return item;
        }
        
        item = new Type();
        if (NULL == item)
        {
            return NULL;
        }
        
        return item;
    };

    void FreeItem(Type* obj) {
        //obj->Reset();        
        TAILQ_INSERT_TAIL(&_free_list, obj, _link_entry);
        obj->_release_time = mt_time_ms();
        _count++;
    };

    void RecycleItem(uint64_t now) {
        Type* item = NULL;
        Type* tmp = NULL;
        TAILQ_FOREACH_SAFE(item, &_free_list, _link_entry, tmp)
        {
            if ((now - item->_release_time) < _expired) {
                break;
            }
        
            TAILQ_REMOVE(&_free_list, item, _link_entry);
            delete item;
            _count--;
        }
    };

    void SetExpiredTime(uint64_t expired) {
        _expired = expired;
    };

private:

    List            _free_list;
    uint64_t        _expired;
    uint32_t        _count;
};

class CNetHandler : public HashKey
{
public:

    enum {
        STATE_IN_SESSION    = 0x1,
        STATE_IN_CONNECT    = 0x2,
        STATE_IN_SEND       = 0x4,
        STATE_IN_RECV       = 0x8,
        STATE_IN_IDLE       = 0x10,
    };

    virtual uint32_t HashValue();

    virtual int HashCmp(HashKey* rhs); 

    int32_t SendRecv(void* data, uint32_t len, uint32_t timeout);

    void* GetRspBuff() {
        if (_rsp_buff != NULL) {
            return _rsp_buff->data;
        } else {
            return NULL;
        }
    };

    uint32_t GetRspLen() {
        if (_rsp_buff != NULL) {
            return _rsp_buff->data_len;
        } else {
            return 0;
        }
    };

    void SetRespBuff(TSkBuffer* buff) {
        if (_rsp_buff != NULL) {
            delete_sk_buffer(_rsp_buff);
            _rsp_buff = NULL;
        }
        
        _rsp_buff = buff;
    };

    void SetProtoType(MT_PROTO_TYPE type) {
        _proto_type = type;    
    };

    void SetConnType(MT_CONN_TYPE type) {
        _conn_type = type;
    };

	void SetDestAddress(struct sockaddr_in* dst) {
        if (dst != NULL) {
            memcpy(&_dest_ipv4, dst, sizeof(*dst));
        }
	};

	void SetSessionId(uint64_t sid) {
        _session_id = sid;
	};	

    void SetSessionCallback(CHECK_SESSION_CALLBACK function) {
        _callback = function;
    };

    CHECK_SESSION_CALLBACK GetSessionCallback() {
        return _callback;
    };
    

public:

    void Link(CSockLink* conn);

    void Unlink();

    int32_t CheckParams();

    int32_t GetConnLink();

    int32_t WaitConnect(uint64_t timeout);

    int32_t WaitSend(uint64_t timeout);
 
    int32_t WaitRecv(uint64_t timeout);

    void SwitchToConn();

    void SwitchToSend();

    void SwitchToRecv();

    void SwitchToIdle();

    void DetachConn();

    bool RegistSession();

    void UnRegistSession();

    uint32_t SkipSendPos(uint32_t len);

    void SetErrNo(int32_t err) {
        _err_no = err;
    };

    MicroThread* GetThread() {
        return _thread;
    };

    void GetSendData(void*& data, uint32_t& len) {
        data = _req_data;
        len  = _req_len;
    };

    void Reset();

    CNetHandler();
    ~CNetHandler();

    TAILQ_ENTRY(CNetHandler)    _link_entry; 
    uint64_t                    _release_time;

protected:

    MicroThread*        _thread;
    MT_PROTO_TYPE       _proto_type;    
    MT_CONN_TYPE        _conn_type;
    struct sockaddr_in  _dest_ipv4;
    uint64_t            _session_id;
    CHECK_SESSION_CALLBACK _callback;
    uint32_t            _state_flags;
    int32_t             _err_no;
    void*               _conn_ptr;
    uint32_t            _send_pos;
    uint32_t            _req_len;
    void*               _req_data;
    TSkBuffer*          _rsp_buff;

};
typedef TAILQ_HEAD(__NetHandlerList, CNetHandler) TNetItemList;
typedef CRecyclePool<TNetItemList, CNetHandler>   TNetItemPool;

class CSockLink : public KqueuerObj
{
public:

    enum {
        LINK_CONNECTING     = 0x1,
        LINK_CONNECTED      = 0x2,
    };

    enum {
        LINK_IDLE_LIST      = 1,
        LINK_CONN_LIST      = 2,
        LINK_SEND_LIST      = 3,
        LINK_RECV_LIST      = 4,
    };

    int32_t CreateSock();

    void Close();

    bool Connect();
    bool Connected() {
        return (_state & LINK_CONNECTED);
    }

    void Destroy();  

    TNetItemList* GetItemList(int32_t type);

    void AppendToList(int32_t type, CNetHandler* item);

    void RemoveFromList(int32_t type, CNetHandler* item);

    struct sockaddr_in* GetDestAddr(struct sockaddr_in* addr);

    int32_t SendData(void* data, uint32_t len);

    int32_t SendCacheUdp(void* data, uint32_t len);

    int32_t SendCacheTcp(void* data, uint32_t len);

    void ExtendRecvRsp();

    int32_t RecvDispath();

    CHECK_SESSION_CALLBACK GetSessionCallback();

    int32_t DispathTcp();

    int32_t DispathUdp();

    CNetHandler* FindSession(uint64_t sid);

    virtual int InputNotify();
    
    virtual int OutputNotify();

    virtual int HangupNotify();

    CSockLink();
    ~CSockLink();

    void Reset();

    void NotifyThread(CNetHandler* item, int32_t result);

    void NotifyAll(int32_t result);

    void SetProtoType(MT_PROTO_TYPE type);

    void SetParentsPtr(void* ptr) {
        _parents = ptr;
    };

    void* GetParentsPtr() {
        return _parents;
    };

    uint64_t GetLastAccess() {
        return _last_access;
    };

public:

    TAILQ_ENTRY(CSockLink) _link_entry; 
    uint64_t               _release_time;
    
private:

    TNetItemList        _wait_connect;
    TNetItemList        _wait_send;
    TNetItemList        _wait_recv;
    TNetItemList        _idle_list;
    MT_PROTO_TYPE       _proto_type;
    int32_t             _errno;
    uint32_t            _state;
    uint64_t            _last_access;
    TRWCache            _recv_cache;
    TSkBuffer*          _rsp_buff;
    void*               _parents;
};
typedef TAILQ_HEAD(__SocklinkList, CSockLink) TLinkList;
typedef CRecyclePool<TLinkList, CSockLink>    TLinkPool;

class CDestLinks : public CTimerNotify, public HashKey
{
public:

    CDestLinks();
    ~CDestLinks();

    void Reset();

    void StartTimer();

    CSockLink* GetSockLink();

    void FreeSockLink(CSockLink* sock);

    MT_PROTO_TYPE GetProtoType() {
        return _proto_type;
    };

    MT_CONN_TYPE GetConnType() {
        return _conn_type;
    };

    void SetKeyInfo(uint32_t ipv4, uint16_t port, MT_PROTO_TYPE proto, MT_CONN_TYPE conn) {
        _addr_ipv4  = ipv4;
        _net_port   = port;
        _proto_type = proto;
        _conn_type  = conn;
    };

    void CopyKeyInfo(CDestLinks* key) {
        _addr_ipv4  = key->_addr_ipv4;
        _net_port   = key->_net_port;
        _proto_type = key->_proto_type;
        _conn_type  = key->_conn_type;
    };

    void GetDestIP(uint32_t& ip, uint16_t& port) {
        ip = _addr_ipv4;
        port = _net_port;
    };

    virtual void timer_notify();

    virtual uint32_t HashValue() {
        return _addr_ipv4 ^ (((uint32_t)_net_port << 16) | (_proto_type << 8) | _conn_type);
    }; 

    virtual int HashCmp(HashKey* rhs) {
        CDestLinks* data = (CDestLinks*)(rhs);
        if (!data) { 
            return -1;
        }
        if (this->_addr_ipv4 != data->_addr_ipv4) {
            return (this->_addr_ipv4 > data->_addr_ipv4) ?  1 : -1;    
        }
        if (this->_net_port != data->_net_port) {
            return (this->_net_port > data->_net_port) ? 1 : -1;
        }
        if (this->_proto_type != data->_proto_type) {
            return (this->_proto_type > data->_proto_type) ? 1 : -1;
        }
        if (this->_conn_type != data->_conn_type) {
            return (this->_conn_type > data->_conn_type) ? 1 : -1;
        }
        
        return 0;
    }; 

    void SetDefaultCallback(CHECK_SESSION_CALLBACK function) {
        _dflt_callback = function;
    };

    CHECK_SESSION_CALLBACK GetDefaultCallback() {
        return _dflt_callback;
    };

    TAILQ_ENTRY(CDestLinks) _link_entry; 
    uint64_t                _release_time;

private:

    uint32_t            _timeout;
    uint32_t            _addr_ipv4;
    uint16_t            _net_port;
    MT_PROTO_TYPE       _proto_type;
    MT_CONN_TYPE        _conn_type;

    uint32_t            _max_links;
    uint32_t            _curr_link;
    TLinkList           _sock_list;
    CHECK_SESSION_CALLBACK _dflt_callback;
        
};
typedef TAILQ_HEAD(__DestlinkList, CDestLinks) TDestList;
typedef CRecyclePool<TDestList, CDestLinks>    TDestPool;

class CNetMgr
{
public:

    static CNetMgr* Instance (void);

    static void Destroy(void);

    CNetHandler* FindNetItem(CNetHandler* key);

    void InsertNetItem(CNetHandler* item);

    void RemoveNetItem(CNetHandler* item);

    CDestLinks* FindCreateDest(CDestLinks* key);

    void DeleteDestLink(CDestLinks* dst);

    CDestLinks* FindDestLink(CDestLinks* key);

    void InsertDestLink(CDestLinks* item);

    void RemoveDestLink(CDestLinks* item);

    ~CNetMgr();

    void RecycleObjs(uint64_t now);

    CNetHandler* AllocNetItem() {
        return _net_item_pool.AllocItem();
    };

    void FreeNetItem(CNetHandler* item) {
        return _net_item_pool.FreeItem(item);
    };

    CSockLink* AllocSockLink() {
        return _sock_link_pool.AllocItem();
    };

    void FreeSockLink(CSockLink* item) {
        return _sock_link_pool.FreeItem(item);
    };

    CDestLinks* AllocDestLink() {
        return _dest_ip_pool.AllocItem();
    };

    void FreeDestLink(CDestLinks* item) {
        return _dest_ip_pool.FreeItem(item);
    };

    TSkBuffMng* GetSkBuffMng(MT_PROTO_TYPE type) {
        if (type == NET_PROTO_TCP) {
            return &_tcp_pool;
        } else {
            return &_udp_pool;
        }
    };
    

private:
    CNetMgr();

    static CNetMgr *    _instance;
    HashList*           _ip_hash;
    HashList*           _session_hash;
    TSkBuffMng          _udp_pool;
    TSkBuffMng          _tcp_pool;
    TDestPool           _dest_ip_pool;
    TLinkPool           _sock_link_pool;
    TNetItemPool        _net_item_pool;
};

}

#endif