#!/usr/bin/perl

use strict;
use warnings;

# This script can convert data from http://www.iana.org/assignments/ipfix/ipfix.xhtml ipfix standard
# represented in CSV form into form suitable for us
# http://www.iana.org/assignments/ipfix/ipfix-information-elements.csv
# to our C/C++ friendly storage format

open my $fl, "<", "ipfix_fields.csv" or die "Can't open input file";
open my $target_h_file, ">", "ipfix_rfc.hpp" or die "Can't open file";
open my $target_cpp_file, ">", "ipfix_rfc.cpp" or die "Can't open file";


my $type_length_hash = {
    unsigned8  => 1,
    unsigned16 => 2,
    unsigned32 => 4,
    signed32   => 4,
    unsigned64 => 8,

    ipv4Address => 4,
    ipv6Address => 16, 

    # types with unknown or variable length
    basicList => 0,
    boolean => 0,
    dateTimeMicroseconds => 0,
    dateTimeMilliseconds => 0,
    dateTimeNanoseconds => 0,
    dateTimeSeconds => 0,
    float64 => 0,
    macAddress => 0,
    octetArray => 0,
    string => 0,
    subTemplateList => 0, 
    subTemplateMultiList => 0,
};

my $field_id = 0;
my @intervals = ();

my $fields = {};
while (<$fl>) {
    chomp;

    if (/^(\d+\-\d+)/) {
        push @intervals, $1;
    }

    my $we_will_process_this_line = /^(\d+),/;

    # Skip descriptions and other crap
    unless ($we_will_process_this_line) {
        next;
    }

    # Numbers should growth monotonous
    if ($1 < $field_id) {
        next;
    }

    $field_id++;

    my @keys = ("id", "name", "data_type", "data_type_semantics", "status", "description",
        "units", "range", "reference", "requester", "revision", "date");

    my %data = ();
    @data{ @keys } = split /,/, $_;

    #print "$data{id} $data{name} $data{data_type}\n"; 
    $fields->{$data{"id"}} = \%data;
}

for my $interval (@intervals) {
    my ($interval_start, $interval_end) = $interval =~ /^(\d+)\-(\d+)$/;

    # Skip reserved interval up to 32767 because it's so long
    # Example: 503-32767	Unassigned
    next if $interval_end == 32767;

    for my $field_id ($interval_start .. $interval_end) {
        $fields->{$field_id} = { id => $field_id, name => 'Reserved' };
    }

    # print "Interval start $interval_start end $interval_end\n";
}

for my $field_id (sort { $a <=> $b } keys %$fields) {
    my $data = $fields->{$field_id};

    if ($data->{name} eq 'Reserved') {
        #print "$data->{id} $data->{name}\n";
    } else {
        #print "$data->{id} $data->{name} $data->{data_type}\n";
    }
}

print {$target_h_file} <<DOC;
#pragma once

/* This file is autogenerated with script ipfix_csv_processor.pl */
/* Please do not edit it directly */

#include <iostream>
#include <map>

class ipfix_information_element_t {
    public:
        ipfix_information_element_t(std::string name, unsigned int length);
        ipfix_information_element_t();
        std::string get_name();
        unsigned int get_length();
        std::string name; 
        unsigned int length;
};

typedef std::map<unsigned int, ipfix_information_element_t> ipfix_database_t;
class ipfix_information_database {
public:
    ipfix_information_database();
    bool add_element(unsigned int field_id, std::string name, unsigned int length);
    std::string get_name_by_id(unsigned int field_id);
    unsigned int get_length_by_id(unsigned int field_id);
private:
    ipfix_database_t database;
};

DOC

print {$target_cpp_file} <<DOC;
#include <iostream>
#include <map>
#include "ipfix_rfc.hpp"

/* This file is autogenerated with script ipfix_csv_processor.pl */
/* Please do not edit it directly */

std::string ipfix_information_element_t::get_name() {
    return this->name;
}

unsigned int ipfix_information_element_t::get_length() {
    return this->length;
}

ipfix_information_element_t::ipfix_information_element_t(std::string name, unsigned int length) {
    this->name = name;
    this->length = length;
}

ipfix_information_element_t::ipfix_information_element_t() {
    this->name = std::string("");
    this->length = 0;
}

std::string ipfix_information_database::get_name_by_id(unsigned int field_id) {
    auto itr = database.find(field_id);

    if (itr == database.end()) {
        return std::string("");
    }

    return itr->second.get_name();
}

unsigned int ipfix_information_database::get_length_by_id(unsigned int field_id) {
    auto itr = database.find(field_id);

    if (itr == database.end()) {
        return 0;
    }

    return itr->second.get_length();
}

bool ipfix_information_database::add_element(unsigned int field_id, std::string name, unsigned int length) {
    auto itr = database.find(field_id);
   
    // Duplicate ID's strictly prohibited 
    if (itr != database.end()) {
        return false;
    }

    database[field_id] = ipfix_information_element_t(name, length);
    return true;
}

ipfix_information_database::ipfix_information_database() {
DOC

for my $field_id (sort { $a <=> $b } keys %$fields) {
    my $data = $fields->{$field_id};

    my $field_length = 0;
    my $field_name = '"reserved"';

    if ($data->{"data_type"}) {
        $field_length = $type_length_hash->{ $data->{"data_type"} }; 
    }

    if (defined($data->{"name"}) && $data->{"name"}) {
        $field_name = "\"$data->{'name'}\"";
    }

    print {$target_cpp_file} "    this->add_element($field_id, $field_name, $field_length);\n";
}

print {$target_cpp_file} "}\n";
