/* vim: set expandtab ts=4 sw=4: */
/*
 * You may redistribute this program 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.
 *
 * 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, see <https://www.gnu.org/licenses/>.
 */

#include "benc/Object.h"
#include "benc/String.h"
#include "benc/Dict.h"
#include "benc/List.h"
#include "benc/serialization/standard/BencMessageWriter.h"
#include "exception/Er.h"
#include "wire/Message.h"
#include "util/Base10.h"

static Er_DEFUN(void writeGeneric(Object* obj, struct Message* msg));

static Er_DEFUN(void writeListItems(struct List_Item* item, struct Message* msg))
{
    if (!item) { Er_ret(); }
    Er(writeListItems(item->next, msg));
    Er(writeGeneric(item->elem, msg));
    Er_ret();
}

static Er_DEFUN(void writeList(List* l, struct Message* msg))
{
    Er(Message_epush8(msg, 'e'));
    Er(writeListItems(*l, msg));
    Er(Message_epush8(msg, 'l'));
    Er_ret();
}

static Er_DEFUN(void writeInt(int64_t num, struct Message* msg))
{
    Er(Message_epush8(msg, 'e'));
    Er(Base10_write(msg, num));
    Er(Message_epush8(msg, 'i'));
    Er_ret();
}

static Er_DEFUN(void writeString(String* str, struct Message* msg))
{
    Er(Message_epush(msg, str->bytes, str->len));
    Er(Message_epush8(msg, ':'));
    Er(Base10_write(msg, str->len));
    Er_ret();
}

static Er_DEFUN(void writeDictEntries(struct Dict_Entry* entry, struct Message* msg))
{
    if (!entry) { Er_ret(); }
    Er(writeDictEntries(entry->next, msg));
    Er(writeGeneric(entry->val, msg));
    Er(writeString(entry->key, msg));
    Er_ret();
}

static Er_DEFUN(void writeDict(Dict* d, struct Message* msg))
{
    Er(Message_epush8(msg, 'e'));
    Er(writeDictEntries(*d, msg));
    Er(Message_epush8(msg, 'd'));
    Er_ret();
}

static Er_DEFUN(void writeGeneric(Object* obj, struct Message* msg))
{
    switch (obj->type) {
        case Object_STRING:  Er(writeString(obj->as.string, msg)); break;
        case Object_DICT:    Er(writeDict(obj->as.dictionary, msg)); break;
        case Object_LIST:    Er(writeList(obj->as.list, msg)); break;
        case Object_INTEGER: Er(writeInt(obj->as.number, msg)); break;
        default: Assert_failure("object of unknown type [%d]", obj->type);
    }
    Er_ret();
}

Er_DEFUN(void BencMessageWriter_write(Dict* toWrite, struct Message* msg))
{
    Er(writeDict(toWrite, msg));

    // lucky
    if (!((uintptr_t)msg->msgbytes % 8)) { Er_ret(); }

    char d = Er(Message_epop8(msg));
    Assert_true(d == 'd');
    Assert_true(msg->msgbytes[0] != 'e' && "Can't serialize empty messages");
    Assert_true(msg->msgbytes[0] >= '1' && msg->msgbytes[0] <= '9');

    // put the message into alignment by padding out the number with leading zeros :)
    do { Er(Message_epush8(msg, '0')); } while ((uintptr_t)msg->msgbytes % 8);

    Er(Message_epop8(msg));
    Er(Message_epush8(msg, 'd'));
    Er_ret();
}
