/*
 * libkarma/karma.c
 *
 * Copyright (c) Frank Zschockelt <libkarma@freakysoft.de> 2004-2005
 * Copyright (c) Enrique Vidal <evidal@iti.upv.es> 2006
 *
 * You may distribute and modify this program under the terms of 
 * the GNU GPL, version 2 or later.
 *
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

#include "lkarma.h"
#include "karma.h"
#include "karmaLan.h"
#include "karmaUsb.h"
#include "properties.h"
#include "fdb.h"

int using_usb = 0;
char *karma_tmpdir = NULL;

#ifdef __USE_ISOC99
#define set_usb_ptr(x) .lk_karma_ ##x = lk_karmaUsb_ ##x
#define set_lan_ptr(x) .lk_karma_ ##x = lk_karmaLan_ ##x
#define set_unimpl(x) .lk_karma_ ##x = unimpl
#define set_unimpl_cast(x,y) (y) unimpl
#else
#define set_usb_ptr(x) lk_karmaUsb_ ##x
#define set_lan_ptr(x) lk_karmaLan_ ##x
#define set_unimpl(x) unimpl
#define set_unimpl_cast(x,y) (y) unimpl
#endif

static int unimpl()
{
    lk_errors_set(E_UNIMPLEM);
    return 0;
}

static struct lk_ops usb_ops = 
{
    set_usb_ptr(connect),
    set_usb_ptr(hangup),
    set_unimpl(send_request),
    set_unimpl(get_protocol_version),
    set_unimpl(get_authentication_salt),
    set_usb_ptr(authenticate),
    set_unimpl(get_device_details),
    set_usb_ptr(get_storage_details),
    set_usb_ptr(get_device_settings),
    set_unimpl(update_device_settings),
    set_usb_ptr(request_io_lock),
    set_usb_ptr(release_io_lock),
    set_unimpl(prepare),
    set_unimpl(get_all_file_details),
    set_unimpl(get_file_details),
    set_usb_ptr(update_file_details),
    set_usb_ptr(read_file_chunk),
    set_usb_ptr(write_file_chunk),
    set_usb_ptr(delete_file),
    set_unimpl(format_storage),
    set_unimpl(device_operation),
    set_usb_ptr(load_database),
    set_unimpl_cast(update_database, void (*)()),
    set_usb_ptr(write_smalldb),
    set_usb_ptr(fidToPath),
};

static struct lk_ops lan_ops = 
{
    set_lan_ptr(connect),
    set_lan_ptr(hangup),
    set_lan_ptr(send_request),
    set_lan_ptr(get_protocol_version),
    set_lan_ptr(get_authentication_salt),
    set_lan_ptr(authenticate),
    set_lan_ptr(get_device_details),
    set_lan_ptr(get_storage_details),
    set_lan_ptr(get_device_settings),
    set_lan_ptr(update_device_settings),
    set_lan_ptr(request_io_lock),
    set_lan_ptr(release_io_lock),
    set_lan_ptr(prepare),
    set_lan_ptr(get_all_file_details),
    set_lan_ptr(get_file_details),
    set_lan_ptr(update_file_details),
    set_lan_ptr(read_file_chunk),
    set_lan_ptr(write_file_chunk),
    set_lan_ptr(delete_file),
    set_lan_ptr(format_storage),
    set_lan_ptr(device_operation),
    set_lan_ptr(load_database),
    set_lan_ptr(update_database),
    set_lan_ptr(write_smalldb),
    set_lan_ptr(fidToPath),
};

struct lk_ops *lk_ops;

void lk_karma_use_smalldb(void)
{
    usb_ops.lk_karma_load_database = lk_karmaUsb_load_database_smalldb;
}

int lk_karma_write_smalldb(void)
{
    return lk_ops->lk_karma_write_smalldb();
}

char *lk_karma_fidToPath(int rio, uint32_t file_id)
{
    return lk_ops->lk_karma_fidToPath(rio, file_id);
}

/* -------------------------------------------------------------------------- */
/* Basic Protocol Functions */
static int lk_karma_find_tmpdir(void)
{
    int fd;
    char path[32];

    memcpy(path, "/dev/shm/lkarmaXXXXXX", 22);
    fd = mkstemp(path);
    if (fd != -1) {
        close(fd);
        unlink(path);
        karma_tmpdir = strdup("/dev/shm/");
        return 0;
    }
    memcpy(path, "/tmp/lkarmaXXXXXX", 19);
    fd = mkstemp(path);
    if (fd != -1) {
        close(fd);
        unlink(path);
        karma_tmpdir = strdup("/tmp/");
        return 0;
    }
    lk_errors_set(E_NOTMPDIR);
    return 1;
}

int lk_karma_connect(char *ipHostOrPath)
{
    int rio;
    using_usb = (ipHostOrPath[0] == '/');
    lk_ops = using_usb ? &usb_ops : &lan_ops;

    rio = lk_ops->lk_karma_connect(ipHostOrPath);
    lk_fdb_set_device(rio);

    if (rio >= 0 && using_usb == 0 && karma_tmpdir == NULL)
        lk_karma_find_tmpdir();

    return rio;
}

int32_t lk_karma_hangup(int rio)
{
    return lk_ops->lk_karma_hangup(rio);
}

int lk_karma_send_request(int rio, uint32_t identifier, char *payload, int plen)
{
    return lk_ops->lk_karma_send_request(rio, identifier, payload, plen);
}

int lk_karma_get_protocol_version(int rio, uint32_t * major_version,
                                  uint32_t * minor_version)
{
    return lk_ops->lk_karma_get_protocol_version(rio, major_version, 
            minor_version);
}

int32_t lk_karma_get_authentication_salt(int rio, char **salt)
{
    return lk_ops->lk_karma_get_authentication_salt(rio, salt);
}

uint32_t lk_karma_authenticate(int rio, char *pass)
{
    return lk_ops->lk_karma_authenticate(rio, pass);
}

int lk_karma_get_device_details(int rio, char **name, char **version,
                                uint32_t * storagedevices)
{
    return lk_ops->lk_karma_get_device_details(rio, name, version, 
            storagedevices);
}

int lk_karma_get_storage_details(int rio, uint32_t storage_id,
                                 uint32_t * n_files, uint64_t * s_size,
                                 uint64_t * f_space, uint32_t * highest_file_id)
{
    return lk_ops->lk_karma_get_storage_details(rio, storage_id, n_files, 
            s_size, f_space, highest_file_id);
}

int lk_karma_get_device_settings(int rio, char **properties)
{
    if (properties)
        *properties = NULL;
    return lk_ops->lk_karma_get_device_settings(rio);
}

int32_t lk_karma_update_device_settings(int rio, char *properties)
{
    return lk_ops->lk_karma_update_device_settings(rio, properties);
}

int32_t lk_karma_request_io_lock(int rio, uint32_t type)
{
    return lk_ops->lk_karma_request_io_lock(rio, type);
}

int32_t lk_karma_release_io_lock(int rio)
{
    return lk_ops->lk_karma_release_io_lock(rio);
}

/* Not yet supported by the Rio Karma.*/
int32_t lk_karma_prepare(int rio, uint64_t size, uint32_t * file_id,
                         uint32_t storage_id)
{
    return lk_ops->lk_karma_prepare(rio, size, file_id, storage_id);
}

int32_t lk_karma_get_all_file_details(int rio, char **properties)
{
    return lk_ops->lk_karma_get_all_file_details(rio, properties);
}

int32_t lk_karma_get_file_details(int rio, uint32_t file_id, char **properties)
{
    return lk_ops->lk_karma_get_file_details(rio, file_id, properties);
}

int32_t lk_karma_update_file_details(int rio, uint32_t file_id,
                                     char *properties)
{
    return lk_ops->lk_karma_update_file_details(rio, file_id, properties);
}

int32_t lk_karma_read_file_chunk(int rio, uint64_t offset, uint64_t size,
                                 uint32_t file_id, char **data,
                                 uint64_t * retsize)
{
    return lk_ops->lk_karma_read_file_chunk(rio, offset, size, file_id,
            data, retsize);
}

int32_t lk_karma_write_file_chunk(int rio, uint64_t offset, uint64_t size,
                                  uint32_t file_id, uint32_t storage_id,
                                  const char *data)
{
    return lk_ops->lk_karma_write_file_chunk(rio, offset, size, file_id,
            storage_id, data);
}

int32_t lk_karma_delete_file(int rio, uint32_t file_id)
{
    return lk_ops->lk_karma_delete_file(rio, file_id);
}

/* Now, that command would be scary ;)
   (It's not supported by the rio karma ;)) */
int32_t lk_karma_format_storage(int rio, uint32_t storage_id)
{
    return lk_ops->lk_karma_format_storage(rio, storage_id);
}

/* Not yet supported by the Rio Karma */
int32_t lk_karma_device_operation(int rio, uint64_t size, char *data,
                                  char **newdata)
{
    return lk_ops->lk_karma_device_operation(rio, size, data, newdata);
}

/* Advanced Protocol Functions */
void lk_karma_load_database(int rio)
{
    lk_ops->lk_karma_load_database(rio);
}

void lk_karma_update_database(int rio)
{
    lk_ops->lk_karma_update_database(rio);
}

int lk_karma_parse_settings(char *buf)
{
    int ret = 0;
    char *str;

    str = strstr(buf, "device_generation");
    if (str) {
         str += strlen("device_generation") + 1;
         device_generation = strtol(str, NULL, 10);
    } else
        ret++;

    str = strstr(buf, "serial");
    if (str) {
         str += strlen("serial") + 1;
         serial = strtol(str, NULL, 10);
    } else
        ret++;

    return ret;
}
