/* 
 *------------------------------------------------------------------
 * persist.c - persistent data structure storage test / demo code
 *
 * Copyright (c) 2013 Cisco and/or its affiliates.
 * Licensed under the Apache 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:
 *
 *     http://www.apache.org/licenses/LICENSE-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.
 *------------------------------------------------------------------
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <string.h>
#include <vppinfra/clib.h>
#include <vppinfra/vec.h>
#include <vppinfra/hash.h>
#include <vppinfra/bitmap.h>
#include <vppinfra/fifo.h>
#include <vppinfra/time.h>
#include <vppinfra/mheap.h>
#include <vppinfra/heap.h>
#include <vppinfra/pool.h>
#include <vppinfra/format.h>
#include <vppinfra/serialize.h>
#include <svmdb.h>

typedef struct {
    svmdb_client_t *c;
} persist_main_t;

persist_main_t persist_main;

typedef struct {
    u8 * string1;
    u8 * string2;
} demo_struct2_t;

typedef struct {
    demo_struct2_t * demo2;
    u8 * name;
} demo_struct1_t;

/* 
 * Data structures in persistent shared memory, all the time
 */
clib_error_t * persist_malloc (persist_main_t * pm)
{
    demo_struct2_t *demo2;
    demo_struct1_t *demo1;
    time_t starttime = time(0);
    char *datestring = ctime(&starttime);
    void *oldheap;

    /* Get back the root pointer */
    demo1 = svmdb_local_get_variable_reference 
        (pm->c, SVMDB_NAMESPACE_VEC, "demo1_location");

    /* It doesnt exist create our data structures */
    if (demo1 == 0) {
        /* If you want MP / thread safety, lock the region... */
        pthread_mutex_lock(&pm->c->db_rp->mutex);

        /* Switch to the shared memory region heap */
        oldheap = svm_push_data_heap (pm->c->db_rp);

        /* Allocate the top-level structure as a single element vector */
        vec_validate (demo1, 0);

        /* Allocate the next-level structure as a plain old memory obj */
        demo2 = clib_mem_alloc (sizeof (*demo2));

        demo1->demo2 = demo2;
        demo1->name = format (0, "My name is Ishmael%c", 0);
        demo2->string1 = format (0, "Here is string1%c", 0);
        demo2->string2 = format (0, "Born at %s%c", datestring, 0);

        /* Back to the process-private heap */
        svm_pop_heap(oldheap);
        pthread_mutex_unlock(&pm->c->db_rp->mutex);

        /* 
         * Set the root pointer. Note: this guy switches heaps, locks, etc.
         * We allocated demo1 as a vector to make this "just work..."
         */
        svmdb_local_set_vec_variable (pm->c, "demo1_location", 
                                      demo1, sizeof (demo1));

    }
    else {
        /* retrieve and print data from shared memory */
        demo2 = demo1->demo2;
        fformat (stdout, "name: %s\n", demo1->name);
        fformat (stdout, "demo2 location: %llx\n", demo2);
        fformat (stdout, "string1: %s\n", demo2->string1);
        fformat (stdout, "string2: %s\n", demo2->string2);
    }
    return 0;
}

void unserialize_demo1 (serialize_main_t *sm, va_list * args)
{
    demo_struct1_t ** result = va_arg (*args, demo_struct1_t **);
    demo_struct1_t * demo1;
    demo_struct2_t * demo2;

    /* Allocate data structures in process private memory */
    demo1 = clib_mem_alloc (sizeof (*demo1));
    demo2 = clib_mem_alloc (sizeof (*demo2));
    demo1->demo2 = demo2;

    /* retrieve data from shared memory checkpoint */
    unserialize_cstring (sm, (char **) &demo1->name);
    unserialize_cstring (sm, (char **) &demo2->string1);
    unserialize_cstring (sm, (char **) &demo2->string2);
    *result = demo1;
}

void serialize_demo1 (serialize_main_t *sm, va_list * args)
{
    demo_struct1_t * demo1 = va_arg (*args, demo_struct1_t *);
    demo_struct2_t * demo2 = demo1->demo2;

    serialize_cstring (sm, (char *)demo1->name);
    serialize_cstring (sm, (char *)demo2->string1);
    serialize_cstring (sm, (char *)demo2->string2);
}        

/* Serialize / unserialize variant */
clib_error_t * 
persist_serialize (persist_main_t * pm)
{
    u8 * checkpoint;
    serialize_main_t sm;

    demo_struct2_t *demo2;
    demo_struct1_t *demo1;
    time_t starttime = time(0);
    char *datestring = ctime(&starttime);

    /* Get back the root pointer */
    checkpoint = svmdb_local_get_vec_variable (pm->c, "demo1_checkpoint", 
                                               sizeof (u8));

    /* It doesnt exist create our data structures */
    if (checkpoint == 0) {
        /* Allocate data structures in process-private memory */
        demo1 = clib_mem_alloc (sizeof (*demo2));
        vec_validate (demo1, 0);
        demo2 = clib_mem_alloc (sizeof (*demo2));

        demo1->demo2 = demo2;
        demo1->name = format (0, "My name is Ishmael%c", 0);
        demo2->string1 = format (0, "Here is string1%c", 0);
        demo2->string2 = format (0, "Born at %s%c", datestring, 0);

        /* Create checkpoint */
        serialize_open_vector (&sm, checkpoint);
        serialize (&sm, serialize_demo1, demo1);
        checkpoint = serialize_close_vector (&sm);

        /* Copy checkpoint into shared memory */
        svmdb_local_set_vec_variable (pm->c, "demo1_checkpoint", 
                                      checkpoint, sizeof (u8));
        /* Toss the process-private-memory original.. */
        vec_free (checkpoint);
    }
    else {
        /* Open the checkpoint */
        unserialize_open_data (&sm, checkpoint, vec_len (checkpoint));
        unserialize (&sm, unserialize_demo1, &demo1);

        /* Toss the process-private-memory checkpoint copy */
        vec_free (checkpoint);

        /* Off we go... */
        demo2 = demo1->demo2;
        fformat (stdout, "name: %s\n", demo1->name);
        fformat (stdout, "demo2 location: %llx\n", demo2);
        fformat (stdout, "string1: %s\n", demo2->string1);
        fformat (stdout, "string2: %s\n", demo2->string2);
    }
    return 0;
}


int main (int argc, char **argv)
{
    unformat_input_t _input, *input=&_input;
    persist_main_t * pm = &persist_main;
    clib_error_t * error = 0;

    /* Make a 4mb database arena, chroot so it's truly private */
    pm->c = svmdb_map_chroot_size ("/ptest", 4<<20);

    ASSERT(pm->c);

    unformat_init_command_line (input, argv);

    while (unformat_check_input(input) != UNFORMAT_END_OF_INPUT)  {
        if (unformat (input, "malloc"))
            error = persist_malloc (pm);
        else if (unformat (input, "serialize"))
            error = persist_serialize (pm);
        else {
            error = clib_error_return (0, "Unknown flavor '%U'",
                                       format_unformat_error, input);
            break;
        }
    }

    svmdb_unmap (pm->c);

    if (error) {
        clib_error_report (error);
        exit (1);
    }
    return 0;
}