/* 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/>.
 */
#ifndef EncodingScheme_H
#define EncodingScheme_H

#include "benc/Object.h"
#include "memory/Allocator.h"
#include "util/Linker.h"
Linker_require("switch/EncodingScheme.c")

#include <stdint.h>
#include <stdbool.h>

struct EncodingScheme_Form
{
    // these only require 5 bits each but use 16 to avoid compiler injected padding
    // which can screw up comparison by memcmp()
    uint16_t bitCount;
    uint16_t prefixLen;
    uint32_t prefix;
};

struct EncodingScheme
{
    struct EncodingScheme_Form* forms;
    int count;
};

/**
 * Get the number of the encoding form used to encode the first director in the given label.
 * If there is no matching prefix for any of the forms then EncodingScheme_getFormNum_INVALID is
 * returned.
 */
#define EncodingScheme_getFormNum_INVALID -1
int EncodingScheme_getFormNum(struct EncodingScheme* scheme, uint64_t routeLabel);

/**
 * Convert the first director in a label from it's current form to the form given by convertTo.
 * convertTo must be between zero and scheme->count unless you are asking for the cannonical form
 * IE: the smallest possible represenation in which case convertTo should be:
 * EncodingScheme_convertLabel_convertTo_CANNONICAL
 *
 * The return value will be the converted label unless conversion is not possible because the
 * format of the label is invalid, the format of the label does not match the scheme, or the
 * label cannot be converted without overrunning the acceptable label size in which case
 * EncodingScheme_convertLabel_INVALID will be returned.
 */
#define EncodingScheme_convertLabel_convertTo_CANNONICAL (-5000)
#define EncodingScheme_convertLabel_INVALID (~((uint64_t)0))
uint64_t EncodingScheme_convertLabel(struct EncodingScheme* scheme,
                                     uint64_t routeLabel,
                                     int convertTo);

/**
 * Check that the given encoding scheme is conformant to protocol.
 * This will not check some things which are impossible to serialize (will never come from a
 * working deserializer) such as unrepresentable bit-widths but it will check that a representable
 * encoding scheme is indeed valid according to the protocol.
 */
bool EncodingScheme_isSane(struct EncodingScheme* scheme);

String* EncodingScheme_serialize(struct EncodingScheme* list,
                                 struct Allocator* alloc);

struct EncodingScheme* EncodingScheme_deserialize(String* data,
                                                  struct Allocator* alloc);

struct EncodingScheme* EncodingScheme_defineFixedWidthScheme(int bitCount, struct Allocator* alloc);


struct EncodingScheme* EncodingScheme_defineDynWidthScheme(struct EncodingScheme_Form* forms,
                                                           int formCount,
                                                           struct Allocator* alloc);

#define EncodingScheme_clone(scheme, alloc) \
    EncodingScheme_defineDynWidthScheme((scheme)->forms, (scheme)->count, (alloc))

static inline int EncodingScheme_formSize(const struct EncodingScheme_Form* form)
{
    return form->bitCount + form->prefixLen;
}

int EncodingScheme_compare(struct EncodingScheme* a, struct EncodingScheme* b);

#define EncodingScheme_equals(a,b) (!EncodingScheme_compare(a,b))

struct EncodingScheme* EncodingScheme_fromList(List* scheme, struct Allocator* alloc);
List* EncodingScheme_asList(struct EncodingScheme* list, struct Allocator* alloc);

/**
 * Return true if the route is to the switch's router interface.
 */
int EncodingScheme_isSelfRoute(struct EncodingScheme* scheme, uint64_t routeLabel);

/**
 * @return non-zero if the route label is one hop.
 */
int EncodingScheme_isOneHop(struct EncodingScheme* scheme, uint64_t routeLabel);

#define EncodingScheme_parseDirector_INVALID -1
int EncodingScheme_parseDirector(struct EncodingScheme* scheme, uint64_t label);

uint64_t EncodingScheme_serializeDirector(struct EncodingScheme* scheme, int number, int formNum);

// Return true if the encoding scheme is the legacy 358 encoding scheme
// which gets special treatment.
bool EncodingScheme_is358(struct EncodingScheme* scheme);

#endif