/*
 * Copyright (C) 2021 Canonical, Ltd.
 * Author: Simon Chopin <simon.chopin@canonical.com>
 *
 * 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; version 3.
 *
 * 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 <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "parse.h"
#include <glib.h>
#include <yaml.h>
#include <uuid.h>

/* Quite a few types are part of our current ABI, and so were isolated
 * in order to make it easier to tell what's fair game and allow for ABI
 * compatibility checks using 'abidiff' (abigail-tools). */
#include "abi.h"

typedef enum {
    NETPLAN_ADDRGEN_DEFAULT,
    NETPLAN_ADDRGEN_EUI64,
    NETPLAN_ADDRGEN_STABLEPRIVACY,
    NETPLAN_ADDRGEN_MAX,
} NetplanAddrGenMode;

struct NetplanOptionalAddressType {
    char* name;
    NetplanOptionalAddressFlag flag;
};

typedef enum {
    NETPLAN_VXLAN_NOTIFICATION_L2_MISS = 1<<0,
    NETPLAN_VXLAN_NOTIFICATION_L3_MISS = 1<<1,
} NetplanVxlanNotificationFlags;

typedef enum {
    NETPLAN_VXLAN_CHECKSUM_UDP = 1<<0,
    NETPLAN_VXLAN_CHECKSUM_ZERO_UDP6_TX = 1<<1,
    NETPLAN_VXLAN_CHECKSUM_ZERO_UDP6_RX = 1<<2,
    NETPLAN_VXLAN_CHECKSUM_REMOTE_TX = 1<<3,
    NETPLAN_VXLAN_CHECKSUM_REMOTE_RX = 1<<4,
} NetplanVxlanChecksumFlags;

typedef enum {
    NETPLAN_VXLAN_EXTENSION_GROUP_POLICY = 1<<0,
    NETPLAN_VXLAN_EXTENSION_GENERIC_PROTOCOL = 1<<1,
} NetplanVxlanExtensionFlags;

// Not strictly speaking a type, but seems fair to keep it around.
extern struct NetplanOptionalAddressType NETPLAN_OPTIONAL_ADDRESS_TYPES[];

extern struct NetplanWifiWowlanType NETPLAN_WIFI_WOWLAN_TYPES[];

typedef struct missing_node {
    char* netdef_id;
    const yaml_node_t* node;
} NetplanMissingNode;

struct private_netdef_data {
    GHashTable* dirty_fields;
};

typedef enum {
    NETPLAN_WIFI_MODE_INFRASTRUCTURE,
    NETPLAN_WIFI_MODE_ADHOC,
    NETPLAN_WIFI_MODE_AP,
    NETPLAN_WIFI_MODE_OTHER,
    NETPLAN_WIFI_MODE_MAX_
} NetplanWifiMode;

typedef struct {
    char *endpoint;
    char *public_key;
    char *preshared_key;
    GArray *allowed_ips;
    guint keepalive;
} NetplanWireguardPeer;

typedef enum {
    NETPLAN_WIFI_BAND_DEFAULT,
    NETPLAN_WIFI_BAND_5,
    NETPLAN_WIFI_BAND_24
} NetplanWifiBand;

typedef struct {
    char* address;
    char* lifetime;
    char* label;
} NetplanAddressOptions;

struct address_iter {
    guint ip4_index;
    guint ip6_index;
    guint address_options_index;
    NetplanNetDefinition* netdef;
    NetplanAddressOptions* last_address;
};

struct nameserver_iter {
    guint ip4_index;
    guint ip6_index;
    guint search_index;
    NetplanNetDefinition* netdef;
};

struct route_iter {
    guint route_index;
    NetplanNetDefinition* netdef;
};

typedef struct {
    NetplanWifiMode mode;
    char* ssid;
    NetplanWifiBand band;
    char* bssid;
    gboolean hidden;
    guint channel;

    NetplanAuthenticationSettings auth;
    gboolean has_auth;

    NetplanBackendSettings backend_settings;
} NetplanWifiAccessPoint;

typedef struct {
    gint family;
    char* type;
    char* scope;
    guint table;

    char* from;
    char* to;
    char* via;

    gboolean onlink;

    /* valid metrics are valid positive integers.
     * invalid metrics are represented by METRIC_UNSPEC */
    guint metric;

    guint mtubytes;
    guint congestion_window;
    guint advertised_receive_window;
    guint advmss;
} NetplanIPRoute;

typedef struct {
    gint family;

    char* from;
    char* to;

    /* table: Valid values are 1 <= x <= 4294967295) */
    guint table;
    guint priority;
    /* fwmark: Valid values are 1 <= x <= 4294967295) */
    guint fwmark;
    /* type-of-service: between 0 and 255 */
    guint tos;
} NetplanIPRule;

struct netplan_vxlan {
        NetplanNetDefinition* link;
        guint vni;
        guint ageing;
        guint limit;
        guint tos;
        guint flow_label;
        guint source_port_min;
        guint source_port_max;
        NetplanTristate mac_learning;
        NetplanTristate arp_proxy;
        NetplanTristate short_circuit;
        gboolean independent;
        NetplanFlags notifications;
        NetplanFlags checksums;
        NetplanFlags extensions;
        NetplanTristate do_not_fragment;
};

struct netplan_state {
    /* Since both netdefs and netdefs_ordered store pointers to the same elements,
     * we consider that only netdefs_ordered is owner of this data. One should not
     * free() objects obtained from netdefs, and proper care should be taken to remove
     * any reference of an object in netdefs when destroying it from netdefs_ordered.
     */
    GHashTable *netdefs;
    GList *netdefs_ordered;
    NetplanBackend backend;
    NetplanOVSSettings ovs_settings;

    /* Hashset of the source files used to create this state. Owns its data (glib-allocated
     * char*) and is initialized with g_hash_table_new_full to avoid leaks. */
    GHashTable* sources;
    GHashTable* global_renderer;

    /* Flags used to change the state's behavior */
    unsigned int flags;
};

struct netplan_parser {
    yaml_document_t doc;
    /* Netplan definitions that have already been processed.
     * Weak references to the nedefs */
    GHashTable* parsed_defs;
    /* Same definitions, stored in the order of processing.
     * Owning structure for the netdefs */
    GList* ordered;
    NetplanBackend global_backend;
    NetplanOVSSettings global_ovs_settings;

    /* Keep track of the files used as data source */
    GHashTable* sources;

    /* Data currently being processed */
    struct {
        /* Refs to objects allocated elsewhere */
        NetplanNetDefinition* netdef;
        NetplanAuthenticationSettings *auth;

        /* Owned refs, not yet referenced anywhere */
        NetplanWifiAccessPoint *access_point;
        NetplanWireguardPeer* wireguard_peer;
        NetplanAddressOptions* addr_options;
        NetplanIPRoute* route;
        NetplanIPRule* ip_rule;
        NetplanVxlan* vxlan;
        const char *filepath;

        /* Plain old data representing the backend for which we are
         * currently parsing. Not necessarily the same as the global
         * backend. */
        NetplanBackend backend;
    } current;

    /* List of "seen" ids not found in netdefs yet by the parser.
     * These are removed when it exists in this list and we reach the point of
     * creating a netdef for that id; so by the time we're done parsing the yaml
     * document it should be empty.
     *
     * Keys are not owned, but the values are. Should be created with NULL and g_free
     * destructors, respectively, so that the cleanup is automatic at destruction.
     */
    GHashTable* missing_id;

    /* Set of IDs in currently parsed YAML file, for being able to detect
     * "duplicate ID within one file" vs. allowing a drop-in to override/amend an
     * existing definition.
     *
     * Appears to be unused?
     * */
    GHashTable* ids_in_file;
    guint missing_ids_found;

    /* Which fields have been nullified by a subsequent patch? */
    GHashTable* null_fields;
    GHashTable* null_overrides;
    GHashTable* global_renderer;

    /* Flags used to change the parser's behavior */
    unsigned int flags;

    /* Number of parsing errors
     * Records the number of parsing errors that happened
     * when the flag IGNORE_ERRORS is set
     * */
    unsigned int error_count;
};

struct netplan_state_iterator {
    GList* next;
};

/**
 * @brief   Flags used to change the state behavior.
 */
typedef enum {
    NETPLAN_STATE_VALIDATION_ONLY = 1 << 0, ///< Only validate the configuration without applying changes.
    NETPLAN_STATE_FLAGS_MAX_,
} NetplanStateFlags;

#define NETPLAN_ADVERTISED_RECEIVE_WINDOW_UNSPEC 0
#define NETPLAN_CONGESTION_WINDOW_UNSPEC 0
#define NETPLAN_MTU_UNSPEC 0
#define NETPLAN_METRIC_UNSPEC G_MAXUINT
#define NETPLAN_ROUTE_TABLE_UNSPEC 0
#define NETPLAN_IP_RULE_PRIO_UNSPEC G_MAXUINT
#define NETPLAN_IP_RULE_FW_MARK_UNSPEC 0
#define NETPLAN_IP_RULE_TOS_UNSPEC G_MAXUINT
#define NETPLAN_ADVMSS_UNSPEC 0

#if defined(UNITTESTS)
#define STATIC
#else
#define STATIC static
#endif

void
reset_netdef(NetplanNetDefinition* netdef, NetplanDefType type, NetplanBackend renderer);

void
reset_ip_rule(NetplanIPRule* ip_rule);

void
reset_ovs_settings(NetplanOVSSettings *settings);

void
reset_vxlan(NetplanVxlan* vxlan);

void
access_point_clear(NetplanWifiAccessPoint** ap, NetplanBackend backend);

void
wireguard_peer_clear(NetplanWireguardPeer** peer);

void
address_options_clear(NetplanAddressOptions** options);

void
ip_rule_clear(NetplanIPRule** rule);

void
route_clear(NetplanIPRoute** route);

gboolean
netplan_state_has_nondefault_globals(const NetplanState* np_state);

void
clear_netdef_from_list(void* def);

void
free_address_options(void* ptr);

void
free_access_point(void* key, void* value, void* data);

/**
 * @brief   Set @ref NetplanState flags.
 * @details State flags are used to change the default behavior of the state.
 * @param[in] np_state  The @ref NetplanState to set the flags.
 * @param[in] flags     The value of the flags. The possible values are defined in @ref NetplanStateFlags
 * @param[out] error    Filled with a @ref NetplanError in case of failure
 * @return              Indication of success or failure
 */
NETPLAN_INTERNAL gboolean
_netplan_state_set_flags(NetplanState* np_state, unsigned int flags, NetplanError** error);

/**
 * @brief   Get @ref NetplanState flags.
 * @details State flags are used to change the default behavior of the state.
 * @param[in] np_state  The @ref NetplanState to get the flags from.
 * @return   The current flags set in the state.
 */
NETPLAN_INTERNAL unsigned int
_netplan_state_get_flags(const NetplanState* np_state);
