package org.freertr.rtr;

import java.util.ArrayList;
import java.util.List;
import org.freertr.addr.addrIP;
import org.freertr.addr.addrIPv4;
import org.freertr.addr.addrPrefix;
import org.freertr.pack.packDnsRec;
import org.freertr.tab.tabGen;
import org.freertr.tab.tabLabelBier;
import org.freertr.tab.tabRoute;
import org.freertr.tab.tabRouteEntry;
import org.freertr.user.userUpgrade;
import org.freertr.util.bits;
import org.freertr.util.cmds;
import org.freertr.spf.spfCalc;

/**
 * lsrp data
 *
 * @author matecsaba
 */
public class rtrLsrpData implements Comparable<rtrLsrpData> {

    /**
     * create instance
     */
    public rtrLsrpData() {
    }

    /**
     * full dump
     */
    public final static int dmpFull = -1;

    /**
     * compare dump
     */
    public final static int dmpComp = dmpFull - 0x800 - 0x200 - 0x100 - 0x80 - 0x8 - 0x4;

    /**
     * checksum dump
     */
    public final static int dmpCsum = dmpComp - 0x4000;

    /**
     * state dump
     */
    public final static int dmpSave = dmpFull - 0x8;

    /**
     * router id
     */
    public addrIPv4 rtrId;

    /**
     * management address
     */
    public addrIP mgmtIp;

    /**
     * other management address
     */
    public addrIP mgmtOp;

    /**
     * topology summary
     */
    public int topoSum;

    /**
     * checksum
     */
    public String password;

    /**
     * router name
     */
    public String hostname;

    /**
     * domain name
     */
    public String domain;

    /**
     * software
     */
    public String software;

    /**
     * hardware
     */
    public String hardware;

    /**
     * forwarder
     */
    public String forwarder;

    /**
     * middleware
     */
    public String middleware;

    /**
     * kernel
     */
    public String kernel;

    /**
     * kernel
     */
    public String kernup;

    /**
     * segment routing maximum
     */
    public int segrouMax;

    /**
     * segment routing base
     */
    public int segrouBeg;

    /**
     * bier length
     */
    public int bierLen;

    /**
     * bier maximum
     */
    public int bierMax;

    /**
     * bier base
     */
    public int bierBeg;

    /**
     * sequence number
     */
    public int sequence;

    /**
     * time
     */
    public long time;

    /**
     * uptime
     */
    public long uptime;

    /**
     * first time
     */
    public long since;

    /**
     * number of changes
     */
    public int changesNum;

    /**
     * last changed
     */
    public long changesTim;

    /**
     * advertised routes
     */
    public tabRoute<addrIP> network;

    /**
     * advertised addresses
     */
    public tabGen<rtrLsrpDataAddr> address;

    /**
     * advertised neighbors
     */
    public tabGen<rtrLsrpDataNeigh> neighbor;

    /**
     * flexible algorithms
     */
    public List<Integer> flexalgo;

    public int compareTo(rtrLsrpData o) {
        return rtrId.compareTo(o.rtrId);
    }

    /**
     * clone this data
     *
     * @return copied data object
     */
    public rtrLsrpData copyHead() {
        rtrLsrpData n = new rtrLsrpData();
        n.rtrId = rtrId.copyBytes();
        n.sequence = sequence;
        n.uptime = uptime;
        return n;
    }

    /**
     * test if differs from other
     *
     * @param other data to compare
     * @return true if differs, false if not
     */
    public boolean differs(rtrLsrpData other) {
        if (other == null) {
            return true;
        }
        return (other.sequence != sequence) || (other.uptime != uptime);
    }

    /**
     * test if better from other
     *
     * @param other data to compare
     * @return true if better, false if not
     */
    public boolean better(rtrLsrpData other) {
        if (other == null) {
            return true;
        }
        if (other.sequence < sequence) {
            return true;
        }
        if (other.sequence > sequence) {
            return false;
        }
        return other.uptime > uptime;
    }

    /**
     * dump out this data
     *
     * @param typ type to use: 0x1=id, 0x2=nam, 0x4=seq, 0x8=time, 0x10=neighs,
     * 0x20=nets, 0x40=sr, 0x80=uptime 0x100=change 0x200=version, 0x400=bier
     * 0x800=toposum, 0x1000=addrs, 0x2000=mgmtip, 0x4000=password, 0x8000=since
     * 0x10000=algo
     * @return dumped data
     */
    public String dump(int typ) {
        String s = "";
        if ((typ & 0x1) != 0) {
            s += " rtrid=" + rtrId;
        }
        if ((typ & 0x2) != 0) {
            s += " hostname=" + hostname;
            s += " domain=" + domain;
        }
        if ((typ & 0x40) != 0) {
            s += " segroubeg=" + segrouBeg;
            s += " segroumax=" + segrouMax;
        }
        if ((typ & 0x10000) != 0) {
            String a = "";
            for (int i = 0; i < flexalgo.size(); i++) {
                a += "," + flexalgo.get(i);
            }
            if (a.length() > 0) {
                a = a.substring(1, a.length());
            }
            s += " flexalgo=" + a;
        }
        if ((typ & 0x400) != 0) {
            s += " bierbeg=" + bierBeg;
            s += " biermax=" + bierMax;
            s += " bierlen=" + bierLen;
        }
        int metric = 0;
        boolean stub = false;
        long bndwdt = 0;
        int affinity = 0;
        int srlg = 0;
        int mtu = 0;
        int segrouAdj = 0;
        int segrouIdx = 0;
        boolean segrouPop = false;
        boolean external = false;
        int bierIdx = 0;
        int bierSub = 0;
        int tag = 0;
        String iface = "";
        if ((typ & 0x10) != 0) {
            for (int i = 0; i < neighbor.size(); i++) {
                rtrLsrpDataNeigh ntry = neighbor.get(i);
                if (!iface.equals(ntry.iface)) {
                    s += " interface=" + ntry.iface;
                    iface = ntry.iface;
                }
                if (ntry.metric != metric) {
                    s += " metric=" + ntry.metric;
                    metric = ntry.metric;
                }
                if (ntry.stub != stub) {
                    s += " stub=" + ntry.stub;
                    stub = ntry.stub;
                }
                if (ntry.bndwdt != bndwdt) {
                    s += " bandwidth=" + ntry.bndwdt;
                    bndwdt = ntry.bndwdt;
                }
                if (ntry.srlg != srlg) {
                    s += " srlg=" + ntry.srlg;
                    srlg = ntry.srlg;
                }
                if (ntry.mtu != mtu) {
                    s += " mtu=" + ntry.mtu;
                    mtu = ntry.mtu;
                }
                if (ntry.affnty != affinity) {
                    s += " affinity=" + ntry.affnty;
                    affinity = ntry.affnty;
                }
                if (ntry.segrou != segrouAdj) {
                    s += " segrouadj=" + ntry.segrou;
                    segrouAdj = ntry.segrou;
                }
                if (!ntry.opeer.isEmpty()) {
                    s += " peeraddr=" + ntry.opeer;
                }
                s += " peeraddr=" + ntry.peer;
                s += " peeriface=" + ntry.perif;
                s += " neighbor=" + ntry.rtrid;
            }
        }
        if ((typ & 0x20) != 0) {
            for (int i = 0; i < network.size(); i++) {
                tabRouteEntry<addrIP> ntry = network.get(i);
                if (ntry.best.metric != metric) {
                    s += " metric=" + ntry.best.metric;
                    metric = ntry.best.metric;
                }
                if (ntry.best.tag != tag) {
                    s += " tag=" + ntry.best.tag;
                    tag = ntry.best.tag;
                }
                boolean ext = (ntry.best.rouSrc & 1) != 0;
                if (ext != external) {
                    s += " external=" + ext;
                    external = ext;
                }
                boolean srp = (ntry.best.rouSrc & 16) != 0;
                if (srp != segrouPop) {
                    s += " segroupop=" + srp;
                    segrouPop = srp;
                }
                if (ntry.best.segrouIdx != segrouIdx) {
                    s += " segrouidx=" + ntry.best.segrouIdx;
                    segrouIdx = ntry.best.segrouIdx;
                }
                if (ntry.best.bierIdx != bierIdx) {
                    s += " bieridx=" + ntry.best.bierIdx;
                    bierIdx = ntry.best.bierIdx;
                }
                if (ntry.best.bierSub != bierSub) {
                    s += " biersub=" + ntry.best.bierSub;
                    bierSub = ntry.best.bierSub;
                }
                s += " network=" + addrPrefix.ip2str(ntry.prefix);
            }
        }
        if ((typ & 0x200) != 0) {
            s += " software=" + software;
            s += " hardware=" + hardware;
            s += " forwarder=" + forwarder;
            s += " middleware=" + middleware;
            s += " kernel=" + kernel;
            s += " kernup=" + kernup;
        }
        if ((typ & 0x4) != 0) {
            s += " sequence=" + sequence;
        }
        if ((typ & 0x100) != 0) {
            s += " changenum=" + changesNum;
            s += " changetim=" + changesTim;
        }
        if ((typ & 0x800) != 0) {
            s += " toposum=" + topoSum;
        }
        if ((typ & 0x1000) != 0) {
            for (int i = 0; i < address.size(); i++) {
                rtrLsrpDataAddr ntry = address.get(i);
                if (!iface.equals(ntry.iface)) {
                    s += " interface=" + ntry.iface;
                    iface = ntry.iface;
                }
                s += " address=" + ntry.addr;
            }
        }
        if ((typ & 0x2000) != 0) {
            if (!mgmtOp.isEmpty()) {
                s += " mgmtaddr=" + mgmtOp;
            }
            s += " mgmtaddr=" + mgmtIp;
        }
        if ((typ & 0x4000) != 0) {
            s += " password=" + password;
        }
        if ((typ & 0x8000) != 0) {
            s += " since=" + since;
        }
        if ((typ & 0x80) != 0) {
            s += " uptime=" + uptime;
        }
        if ((typ & 0x8) != 0) {
            s += " time=" + (time - bits.getTime());
        }
        return s.trim() + " ending=true";
    }

    /**
     * read up data
     *
     * @param cmd where from read
     * @return false on success, true on error
     */
    public boolean fromString(cmds cmd) {
        rtrId = new addrIPv4();
        hostname = "";
        domain = "";
        software = "";
        hardware = "";
        forwarder = "";
        middleware = "";
        password = "none";
        kernel = "";
        kernup = "";
        topoSum = 0;
        sequence = 0;
        segrouBeg = 0;
        int segrouIdx = 0;
        segrouMax = 0;
        boolean segrouPop = false;
        bierBeg = 0;
        bierMax = 0;
        int bierIdx = 0;
        int bierSub = 0;
        bierLen = 0;
        time = 0;
        uptime = 0;
        changesNum = 0;
        changesTim = 0;
        since = bits.getTime();
        addrIP peerAddr = new addrIP();
        addrIP peerOddr = new addrIP();
        String peerIf = "unknown";
        mgmtIp = new addrIP();
        mgmtOp = new addrIP();
        String iface = "unknown";
        boolean stub = false;
        int segrouAdj = 0;
        int metric = 0;
        long bndwdt = 0;
        int affinity = 0;
        int srlg = 0;
        int mtu = 0;
        boolean external = false;
        int tag = 0;
        address = new tabGen<rtrLsrpDataAddr>();
        network = new tabRoute<addrIP>("net");
        neighbor = new tabGen<rtrLsrpDataNeigh>();
        flexalgo = new ArrayList<Integer>();
        for (;;) {
            String a = cmd.word();
            if (a.length() < 1) {
                return true;
            }
            int i = a.indexOf("=");
            if (i < 0) {
                return true;
            }
            String s = a.substring(i + 1, a.length()).trim();
            a = a.substring(0, i).trim().toLowerCase();
            if (a.equals("ending")) {
                return false;
            }
            if (a.equals("rtrid")) {
                if (rtrId.fromString(s)) {
                    return true;
                }
                continue;
            }
            if (a.equals("password")) {
                password = s;
                continue;
            }
            if (a.equals("flexalgo")) {
                cmds c = new cmds("alg", s);
                for (;;) {
                    a = c.word(",");
                    if (a.length() < 1) {
                        break;
                    }
                    flexalgo.add(bits.str2num(a));
                }
                continue;
            }
            if (a.equals("external")) {
                external = s.toLowerCase().equals("true");
                continue;
            }
            if (a.equals("stub")) {
                stub = s.toLowerCase().equals("true");
                continue;
            }
            if (a.equals("toposum")) {
                topoSum = bits.str2num(s);
                continue;
            }
            if (a.equals("hostname")) {
                hostname = s;
                continue;
            }
            if (a.equals("domain")) {
                domain = s;
                continue;
            }
            if (a.equals("software")) {
                software = s;
                continue;
            }
            if (a.equals("hardware")) {
                hardware = s;
                continue;
            }
            if (a.equals("forwarder")) {
                forwarder = s;
                continue;
            }
            if (a.equals("middleware")) {
                middleware = s;
                continue;
            }
            if (a.equals("kernel")) {
                kernel = s;
                continue;
            }
            if (a.equals("kernup")) {
                kernup = s;
                continue;
            }
            if (a.equals("segroubeg")) {
                segrouBeg = bits.str2num(s);
                continue;
            }
            if (a.equals("segroumax")) {
                segrouMax = bits.str2num(s);
                continue;
            }
            if (a.equals("segrouidx")) {
                segrouIdx = bits.str2num(s);
                continue;
            }
            if (a.equals("segroupop")) {
                segrouPop = s.toLowerCase().equals("true");
                continue;
            }
            if (a.equals("segrouadj")) {
                segrouAdj = bits.str2num(s);
                continue;
            }
            if (a.equals("bierbeg")) {
                bierBeg = bits.str2num(s);
                continue;
            }
            if (a.equals("biermax")) {
                bierMax = bits.str2num(s);
                continue;
            }
            if (a.equals("bieridx")) {
                bierIdx = bits.str2num(s);
                continue;
            }
            if (a.equals("biersub")) {
                bierSub = bits.str2num(s);
                continue;
            }
            if (a.equals("bierlen")) {
                bierLen = bits.str2num(s);
                continue;
            }
            if (a.equals("sequence")) {
                sequence = bits.str2num(s);
                continue;
            }
            if (a.equals("time")) {
                time = bits.str2long(s) + bits.getTime();
                continue;
            }
            if (a.equals("uptime")) {
                uptime = bits.str2long(s);
                continue;
            }
            if (a.equals("since")) {
                since = bits.str2long(s);
                continue;
            }
            if (a.equals("changenum")) {
                changesNum = bits.str2num(s);
                continue;
            }
            if (a.equals("changetim")) {
                changesTim = bits.str2long(s);
                continue;
            }
            if (a.equals("metric")) {
                metric = bits.str2num(s);
                continue;
            }
            if (a.equals("bandwidth")) {
                bndwdt = bits.str2long(s);
                continue;
            }
            if (a.equals("affinity")) {
                affinity = bits.str2num(s);
                continue;
            }
            if (a.equals("srlg")) {
                srlg = bits.str2num(s);
                continue;
            }
            if (a.equals("mtu")) {
                mtu = bits.str2num(s);
                continue;
            }
            if (a.equals("tag")) {
                tag = bits.str2num(s);
                continue;
            }
            if (a.equals("mgmtaddr")) {
                addrIP adr = new addrIP();
                if (adr.fromString(s)) {
                    return true;
                }
                mgmtOp = mgmtIp;
                mgmtIp = adr;
                continue;
            }
            if (a.equals("address")) {
                rtrLsrpDataAddr ntry = new rtrLsrpDataAddr();
                if (ntry.addr.fromString(s)) {
                    return true;
                }
                ntry.iface = iface;
                address.add(ntry);
                continue;
            }
            if (a.equals("interface")) {
                iface = s;
                continue;
            }
            if (a.equals("peeriface")) {
                peerIf = s;
                continue;
            }
            if (a.equals("peeraddr")) {
                addrIP adr = new addrIP();
                if (adr.fromString(s)) {
                    return true;
                }
                peerOddr = peerAddr;
                peerAddr = adr;
                continue;
            }
            if (a.equals("network")) {
                tabRouteEntry<addrIP> ntry = new tabRouteEntry<addrIP>();
                ntry.prefix = addrPrefix.str2ip(s);
                if (ntry.prefix == null) {
                    return true;
                }
                ntry.best.metric = metric;
                ntry.best.tag = tag;
                ntry.best.bierIdx = bierIdx;
                ntry.best.bierSub = bierSub;
                ntry.best.segrouIdx = segrouIdx;
                ntry.best.rouSrc = segrouPop ? 16 : 0;
                if (external) {
                    ntry.best.rouSrc |= 1;
                }
                network.add(tabRoute.addType.better, ntry, true, true);
                continue;
            }
            if (a.equals("neighbor")) {
                addrIPv4 adr = new addrIPv4();
                if (adr.fromString(s)) {
                    return true;
                }
                addNeigh(adr, iface, metric, stub, bndwdt, affinity, srlg, mtu, segrouAdj, peerAddr, peerOddr, peerIf);
                peerAddr = new addrIP();
                peerOddr = new addrIP();
                continue;
            }
        }
    }

    /**
     * add address
     *
     * @param ifc interface
     * @param adr interface address
     */
    protected void addAddr(String ifc, addrIP adr) {
        rtrLsrpDataAddr ntry = new rtrLsrpDataAddr();
        ntry.addr.setAddr(adr);
        ntry.iface = ifc;
        address.add(ntry);
    }

    /**
     * add neighbor
     *
     * @param nei router id
     * @param ifc interface
     * @param met metric
     * @param stb stub flag
     * @param bw bandwidth
     * @param aff affinity
     * @param srl srlg
     * @param mtu mtu
     * @param adj segrout adjacency
     * @param adr adjacency address
     * @param odr other adjacency address
     * @param pif peer interface
     */
    protected void addNeigh(addrIPv4 nei, String ifc, int met, boolean stb, long bw, int aff, int srl, int mtu, int adj, addrIP adr, addrIP odr, String pif) {
        rtrLsrpDataNeigh ntry = new rtrLsrpDataNeigh();
        ntry.rtrid = nei.copyBytes();
        ntry.metric = met;
        ntry.stub = stb;
        ntry.iface = ifc;
        ntry.bndwdt = bw;
        ntry.affnty = aff;
        ntry.srlg = srl;
        ntry.mtu = mtu;
        ntry.segrou = adj;
        ntry.peer = adr.copyBytes();
        ntry.opeer = odr.copyBytes();
        ntry.perif = pif;
        rtrLsrpDataNeigh old = neighbor.find(ntry);
        if (old != null) {
            if (old.metric < met) {
                return;
            }
        }
        neighbor.put(ntry);
    }

    /**
     * add to spf
     *
     * @param spf calcer
     * @param ipv4 true if yes, false if not
     * @param dist distance
     */
    protected void put2spf(spfCalc<addrIPv4> spf, boolean ipv4, int dist) {
        for (int i = 0; i < neighbor.size(); i++) {
            rtrLsrpDataNeigh ntry = neighbor.get(i);
            String a;
            if (ntry.iface == null) {
                a = "" + ntry.peer;
            } else {
                a = ntry.iface;
            }
            spf.addConn(rtrId, ntry.rtrid, ntry.metric, true, ntry.stub, a);
        }
        spf.addAlgo(rtrId, flexalgo);
        spf.addIdent(rtrId, hostname);
        spf.addSegRouB(rtrId, segrouBeg);
        spf.addBierB(rtrId, bierBeg);
        int brh = tabLabelBier.num2bsl(bierLen);
        for (int i = 0; i < network.size(); i++) {
            tabRouteEntry<addrIP> rou = network.get(i).copyBytes(tabRoute.addType.notyet);
            rou.best.bierHdr = brh;
            rou.best.distance = dist;
            if (rou.prefix.network.isIPv4() == ipv4) {
                spf.addPref(rtrId, rou, false);
            } else {
                spf.addOpref(rtrId, rou, false);
            }
            spf.addSegRouI(rtrId, rou.best.segrouIdx);
            spf.addBierI(rtrId, rou.best.bierIdx);
            spf.addBierS(rtrId, rou.best.bierSub);
        }
    }

    /**
     * calculate password
     *
     * @param pwd password
     * @return value
     */
    protected String calcPass(String pwd) {
        List<String> lst = new ArrayList<String>();
        lst.add(pwd);
        lst.add(dump(dmpCsum));
        lst.add(pwd);
        return userUpgrade.calcTextHash(lst);
    }

    /**
     * show zonefile
     *
     * @param r4 ipv4 results
     * @param r6 ipv6 results
     * @param d domain
     * @param s separator
     * @param r replacers
     */
    protected void showZoneRev(List<String> r4, List<String> r6, String d, String s, List<String> r) {
        for (int i = 0; i < address.size(); i++) {
            rtrLsrpDataAddr cur = address.get(i);
            String a = cur.iface;
            for (int o = 0; o < r.size(); o += 2) {
                a = a.replaceAll(r.get(o), r.get(o + 1));
            }
            showZonesRev(r4, r6, cur.addr, s + a + "." + d);
        }
        for (int i = 0; i < network.size(); i++) {
            tabRouteEntry<addrIP> cur = network.get(i);
            showZonesRev(r4, r6, cur.prefix.network, "." + d);
        }
        showZonesRev(r4, r6, mgmtIp, "." + d);
        if (mgmtOp.isEmpty()) {
            return;
        }
        showZonesRev(r4, r6, mgmtOp, "." + d);
    }

    private void showZonesRev(List<String> r4, List<String> r6, addrIP adr, String a) {
        a = "rr|" + packDnsRec.generateReverse(adr) + "|ptr|" + hostname + a;
        if (adr.isIPv4()) {
            r4.add(a);
        } else {
            r6.add(a);
        }
    }

    private void showZoneFwd(List<String> r4, List<String> r6, addrIP adr, String a) {
        if (adr.isIPv4()) {
            r4.add("rr|" + hostname + a + "|" + packDnsRec.type2str(packDnsRec.typeA) + "|" + adr);
        } else {
            r6.add("rr|" + hostname + a + "|" + packDnsRec.type2str(packDnsRec.typeAAAA) + "|" + adr);
        }
    }

    /**
     * show zonefile
     *
     * @param r4 ipv4 results
     * @param r6 ipv6 results
     * @param d domain
     * @param s separator
     * @param r replacers
     */
    protected void showZoneFwd(List<String> r4, List<String> r6, String d, String s, List<String> r) {
        for (int i = 0; i < address.size(); i++) {
            rtrLsrpDataAddr cur = address.get(i);
            String a = cur.iface;
            for (int o = 0; o < r.size(); o += 2) {
                a = a.replaceAll(r.get(o), r.get(o + 1));
            }
            showZoneFwd(r4, r6, cur.addr, s + a + "." + d);
        }
        showZoneFwd(r4, r6, mgmtIp, "." + d);
        if (mgmtOp.isEmpty()) {
            return;
        }
        showZoneFwd(r4, r6, mgmtOp, "." + d);
    }

}

class rtrLsrpDataAddr implements Comparable<rtrLsrpDataAddr> {

    /**
     * address
     */
    public addrIP addr = new addrIP();

    /**
     * interface
     */
    public String iface;

    public int compareTo(rtrLsrpDataAddr o) {
        return addr.compareTo(o.addr);
    }

}

class rtrLsrpDataNeigh implements Comparable<rtrLsrpDataNeigh> {

    /**
     * router id
     */
    public addrIPv4 rtrid;

    /**
     * local interface
     */
    public String iface;

    /**
     * adjacency address
     */
    public addrIP peer;

    /**
     * other adjacency address
     */
    public addrIP opeer;

    /**
     * stub flag
     */
    public boolean stub;

    /**
     * metric
     */
    public int metric;

    /**
     * affinity
     */
    public int affnty;

    /**
     * srlg
     */
    public int srlg;

    /**
     * mtu
     */
    public int mtu;

    /**
     * bandwidth
     */
    public long bndwdt;

    /**
     * segment routing
     */
    public int segrou;

    /**
     * peer interface
     */
    public String perif;

    public int compareTo(rtrLsrpDataNeigh o) {
        int i = rtrid.compareTo(o.rtrid);
        if (i != 0) {
            return i;
        }
        return peer.compareTo(o.peer);
    }

}
