/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>

#include "headers.p4"
#include "parsers.p4"
#include "checksums.p4"
#include "settings.p4"


/*************************************************************************
**************  I N G R E S S   P R O C E S S I N G   *******************
*************************************************************************/

control MyIngress(inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t standard_metadata) {

    /********************** ACTIONS ***********************************/

    action drop() {
        mark_to_drop();
    }

	action set_egress_port (port_t out_port) {
        standard_metadata.egress_spec = out_port;
	}

    action controller_debug() {
        meta.task = TASK_DEBUG;
        meta.ingress_port = standard_metadata.ingress_port;
        clone3(CloneType.I2E, 100, meta);
    }

    action controller_reply(task_t task) {
        meta.task = task;
        meta.ingress_port = standard_metadata.ingress_port;
        clone3(CloneType.I2E, 100, meta);
    }

    action multicast_pkg(mcast_t mcast_grp) {         /* Output PKG on correct ports (plural) */
        standard_metadata.mcast_grp = mcast_grp;
    }

    action icmp6_neighbor_solicitation(ipv6_addr_t addr) {
        /* egress = ingress */
        standard_metadata.egress_spec = standard_metadata.ingress_port;

        hdr.ipv6.dst_addr = hdr.ipv6.src_addr;
        hdr.ipv6.src_addr = addr;
        hdr.icmp6.type = ICMP6_NA;
    }

    action icmp6_echo_reply() {
        mac_addr_t mac_tmp = hdr.ethernet.dst_addr;
        hdr.ethernet.dst_addr = hdr.ethernet.src_addr;
        hdr.ethernet.src_addr = mac_tmp;

        ipv6_addr_t addr_tmp = hdr.ipv6.dst_addr;
        hdr.ipv6.dst_addr = hdr.ipv6.src_addr;
        hdr.ipv6.src_addr = addr_tmp;

        hdr.icmp6.type = ICMP6_ECHO_REPLY;

        meta.do_cksum = true;
        meta.cast_length = (bit<32>) hdr.ipv6.payload_length;
    }

    /* this needs SESSIONS!!
    - icmp6: (src addr, dst addr, ID??, )
    - tcp: (src port, dst port, dst_addr, src addr)
    - udp: (src port, dst port, dst_addr, src addr)
    */
    // action nat64_static(ipv4_addr_t nataddr, ipv6_addr_t nat64_prefix) {
    //     hdr.ipv4.dst_addr = hdr.ipv6.dst_addr - nat64_prefix;
    //     hdr.ipv4.dst_addr = hdr.ipv6.dst_addr - nat64_prefix;
    // }

    /********************** Reply to NDP for US ***********************************/
    table ndp_answer {
        key = {
            hdr.ipv6.dst_addr: exact; /* our multicast embedded mac address */
            hdr.icmp6.type: exact;
        }
        actions = {
            controller_debug;
            icmp6_neighbor_solicitation;
            NoAction;
        }
        size = NDP_TABLE_SIZE;
        default_action = NoAction;
    }

    /********************** debugging / general support ***********************************/

    table port2mcast {
        key = {
            standard_metadata.ingress_port : exact;
        }
        actions = {
			multicast_pkg;
            controller_debug;
            NoAction;
        }
        size = NDP_TABLE_SIZE;
        default_action = NoAction;
//        default_action = controller_debug;
    }

    /* Handle multicast registration of NDP */
    table addr2mcast {
        key = {
            hdr.ipv6.dst_addr: exact;
        }
        actions = {
			multicast_pkg;
            controller_debug;
            NoAction;
        }
        size = NDP_TABLE_SIZE;
        default_action = NoAction;
//        default_action = controller_debug;
    }

    /********************** NDP support ***********************************/


    table ndp {
        key = {
            hdr.ipv6.dst_addr: lpm;
            standard_metadata.ingress_port : exact;
        }
        actions = {
			multicast_pkg;
            controller_debug;
            NoAction;
        }
        size = NDP_TABLE_SIZE;
//        default_action = NoAction;
        default_action = controller_debug;
    }


    /********************** ADDRESS TABLES ***********************************/
    action icmp6_answer() {

        if(hdr.icmp6.isValid()) {
            if(hdr.icmp6.code == ICMP6_ECHO_REQUEST) {
                ipv6_addr_t tmp = hdr.ipv6.src_addr;
                hdr.ipv6.src_addr = hdr.ipv6.dst_addr;
                hdr.ipv6.dst_addr = tmp;
                hdr.icmp6.code = ICMP6_ECHO_REPLY;
            }
        }

        /* do something:
        - change src/dst
        - change type
        */
    }


	/********************** ROUTING (egress definiton) TABLES ***********************************/

    table v6_addresses {
        key = {
            hdr.ipv6.dst_addr: exact;
        }
        actions = {
            controller_debug;
            controller_reply;
            icmp6_echo_reply;
            NoAction;
        }
        size = ADDRESS_TABLE_SIZE;
        default_action = NoAction;

    }

    table v6_networks {
        key = {
            hdr.ipv6.dst_addr: lpm;
        }
        actions = {
			set_egress_port;
            controller_debug;
            controller_reply;
            NoAction;
        }
        size = ROUTING_TABLE_SIZE;
        default_action = NoAction;
    }

    table v4_networks {
        key = {
            hdr.ipv4.dst_addr: lpm;
        }
        actions = {
			set_egress_port;
            NoAction;
        }
        size = ROUTING_TABLE_SIZE;
        default_action = NoAction;
    }

	/********************** APPLYING TABLES ***********************************/
    apply {
        if(hdr.ipv6.isValid()) {
            /* FIXME: structure / use .hit to do logic */
            // ndp_answer.apply();
            //ndp.apply(); /* flood or if it is us - answer */

            v6_addresses.apply();
            v6_networks.apply();
        }
        if(hdr.ipv4.isValid()) {
            v4_networks.apply();
        }
    }
}

/*************************************************************************
****************  E G R E S S   P R O C E S S I N G   *******************
*************************************************************************/

control MyEgress(inout headers hdr,
                 inout metadata meta,
                 inout standard_metadata_t standard_metadata) {
	apply {
        // ingress clone
        if (standard_metadata.instance_type == 1){
            hdr.cpu.setValid();
            hdr.cpu.task = meta.task;
            hdr.cpu.ethertype = hdr.ethernet.ethertype;
            hdr.cpu.ingress_port = (bit<16>)meta.ingress_port;

            hdr.ethernet.ethertype = TYPE_CPU;
        }
	}
}

/*************************************************************************
***********************  S W I T C H  *******************************
*************************************************************************/

V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;


        //     truncate((bit<32>)22); //ether+cpu header