/**
 * @file   type.h
 * @author mathslinux <riegamaths@gmail.com>
 * @date   Sun May 20 22:24:30 2012
 *
 * @brief  Linux WebQQ Data Struct API
 *
 *
 */

#ifndef LWQQ_TYPE_H
#define LWQQ_TYPE_H

#include <pthread.h>
#include <stdarg.h>
#include "lwqq-config.h"
#include "queue.h"
#include "vplist.h"

struct LwqqHttpHandle;
struct LwqqMsgContent;
struct LwqqArguments;
struct LwqqEvents;
struct LwqqMsg;

typedef struct LwqqClient LwqqClient;
typedef struct LwqqHttpRequest LwqqHttpRequest;
typedef VP_DISPATCH DISPATCH_FUNC;
typedef VP_CALLBACK CALLBACK_FUNC;
typedef vp_command LwqqCommand;
#define _F_(f) (CALLBACK_FUNC) f
#define _C_2(d, c, ...) vp_make_command(vp_func_##d, _F_(c), __VA_ARGS__)
#define _C_(d, c, ...) _C_2(d, c, __VA_ARGS__)
#define _P_2(d, ...) vp_make_params(vp_func_##d, __VA_ARGS__)
#define _P_(d, ...) _P_2(d, __VA_ARGS__)
typedef char* (*LwqqHashFunc)(const char* uin, const char* ptwebqq,
                              void* userdata);
// return zero means continue.>1 means abort
typedef int (*LwqqProgressFunc)(void* data, size_t now, size_t total);
/************************************************************************/

//=========================INSTRUCTION=================================//
/**
 * LWQQ head is defined local enum
 * WEBQQ head is defined server enum
 * that is it's value same as Server
 *
 * there are some old LWQQ head that should be WEBQQ
 */

extern const LwqqFeatures lwqq_features();
extern const char* lwqq_version;

typedef enum {
   LWQQ_STATUS_LOGOUT = 0,
   LWQQ_STATUS_ONLINE = 10,
   LWQQ_STATUS_OFFLINE = 20,
   LWQQ_STATUS_AWAY = 30,
   LWQQ_STATUS_HIDDEN = 40,
   LWQQ_STATUS_BUSY = 50,
   LWQQ_STATUS_CALLME = 60,
   LWQQ_STATUS_SLIENT = 70
} LwqqStatus;
typedef enum {
   LWQQ_CLIENT_PC = 1, /*1-10*/
   LWQQ_CLIENT_MOBILE = 21, /*21-24*/
   LWQQ_CLIENT_WEBQQ = 41,
   LWQQ_CLIENT_QQFORPAD = 42
} LwqqClientType;
typedef enum {
   LWQQ_MASK_NONE = 0,
   LWQQ_MASK_1 = 1,
   LWQQ_MASK_ALL = 2
} LwqqMask;
typedef enum {
   LWQQ_MEMBER_IS_ADMIN = 0x1,
} LwqqMemberFlags;
#define LWQQ_UNKNOW 0
typedef enum {
   LWQQ_MOUTH = 1,
   LWQQ_CATTLE,
   LWQQ_TIGER,
   LWQQ_RABBIT,
   LWQQ_DRAGON,
   LWQQ_SNACK,
   LWQQ_HORSE,
   LWQQ_SHEEP,
   LWQQ_MONKEY,
   LWQQ_CHOOK,
   LWQQ_DOG,
   LWQQ_PIG
} LwqqShengxiao;
typedef enum {
   LWQQ_AQUARIUS = 1,
   LWQQ_PISCES,
   LWQQ_ARIES,
   LWQQ_TAURUS,
   LWQQ_GEMINI,
   LWQQ_CANCER,
   LWQQ_LEO,
   LWQQ_VIRGO,
   LWQQ_LIBRA,
   LWQQ_SCORPIO,
   LWQQ_SAGITTARIUS,
   LWQQ_CAPRICORNUS
} LwqqConstel;
typedef enum {
   LWQQ_BLOOD_A = 1,
   LWQQ_BLOOD_B,
   LWQQ_BLOOD_O,
   LWQQ_BLOOD_AB,
   LWQQ_BLOOD_OTHER
} LwqqBloodType;
typedef enum { LWQQ_FEMALE = 1, LWQQ_MALE = 2 } LwqqGender;
typedef enum {
   LWQQ_DEL_KEEP_OTHER
   = 1, /* delete buddy and keep myself from other buddy list */
   LWQQ_DEL_FROM_OTHER
   = 2 /* delete buddy and remove myself from other buddy list */
} LwqqDelFriendType;
typedef enum { LWQQ_GROUP_QUN = 0, LWQQ_GROUP_DISCU = 1 } LwqqGroupType;
typedef enum { LWQQ_CLEANUP_WAITALL = 0, LWQQ_CLEANUP_IGNORE = 1 } LwqqCleanUp;

/* Lwqq Error Code */
typedef enum {
   // db error
   LWQQ_EC_DB_EXEC_FAILED = -50,
   // login error
   LWQQ_EC_WAIT_VERIFY = -43, // a special error abort login
                              // stage because wait user input vcode
   LWQQ_EC_FAILD_VERIFY = -42, // verify failed by other reason
   LWQQ_EC_WRONG_VERIFY = -41, // verify code is wrong, default
   LWQQ_EC_WRONG_PASS = -40,
   // response unexpected
   LWQQ_EC_NOT_JSON_FORMAT = -30,
   // upload error code
   LWQQ_EC_UPLOAD_OVERSIZE = -21,
   LWQQ_EC_UPLOAD_OVERRETRY = -20,
   // network error code
   LWQQ_EC_SSL_ERROR = -12,
   LWQQ_EC_HTTP_ERROR = -11,
   LWQQ_EC_NETWORK_ERROR = -10,
   // system error code
   LWQQ_EC_FILE_NOT_EXIST = -6,
   LWQQ_EC_NULL_POINTER = -5,
   LWQQ_EC_CANCELED = -4,
   LWQQ_EC_TIMEOUT_OVER = -3,
   LWQQ_EC_NO_RESULT = -2,
   LWQQ_EC_ERROR = -1,

   // webqq error code
   LWQQ_EC_OK = 0,
   LWQQ_EC_LOGIN_NEED_VC = 10,
   LWQQ_EC_HASH_WRONG = 50,
   LWQQ_EC_LOGIN_ABNORMAL = 60, // 登录需要解禁
   LWQQ_EC_NO_MESSAGE = 102,
   LWQQ_EC_COOKIE_WRONG = 103,
   LWQQ_EC_PTWEBQQ = 116,
   LWQQ_EC_LOST_CONN = 121,
   LWQQ_EC_LOGIN_NEED_BARCODE = 10005
} LwqqErrorCode;

typedef struct LwqqHashEntry {
   const char* name;
   LwqqHashFunc func;
   void* data;
} LwqqHashEntry;

/* Struct defination */
typedef struct LwqqFriendCategory {
   int index;
   int sort;
   char* name;
   int count;
   LIST_ENTRY(LwqqFriendCategory) entries;
} LwqqFriendCategory;
#define LWQQ_FRIEND_CATE_IDX_DEFAULT 0
#define LWQQ_FRIEND_CATE_IDX_PASSERBY -1

// means the buddy need update by hand
#define LWQQ_LAST_MODIFY_RESET 0
// means the buddy is not in database
#define LWQQ_LAST_MODIFY_UNKNOW -1
/* QQ buddy */
typedef struct LwqqBuddy {
   char* uin; /**< Uin. Change every login */
   char* qqnumber; /**< QQ number */
   char* face;
   char* occupation;
   char* phone;
   char* allow;
   char* college;
   char* reg_time;
   LwqqConstel constel;
   LwqqBloodType blood;
   char* homepage;
   char* country;
   char* city;
   char* personal;
   char* nick;
   char* long_nick;
   LwqqShengxiao shengxiao;
   char* email;
   char* province;
   LwqqGender gender;
   char* mobile;
   char* vip_info;
   char* markname;
   LwqqStatus stat;
   LwqqClientType client_type;
   time_t birthday;
   char* flag;
   int cate_index; /** < Index of the category */
   // extra data
   char* avatar;
   size_t avatar_len;
   time_t last_modify;
   char* token; /** < Only used in add friend */
   void* data; /** < user defined data */
   int level;
   LIST_ENTRY(LwqqBuddy) entries; /* FIXME: Do we really need this? */
} LwqqBuddy;

typedef LIST_HEAD(LwqqFriendList, LwqqBuddy) LwqqFriendList;
typedef struct LwqqSimpleBuddy {
   char* uin;
   char* qq;
   char* nick;
   char* card; /** 群名片 */
   LwqqClientType client_type;
   LwqqStatus stat;
   LwqqMemberFlags mflag;
   char* cate_index;
   char* group_sig; /** only use at sess message */
   LIST_ENTRY(LwqqSimpleBuddy) entries;
} LwqqSimpleBuddy;

/* QQ group */
typedef struct LwqqGroup {
   LwqqGroupType type;
   char* name; /**< QQ Group name */
   union {
      char* gid; /**< QQ Group id */
      char* did; /**< QQ Discu id */
   };
   union {
      char* account; /** < Group: QQ number
Discu: (with lwdb) the only one and stable key reference
(without lwdb) equal to info_seq;
*/
      char* qq; /** < QQ number */
   };
   int info_seq; /** < avaliable for discu */
   char* code;
   char* markname; /** < QQ Group mark name */

   /* ginfo */
   char* face;
   char* memo;
   char* classes;
   char* fingermemo;
   time_t createtime;
   char* level;
   char* owner; /** < owner's QQ number  */
   char* flag;
   char* option;
   LwqqMask mask; /** < group mask */

   char* group_sig; /** < use in sess msg */

   int last_seq; /** readonly: received last message seq **/
   time_t last_modify;
   char* avatar;
   size_t avatar_len;
   void* data; /** < user defined data */

   LIST_ENTRY(LwqqGroup) entries;
   LIST_HEAD(, LwqqSimpleBuddy) members; /** < QQ Group members */
} LwqqGroup;
#define lwqq_member_is_founder(member, group)                                  \
   (strcmp(member->uin, group->owner) == 0)
#define lwqq_group_is_qun(group) (group->type == LWQQ_GROUP_QUN)
#define lwqq_group_is_discu(group) (group->type == LWQQ_GROUP_DISCU)

typedef struct LwqqVerifyCode {
   char* str; // input
   //{image
   char* data;
   size_t size;
   //}
   LwqqClient* lc;
   LwqqCommand cmd;
} LwqqVerifyCode;

typedef enum { LWQQ_NO, LWQQ_YES, LWQQ_EXTRA_ANSWER, LWQQ_IGNORE } LwqqAnswer;
#define LWQQ_ALLOW_AND_ADD LWQQ_EXTRA_ANSWER

/* LwqqClient API */
struct LwqqClient {
   char* username; /** < Username */
   char* password; /** < Password */
   char* clientid;
   char* seskey;
   char* cip;
   char* index;
   char* port;
   char* vfwebqq;
   char* psessionid;
   char* gface_key; /** < use at cface */
   char* gface_sig; /** < use at cfage */
   char* login_sig;
   LwqqErrorCode sync_result; /** < a safe place to get last sync event->result **/
   char* error_description;
   // char *new_ptwebqq;     /** < this only used when relogin */
   struct {
      char* ptwebqq;
      char* pt_verifysession;
   } session;

   LwqqBuddy* myself; /** < Myself */
   //LwqqVerifyCode* vc; /** < Verify Code */
   struct LwqqEvents* events;
   struct LwqqArguments* args;
   struct LwqqRecvMsgList* msg_list;

   LwqqStatus stat;

   void (*dispatch)(LwqqCommand, unsigned long timeout);
   LwqqBuddy* (*find_buddy_by_uin)(struct LwqqClient* lc, const char* uin);
   LwqqBuddy* (*find_buddy_by_qqnumber)(struct LwqqClient* lc,
                                        const char* qqnumber);

   LwqqFriendList friends; /** < QQ friends */
   LIST_HEAD(, LwqqFriendCategory) categories; /** < QQ friends categories */
   LIST_HEAD(, LwqqGroup) groups; /** < QQ groups */
   LIST_HEAD(, LwqqGroup) discus; /** < QQ discus */

   //-- non data area --//
   void* data; /** < user defined data*/
   int magic; /** < 0x4153 **/
};
#define lwqq_client_userdata(lc) (lc->data)
#define lwqq_client_dispatch(lc, cmd) (lc->dispatch(cmd, 0))

/**
 * this is used for some long http request actions chain.
 * because event tigger only once. while async option often calls repeatly.
 * and most of async option require gui display some information.
 *
 */
typedef struct LwqqEvents {
   /**
    * for test only
    * modify: login_ec <- 2
    */
   LwqqCommand start_login;
   /**
    * this is login complete .whatever successed or failed
    * except need verify code
    */
   LwqqCommand login_complete;
   /*
    * this is very important when poll message come
    */
   LwqqCommand poll_msg;
   /* this is poll lost after recv retcode 112 or 108 */
   LwqqCommand poll_lost;
   /* this is upload content failed such as lwqq offline pic */
   LwqqCommand upload_fail;
   /* this is you confirmed a friend request
    * you should add buddy to gui level.
    */
   LwqqCommand new_friend;
   LwqqCommand new_group;
   LwqqCommand need_verify;
   /* this called when successfully delete group from server
    * and the last chance to visit group
    * do not delete group in this function
    * it would deleted later.
    */
   LwqqCommand delete_group;
   /** this called when group member changes
    * you need flush displayed group member
    */
   LwqqCommand group_member_chg;
   LwqqCommand ext_clean;
   /** a group information has changed
    *  modify : buddy <- changed buddy
    */
   LwqqCommand friend_chg;
   /** a group information has changed
    *  modify : group <- changed group
    */
   LwqqCommand group_chg;
   /** a safe event last chance do http request ( must be synced) */
   LwqqCommand start_logout;
} LwqqEvents;

LwqqEvents* lwqq_client_get_events(LwqqClient* lc);

typedef struct LwqqArguments {
   LwqqErrorCode login_ec;
   LwqqBuddy* buddy;
   LwqqGroup* group;
   LwqqVerifyCode* vf_image;
   const LwqqGroup* deleted_group;
   const char* serv_id;
   struct LwqqMsgContent* content;
   LwqqErrorCode err;
   char* hash_result;
} LwqqArguments;

LwqqArguments* lwqq_client_get_args(LwqqClient* lc);

/** add repeat event listener to lc->events->...
 * @param : event <- a pointer to lc->events->...
 *          cmd   <- _C_ macro
 * @return <- an id used to unlink
 */
const LwqqCommand* lwqq_add_event_listener(LwqqCommand* event, LwqqCommand cmd);
#define lwqq_add_event(event, cmd) lwqq_add_event_listener(&event, cmd);

/* Struct defination end */

/************************************************************************/
/* LwqqClient API */
/**
 * Create a new lwqq client
 *
 * @param username QQ username
 * @param password QQ password
 *
 * @return A new LwqqClient instance, or NULL failed
 */
LwqqClient* lwqq_client_new(const char* username, const char* password);

#define lwqq_client_valid(lc)                                                  \
   (lc != 0 && ((LwqqClient*)lc)->magic == LWQQ_MAGIC)
#define lwqq_client_logined(lc) (lc->stat != LWQQ_STATUS_LOGOUT)

/**
 * Free LwqqVerifyCode object
 *
 * @param vc
 */
void lwqq_vc_free(LwqqVerifyCode* vc);

/**
 * Free LwqqClient instance
 *
 * @param client LwqqClient instance
 */
void lwqq_client_free(LwqqClient* client);
struct LwqqHttpHandle* lwqq_get_http_handle(LwqqClient* lc);

/* LwqqClient API end */

/************************************************************************/
/* LwqqBuddy API  */
/**
 *
 * Create a new buddy
 *
 * @return A LwqqBuddy instance
 */
LwqqBuddy* lwqq_buddy_new();
LwqqSimpleBuddy* lwqq_simple_buddy_new();

// add the ref count of buddy.
// that means only ref count down to zero.
// we really free the memo space.
#define lwqq_buddy_ref(buddy) buddy->ref++;
/**
 * Free a LwqqBuddy instance
 *
 * @param buddy
 */
void lwqq_buddy_free(LwqqBuddy* buddy);
void lwqq_simple_buddy_free(LwqqSimpleBuddy* buddy);

/**
 * Find buddy object by buddy's uin member
 *
 * @param lc Our Lwqq client object
 * @param uin The uin of buddy which we want to find
 *
 * @return
 */
LwqqBuddy* lwqq_buddy_find_buddy_by_uin(LwqqClient* lc, const char* uin);
LwqqBuddy* lwqq_buddy_find_buddy_by_qqnumber(LwqqClient* lc,
                                             const char* qqnumber);
/**
 * Find buddy object by buddy's nick or mark name
 */
LwqqBuddy* lwqq_buddy_find_buddy_by_name(LwqqClient* lc, const char* name);

LwqqFriendCategory* lwqq_category_find_by_name(LwqqClient* lc,
                                               const char* name);
LwqqFriendCategory* lwqq_category_find_by_id(LwqqClient* lc, int index);

/* LwqqBuddy API END*/

/**
 * Free a LwqqGroup instance
 *
 * @param group
 */
void lwqq_group_free(LwqqGroup* group);

/**
 *
 * Create a new group
 * @param type LWQQ_GROUP_DISCU or LWQQ_GROUP_QUN
 *
 * @return A LwqqGroup instance
 */
LwqqGroup* lwqq_group_new(int type);

/**
 * Find group object or discus object by group's gid member
 *
 * @param lc Our Lwqq client object
 * @param uin The gid of group which we want to find
 *
 * @return A LwqqGroup instance
 */
LwqqGroup* lwqq_group_find_group_by_gid(LwqqClient* lc, const char* gid);
#define lwqq_discu_find_discu_by_did(lc, did)                                  \
   lwqq_group_find_group_by_gid(lc, did);
LwqqGroup* lwqq_group_find_group_by_qqnumber(LwqqClient* lc,
                                             const char* qqnumber);
#define lwqq_group_find_group_by_account(lc, acc)                              \
   lwqq_group_find_group_by_qqnumber(lc, acc)

/**
 * Find group member object by member's uin
 *
 * @param group Our Lwqq group object
 * @param uin The uin of group member which we want to find
 *
 * @return A LwqqBuddy instance
 */
LwqqSimpleBuddy* lwqq_group_find_group_member_by_uin(LwqqGroup* group,
                                                     const char* uin);

#define format_append(str, format...)                                          \
   snprintf(str + strlen(str), sizeof(str) - strlen(str), ##format)

/** auto select hash function, it try one form system queue, if failed, try
 * next one, you can set begin postion to start scan */
char* lwqq_hash_auto(const char* uin, const char* ptwebqq, void* lc);
/* check we have already tried all hash */
int lwqq_hash_all_finished(LwqqClient* lc);
/* register a new js entry for auto select.
 * note we have only 8 size, and lwqq used 4, so you can only add 3 at most,
 * last one must be empty*/
void lwqq_hash_add_entry(LwqqClient* lc, const char* name, LwqqHashFunc func,
                         void* data);
/* set the begin postion to start auto select */
void lwqq_hash_set_beg(LwqqClient* lc, const char* hash_name);
/* get the last one successful hash, you should save it for next time use */
const LwqqHashEntry* lwqq_hash_get_last(LwqqClient* lc);

/************************************************************************/

const char* lwqq_status_to_str(LwqqStatus status);
LwqqStatus lwqq_status_from_str(const char* str);
const char* lwqq_date_to_str(time_t date);

// long time
#define LTIME lwqq_time()
long lwqq_time();

#endif /* LWQQ_TYPE_H */
