/* Copyright 2014 the SumatraPDF project authors (see AUTHORS file).
   License: Simplified BSD (see COPYING.BSD) */

#ifndef BencUtil_h
#define BencUtil_h

/* Handling of bencoded format. See:
   http://www.bittorrent.org/protocol.html or
   http://en.wikipedia.org/wiki/Bencode or
   http://wiki.theory.org/BitTorrentSpecification

   Note: As an exception to the above specifications, this implementation
         only handles zero-terminated strings (i.e. \0 may not appear within
         strings) and considers all encoded strings to be UTF-8 encoded
         (unless added with AddRaw and retrieved with RawValue()).
*/

#define __STDC_FORMAT_MACROS
#include <inttypes.h>

enum BencType { BT_STRING, BT_INT, BT_ARRAY, BT_DICT };

class BencObj {
    BencType type;

public:
    explicit BencObj(BencType type) : type(type) { }
    virtual ~BencObj() { }
    BencType Type() const { return type; }

    virtual char *Encode() const = 0;
    static BencObj *Decode(const char *bytes, size_t *lenOut=NULL);
};

class BencString : public BencObj {
    char *value;

public:
    explicit BencString(const WCHAR *value);
    BencString(const char *rawValue, size_t len);
    virtual ~BencString() { free(value); }

    WCHAR *Value() const;
    const char *RawValue() const { return value; }

    virtual char *Encode() const;
    static BencString *Decode(const char *bytes, size_t *lenOut);
};

class BencInt : public BencObj {
    int64_t value;

public:
    explicit BencInt(int64_t value) : BencObj(BT_INT), value(value) { }
    int64_t Value() const { return value; }

    virtual char *Encode() const;
    static BencInt *Decode(const char *bytes, size_t *lenOut);
};

class BencDict;

class BencArray : public BencObj {
    Vec<BencObj *> value;

public:
    BencArray() : BencObj(BT_ARRAY) { }
    virtual ~BencArray() { DeleteVecMembers(value); }
    size_t Length() const { return value.Count(); }

    void Add(BencObj *obj) {
        CrashIf(!obj || value.Contains(obj));
        if (!obj || value.Contains(obj)) return;
        value.Append(obj);
    }
    void Add(const WCHAR *string) {
        assert(string);
        if (string)
            Add(new BencString(string));
    }
    void AddRaw(const char *string, size_t len=-1) {
        assert(string);
        if (string)
            Add(new BencString(string, len));
    }
    void Add(int64_t val) {
        Add(new BencInt(val));
    }
    BencObj *Remove(size_t index);

    BencString *GetString(size_t index) const {
        if (index < Length() && value.At(index)->Type() == BT_STRING)
            return static_cast<BencString *>(value.At(index));
        return NULL;
    }
    BencInt *GetInt(size_t index) const {
        if (index < Length() && value.At(index)->Type() == BT_INT)
            return static_cast<BencInt *>(value.At(index));
        return NULL;
    }
    BencArray *GetArray(size_t index) const {
        if (index < Length() && value.At(index)->Type() == BT_ARRAY)
            return static_cast<BencArray *>(value.At(index));
        return NULL;
    }
    BencDict *GetDict(size_t index) const;

    virtual char *Encode() const;
    static BencArray *Decode(const char *bytes, size_t *lenOut);
};

class BencDict : public BencObj {
    Vec<char *> keys;
    Vec<BencObj *> values;
    size_t lastIdx;

    BencObj *GetObj(const char *key) const;

public:
    BencDict() : BencObj(BT_DICT), lastIdx(0) { }
    virtual ~BencDict() {
        FreeVecMembers(keys);
        DeleteVecMembers(values);
    }
    size_t Length() const { return values.Count(); }

    void Add(const char *key, BencObj *obj);
    void Add(const char *key, const WCHAR *string) {
        assert(string);
        if (string)
            Add(key, new BencString(string));
    }
    void AddRaw(const char *key, const char *string, size_t len=-1) {
        assert(string);
        if (string)
            Add(key, new BencString(string, len));
    }
    void Add(const char *key, int64_t val) {
        Add(key, new BencInt(val));
    }
    BencObj *Remove(const char *key);

    BencString *GetString(const char *key) const {
        BencObj *obj = GetObj(key);
        if (obj && obj->Type() == BT_STRING)
            return static_cast<BencString *>(obj);
        return NULL;
    }
    BencInt *GetInt(const char *key) const {
        BencObj *obj = GetObj(key);
        if (obj && obj->Type() == BT_INT)
            return static_cast<BencInt *>(obj);
        return NULL;
    }
    BencArray *GetArray(const char *key) const {
        BencObj *obj = GetObj(key);
        if (obj && obj->Type() == BT_ARRAY)
            return static_cast<BencArray *>(obj);
        return NULL;
    }
    BencDict *GetDict(const char *key) const {
        BencObj *obj = GetObj(key);
        if (obj && obj->Type() == BT_DICT)
            return static_cast<BencDict *>(obj);
        return NULL;
    }

    virtual char *Encode() const;
    static BencDict *Decode(const char *bytes, size_t *lenOut);
};

#endif
