/* Chrysalide - Outil d'analyse de fichiers binaires
 * client.c - connexion à un serveur Chrysalide
 *
 * Copyright (C) 2014 Cyrille Bagard
 *
 *  This file is part of Chrysalide.
 *
 *  OpenIDA is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  OpenIDA 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 General Public License
 *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "client.h"


#include <netdb.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>


#include <i18n.h>


#include "protocol.h"
#include "misc/rlestr.h"
#include "../../common/io.h"
#include "../../gui/panels/log.h"



/* Description de client à l'écoute (instance) */
struct _GDbClient
{
    GObject parent;                         /* A laisser en premier        */

    const char *name;                       /* Désignation du binaire      */

    rle_string hash;                        /* Empreinte du binaire lié    */
    GList *collections;                     /* Collections d'un binaire    */

    int fd;                                 /* Canal de communication      */

    GMutex sending_lock;                    /* Concurrence des envois      */
    GThread *update;                        /* Procédure de traitement     */

};

/* Description de client à l'écoute (classe) */
struct _GDbClientClass
{
    GObjectClass parent;                    /* A laisser en premier        */

};


/* Initialise la classe des descriptions de fichier binaire. */
static void g_db_client_class_init(GDbClientClass *);

/* Initialise une description de fichier binaire. */
static void g_db_client_init(GDbClient *);

/* Procède à la libération totale de la mémoire. */
static void g_db_client_finalize(GDbClient *);

/* Assure l'accueil des nouvelles mises à jour. */
static void *g_db_client_update(GDbClient *);



/* Indique le type défini pour une description de client à l'écoute. */
G_DEFINE_TYPE(GDbClient, g_db_client, G_TYPE_OBJECT);


/******************************************************************************
*                                                                             *
*  Paramètres  : klass = classe à initialiser.                                *
*                                                                             *
*  Description : Initialise la classe des descriptions de fichier binaire.    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_db_client_class_init(GDbClientClass *klass)
{
    GObjectClass *object;                   /* Autre version de la classe  */

    object = G_OBJECT_CLASS(klass);

    object->finalize = (GObjectFinalizeFunc)g_db_client_finalize;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : client = instance à initialiser.                             *
*                                                                             *
*  Description : Initialise une description de fichier binaire.               *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_db_client_init(GDbClient *client)
{
    client->fd = -1;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : client = instance d'objet GLib à traiter.                    *
*                                                                             *
*  Description : Procède à la libération totale de la mémoire.                *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void g_db_client_finalize(GDbClient *client)
{
    unset_rle_string(&client->hash);

    G_OBJECT_CLASS(g_db_client_parent_class)->finalize(G_OBJECT(client));

}


/******************************************************************************
*                                                                             *
*  Paramètres  : name        = désignation humaine du binaire associé.        *
*                hash        = empreinte d'un binaire en cours d'analyse.     *
*                collections = ensemble de collections existantes.            *
*                                                                             *
*  Description : Prépare un client pour une connexion à une BD.               *
*                                                                             *
*  Retour      : Structure mise en plae ou NULL en cas d'échec.               *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

GDbClient *g_db_client_new(const char *name, const char *hash, GDbCollection *collections)
{
    GDbClient *result;                      /* Adresse à retourner         */

    result = g_object_new(G_TYPE_DB_CLIENT, NULL);

    result->name = name;

    set_rle_string(&result->hash, hash);
    result->collections = collections;

    return result;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : client   = client pour les accès distants à manipuler.       *
*                host     = hôte à représenter pour le service.               *
*                port     = port de connexion pour les clients.               *
*                username = utilisateur effectuant les évolutions.            *
*                                                                             *
*  Description : Démarre la connexion à la base de données.                   *
*                                                                             *
*  Retour      : Bilan de l'opération.                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_db_client_start(GDbClient *client, const char *host, unsigned short port, const char *username)
{
    struct hostent *hp;                     /* Informations sur l'hôte     */
    struct sockaddr_in addr;                /* Adresse de transmission     */
    int ret;                                /* Bilan d'un appel            */
    rle_string user;                        /* Nom d'utilisateur associé   */
    uint32_t data;                          /* Mot de données lues         */
    DBError error;                          /* Validation de la connexion  */

    /* Identification du serveur à contacter */

    hp = gethostbyname(host);
    if (hp == NULL) return false;

    addr.sin_family = hp->h_addrtype;
    memcpy(&addr.sin_addr, hp->h_addr_list[0], sizeof(struct in_addr));

    addr.sin_port = htons(port);

    /* Création d'un canal de communication */

    client->fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client->fd == -1)
    {
        perror("socket");
        return false;
    }

    ret = connect(client->fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    if (ret == -1)
    {
        perror("connect");
        goto gdcs_no_listening;
    }

    /* Préparation du nom d'utilisateur à diffuser */

    init_rle_string(&user, username);

    /**
     * On réalise l'envoi initial ; le premier paquet doit contenir :
     *    - la commande 'DBC_HELO'.
     *    - le numéro de version du client.
     *    - l'empreinte du binaire analysé.
     *    - l'identifiant de l'utilisateur effectuant des modifications.
     *
     * Tout ceci est à synchroniser avec la fonction g_db_server_listener().
     */

    if (!safe_send(client->fd, (uint32_t []) { htobe32(DBC_HELO) }, sizeof(uint32_t), MSG_MORE))
        goto gdcs_error;

    if (!safe_send(client->fd, (uint32_t []) { htobe32(CDB_PROTOCOL_VERSION) }, sizeof(uint32_t), MSG_MORE))
        goto gdcs_error;

    if (!send_rle_string(&client->hash, client->fd, MSG_MORE))
        goto gdcs_error;

    if (!send_rle_string(&user, client->fd, 0))
        goto gdcs_error;

    /**
     * Le serveur doit répondre pour un message type :
     *    - la commande 'DBC_WELCOME'.
     *    - un identifiant d'erreur ('DBE_NONE' ou 'DBE_WRONG_VERSION').
     */

    if (!safe_recv(client->fd, &data, sizeof(uint32_t), 0))
        goto gdcs_error;

    if (be32toh(data) != DBC_WELCOME)
    {
        log_variadic_message(LMT_ERROR, _("The server '%s:%hu' did not welcome us!"), host, port);
        goto gdcs_error;
    }

    if (!safe_recv(client->fd, &data, sizeof(uint32_t), 0))
        goto gdcs_error;

    error = be32toh(data);

    switch (error)
    {
        case DBE_NONE:
            log_variadic_message(LMT_INFO, _("Connected to the server '%s:%hu'!"), host, port);
            break;

        case DBE_WRONG_VERSION:
            log_variadic_message(LMT_ERROR, _("The server '%s:%hu' does not use our protocol version (0x%08x)..."),
                                 host, port, CDB_PROTOCOL_VERSION);
            goto gdcs_error;
            break;

        default:
            log_variadic_message(LMT_ERROR, _("The server '%s:%hu' uses an unknown protocol..."), host, port);
            goto gdcs_error;
            break;

    }

    client->update = g_thread_try_new("cdb_client", (GThreadFunc)g_db_client_update, client, NULL);
    if (client->update == NULL)
    {
        log_variadic_message(LMT_ERROR, _("Failed to start a listening thread for the server '%s:%hu'!"),
                             host, port);
        goto gdcs_error;
    }

    return true;

 gdcs_error:

    unset_rle_string(&user);

 gdcs_no_listening:

    close(client->fd);
    client->fd = -1;

    return false;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : client = client pour les accès distants à manipuler.         *
*                                                                             *
*  Description : Assure l'accueil des nouvelles mises à jour.                 *
*                                                                             *
*  Retour      : NULL.                                                        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void *g_db_client_update(GDbClient *client)
{
    struct pollfd fds;                      /* Surveillance des flux       */
    int ret;                                /* Bilan d'un appel            */
    uint32_t val32;                         /* Valeur sur 32 bits          */
    bool status;                            /* Bilan d'une opération       */
    uint32_t command;                       /* Commande de la requête      */
    DBError error;                          /* Bilan d'une commande passée */
    GDbCollection *collec;                  /* Collection visée au final   */

    fds.fd = client->fd;
    fds.events = POLLIN | POLLPRI;

    while (1)
    {
        ret = poll(&fds, 1, -1);
        if (ret != 1) continue;

        printf("fds.revents :: %x\n", fds.revents);

        /* Le canal est fermé, une sortie doit être demandée... */
        if (fds.revents & POLLNVAL)
            break;

        if (fds.revents & (POLLIN | POLLPRI))
        {
            status = safe_recv(fds.fd, &val32, sizeof(uint32_t), 0);
            if (!status) goto gdcu_bad_exchange;

            command = be32toh(val32);

            switch (command)
            {
                case DBC_SAVE:

                    status = safe_recv(fds.fd, &val32, sizeof(uint32_t), 0);
                    if (!status) goto gdcu_bad_exchange;

                    error = be32toh(val32);

                    if (error == DBE_NONE)
                        log_variadic_message(LMT_INFO, _("Archive saved for binary '%s'"),
                                             client->name);
                    else
                        log_variadic_message(LMT_ERROR, _("Failed to save the archive for binary '%s'"),
                                             client->name);

                    break;

                case DBC_COLLECTION:

                    status = safe_recv(fds.fd, &val32, sizeof(uint32_t), 0);
                    if (!status) goto gdcu_bad_exchange;

                    collec = find_collection_in_list(client->collections, be32toh(val32));
                    if (collec == NULL) goto gdcu_bad_exchange;

                    status = g_db_collection_recv(collec, fds.fd, NULL);
                    if (!status) goto gdcu_bad_exchange;




                    printf("## CLIENT ## Got Something to read...\n");

                    break;

            }

            continue;

 gdcu_bad_exchange:

                printf("Bad reception...\n");

                /* TODO : close conn */

                ;



        }

    }

    //g_db_client_stop(client);

    return NULL;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : client = client pour les accès distants à manipuler.         *
*                                                                             *
*  Description : Arrête la connexion à la base de données.                    *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_db_client_stop(GDbClient *client)
{
    if (client->fd != -1)
        return;

    close(client->fd);
    client->fd = -1;

    g_thread_join(client->update);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : client = client pour les accès distants à manipuler.         *
*                                                                             *
*  Description : Identifie le canal de communication pour envois au serveur.  *
*                                                                             *
*  Retour      : Descripteur de flux normalement ouvert.                      *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

int g_db_client_get_fd(GDbClient *client)
{
    g_mutex_lock(&client->sending_lock);

    return client->fd;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : client = client pour les accès distants à manipuler.         *
*                                                                             *
*  Description : Marque le canal de communication comme disponible.           *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

void g_db_client_put_fd(GDbClient *client)
{
    g_mutex_unlock(&client->sending_lock);

}


/******************************************************************************
*                                                                             *
*  Paramètres  : client = client pour les accès distants à manipuler.         *
*                                                                             *
*  Description : Effectue une demande de sauvegarde de l'état courant.        *
*                                                                             *
*  Retour      : true si la commande a bien été envoyée, false sinon.         *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

bool g_db_client_save(GDbClient *client)
{
    bool result;                            /* Bilan partiel à remonter    */

    g_db_client_get_fd(client);

    result = safe_send(client->fd, (uint32_t []) { htobe32(DBC_SAVE) }, sizeof(uint32_t), 0);

    g_db_client_put_fd(client);

    return result;

}