
/*
 * Osmo - a handy personal organizer
 *
 * Copyright (C) 2015 Tomasz Mąka <pasp@users.sourceforge.net>
 *               2015 Piotr Mąka <silloz@users.sourceforge.net>
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>

#include "vcf.h"
#include "gui.h"
#include <stdlib.h>
#include <glib.h>
#include <string.h>

#define CRLF "\r\n"
#define CRLF_SIZE 2
#define DELIM_COMPONENT ';'
#define DELIM_LIST ','

#define MAX_LINE_SIZE 75
#define MAX_FOLDED_LINE_SIZE 74
#define LINE_FOLD CRLF " "
#define LINE_FOLD_SIZE 3
#define MAX_ESCAPE_SIZE 2

#define BEGIN "BEGIN:VCARD"
#define END "END:VCARD"
#define VERSION_40 "VERSION:4.0"

#define FN "FN"
#define N "N"
#define NICKNAME "NICKNAME"
#define BDAY "BDAY"
#define ADR "ADR"
#define ORG "ORG"
#define TEL "TEL"
#define EMAIL "EMAIL"
#define URL "URL"
#define IMPP "IMPP"
#define NOTE "NOTE"

#define TYPE_PARAM "TYPE"
#define PREF_PARAM "PREF"

#define TEL_URI_SCHEME "tel"

struct vcf_writer {
    vcf_writer_callback *callback;
    void *user_data;
};

static void write_string(vcf_writer *writer, gchar const *content);
static void write_value(vcf_writer *writer, gchar const *property_name, gchar const *value);
static void write_multivalue(vcf_writer *writer, gchar const *property_name, gchar separator, gint component_count, ...);
static gchar *format_date(guint32 julian_day);

/*------------------------------------------------------------------------------*/

vcf_writer *
vcf_create_writer(vcf_writer_callback *callback, void *user_data) {
      vcf_writer *writer = g_new(vcf_writer, 1);
      if(!writer) {
          return NULL;
      }
      writer->callback = callback;
      writer->user_data = user_data;

      return writer;
}

void
vcf_close_writer(vcf_writer *writer) {
    g_free(writer);
}


void
vcf_write_begin(vcf_writer *writer) {
    write_string(writer, BEGIN);
    write_string(writer, VERSION_40);
}

void
vcf_write_end(vcf_writer *writer) {
    write_string(writer, END);
}

void
vcf_write_FN(vcf_writer *writer, char const *first_name, char const *last_name) {
    gchar *buffer;
    
    if(first_name && last_name) {
        buffer = g_strdup_printf("%s %s", first_name, last_name);
    } else  if(first_name) {
        buffer = g_strdup(first_name);
    }else {
        buffer = g_strdup(last_name);
    }
    
    write_value(writer, FN, buffer);

    g_free(buffer);
}

void
vcf_write_N(vcf_writer *writer, char const *last_name, char const *first_name, char const *second_name) {
    write_multivalue(writer, N, DELIM_COMPONENT, 5, last_name, first_name, second_name, NULL, NULL);
}

void
vcf_write_NICKNAME(vcf_writer *writer, char const *nick_name) {
    write_multivalue(writer, NICKNAME, DELIM_LIST, 1, nick_name);
}

void
vcf_write_BDAY(vcf_writer *writer, guint32 julian_day) {
    gchar *text = format_date(julian_day);
    g_return_if_fail(text);
 
    write_value(writer, BDAY, text);
    g_free(text);
}

void
vcf_write_ADR(vcf_writer *writer, char const *type, char const *address, char const *city, char const *state, char const *post_code, char const *country) {
    gchar *property = g_strdup_printf("%s;%s=%s", ADR, TYPE_PARAM, type);
    write_multivalue(writer, property, DELIM_COMPONENT, 7, "", "", address, city, state, post_code, country);
    g_free(property);
}

void
vcf_write_ORG(vcf_writer *writer, char const *organization, char const *department) {
    write_multivalue(writer, ORG, DELIM_COMPONENT, 2, organization, department);
}

void
vcf_write_TEL_internal(vcf_writer *writer, gchar const *number, gint pref, gchar const **types) {
    gchar *joined_types, *property, *number_uri;
    g_return_if_fail(pref >= 1 && pref <=100);
    
    joined_types = g_strjoinv (";" TYPE_PARAM "=", (gchar **) types);
    property = g_strdup_printf("%s;%s=%d%s%s", TEL, PREF_PARAM, pref, ";" TYPE_PARAM "=", joined_types);
    number_uri = g_strdup_printf("%s:%s", TEL_URI_SCHEME, number);
    
    write_value(writer, property, number_uri);
    
    g_free(number_uri);
    g_free(property);
    g_free(joined_types);
}

void
vcf_write_EMAIL(vcf_writer *writer, gchar const *email, gint pref) {
    gchar *property;
    g_return_if_fail(pref >= 1 && pref <=100);
    
    property = g_strdup_printf("%s;%s=%d", EMAIL, PREF_PARAM, pref);
    
    write_value(writer, property, email);
    
    g_free(property);
}

void
vcf_write_URL(vcf_writer *writer, gchar const *url) {
    write_value(writer, URL, url);
}

void
vcf_write_URL_pref(vcf_writer *writer, gchar const *url, gint pref) {
    gchar *property;
    g_return_if_fail(pref >= 1 && pref <= 100);

    property = g_strdup_printf("%s;%s=%d", URL, PREF_PARAM, pref);

    write_value(writer, property, url);

    g_free(property);
}
void
vcf_write_IMPP(vcf_writer *writer, gchar const *url, gchar const *type) {
    gchar *property = g_strdup_printf("%s;%s=%s", IMPP, TYPE_PARAM, type);

    write_value(writer, property, url);

    g_free(property);
}

void
vcf_write_NOTE(vcf_writer *writer, gchar const *note) {
    write_value(writer, NOTE, note);
}

/*------------------------------------------------------------------------------*/
static void
replace_char(gchar *string, gchar search, gchar const *replacement, gsize string_size) {
    gchar *buffer, *tmp;
    gsize buffer_size, string_len, replacement_len;
    gint pos = 0;

    replacement_len = strlen(replacement);
    string_len = strlen(string);
    if(!string_len) {
        return;
    }
    buffer_size = string_len * replacement_len + 1;
    buffer = g_new(gchar, buffer_size);

    tmp = string;
    while (*tmp) {
        if (*tmp == search) {
            g_strlcat(buffer, replacement, buffer_size);
            pos += replacement_len;
        } else {
            buffer[pos] = *tmp;
            buffer[pos+1] = '\0';
            pos++;
        }
        tmp++;
    }
    g_strlcpy(string, buffer, string_size);
    g_free(buffer);
}

static void
escape_value(gchar *string, gsize string_size) {
    replace_char(string, '\\', "\\\\", string_size);
    replace_char(string, DELIM_COMPONENT, "\\;", string_size);
    replace_char(string, DELIM_LIST, "\\,", string_size);
    replace_char(string, '\n', "\\n", string_size);
}

static void
write_multivalue(vcf_writer *writer, gchar const *property, gchar separator, gint component_count, ...) {
    va_list arguments;
    gchar **components;
    gint i, buffer_size;
    gchar *buffer;

    components = g_new(gchar *, component_count);

    buffer_size = strlen(property) + 1; /* property */
    va_start(arguments, component_count);
    for (i = 0; i < component_count; i++) {
        components[i] = va_arg(arguments, gchar *);
        if (components[i]) {
            buffer_size += (strlen(components[i]) * MAX_ESCAPE_SIZE); /* escaped value */
        }
    }
    va_end(arguments);
    buffer_size += (component_count - 1); /* separators */
    buffer_size++;

    buffer = g_new(gchar, buffer_size);

    g_snprintf(buffer, buffer_size, "%s:", property);
    for (i = 0; i < component_count; i++) {
        if (i) {
            gint len = strlen(buffer);
            buffer[len++] = separator;
            buffer[len] = '\0';
        }
        if (components[i]) {
            gchar *component_buffer;
            gint len = strlen(components[i]);
            gsize component_buffer_size = (len * MAX_ESCAPE_SIZE + 1);
            component_buffer = g_new(gchar, component_buffer_size);
            g_strlcpy(component_buffer, components[i], component_buffer_size);
            escape_value(component_buffer, component_buffer_size);
            g_strlcat(buffer, component_buffer, buffer_size);
            g_free(component_buffer);
        }
    }

    write_string(writer, buffer);

    g_free(buffer);
    g_free(components);
}

static void
write_value(vcf_writer *writer, gchar const *property_name, gchar const * value) {
    write_multivalue(writer, property_name, '\0', 1, value);
}

static gchar *
format_date(guint32 julian_day) {
    gchar *buffer;
    GDate *cdate;
    gsize size = 9;
    
    buffer = g_new(gchar, size);
    cdate = g_date_new_julian (julian_day);
    size = g_date_strftime (buffer, size, "%Y%m%d", cdate);
    if(!size) {
        g_free(buffer);
        return NULL;
    }
    return buffer;
}

static void
write_string(vcf_writer *writer, gchar const *content) {
    gchar *buffer;
    gsize max_len;
            
    buffer = g_new(gchar, MAX_LINE_SIZE + 1);
    g_return_if_fail(buffer);
    
    max_len = MAX_LINE_SIZE;
    while (*content) {
        gsize len = strlen(content);
        if (len > max_len) {
            gchar *end = g_utf8_find_prev_char(content, content + MAX_LINE_SIZE);
            len = (end - content);
            strncpy(buffer, content, len);
            buffer[len] = '\0';
            writer->callback(buffer, len, writer->user_data);
            writer->callback(LINE_FOLD, LINE_FOLD_SIZE, writer->user_data);

            content = end;
            max_len = MAX_FOLDED_LINE_SIZE;
        } else {
            g_strlcpy(buffer, content, MAX_LINE_SIZE + 1);
            writer->callback(buffer, len, writer->user_data);
            writer->callback(CRLF, CRLF_SIZE, writer->user_data);
            content += len;
        }
    }
    g_free(buffer);
}