package cnetmap;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author Dominik Janura
 */
public class NetNode {

  /**
   * Vráti identifikátor rozhrania odpovedajúci vstupnému názvu rozhrania. Napr.
   * FastEthernet0/1 -> Fa0/1
   *
   * @param name vstupný názov rozhrania
   * @return identifikátor rozhrania
   */
  public static String toId(String name) {
    String prefix, suffix;
    prefix = name.substring(0, 2);
    if (prefix.equals("Se")) {
      suffix = name.substring(name.length() - 5);
    } else {
      suffix = name.substring(name.length() - 3);
    }

    return prefix.concat(suffix);
  }

  /**
   * Vráti názov rozhrania odpovedajúci vstupnému identifikátoru rozhrania.
   * Napr. Fa0/1 -> FastEthernet0/1
   *
   * @param id vstupný identifikátor rozhrania
   * @return názov rozhrania
   */
  public static String toName(String id) {
    String prefix, suffix;
    switch (id.substring(0, 2)) {
      case "Et":
        prefix = "Ethernet";
        suffix = id.substring(id.length() - 3);
        break;
      case "Fa":
        prefix = "FastEthernet";
        suffix = id.substring(id.length() - 3);
        break;
      case "Gi":
        prefix = "GigabitEthernet";
        suffix = id.substring(id.length() - 3);
        break;
      case "Se":
        prefix = "Serial";
        suffix = id.substring(id.length() - 5);
        break;
      case "Mo":
        prefix = "Modem";
        suffix = id.substring(id.length() - 3);
        break;
      default:
        prefix = "Invalid";
        suffix = "Interface";
        break;
    }

    return prefix.concat(suffix);
  }

  /**
   * Vráti zoznam aktívnych fyzických sieťových rozhraní cisco zariadenia
   * konfigurovaného vstupným súborom typu running configuration.
   *
   * @param inputFile vstupný textový súbor
   * @return zoznam nájdených sieťových rozhraní typu NetInterface
   * @throws IOException
   * @see BufferedReader, Inet4Address, InetAddress, List, Pattern, Matcher
   */
  public static List<NetInterface> parseRC(File inputFile) throws IOException {
    List<NetInterface> interfaceList = new ArrayList<>();
    String ifName = " ";
    Inet4Address host, mask, subnet;
    Pattern ipPattern = Pattern.compile(CNetMap.PATTERN_IP);
    Pattern interfacePattern = Pattern.compile(CNetMap.PATTERN_INTERFACE);
    Matcher ipMatch, interfaceMatch;

    try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {
      String line;
      while ((line = reader.readLine()) != null) {
        interfaceMatch = interfacePattern.matcher(line);
        ipMatch = ipPattern.matcher(line);
        if (interfaceMatch.find()) {
          ifName = line.split("\\s")[1];
        } else if (ipMatch.find()) {
          if (!ifName.contains(".")) {
            host = (Inet4Address) InetAddress.getByName(line.split("\\s")[3]);
            mask = (Inet4Address) InetAddress.getByName(line.split("\\s")[4]);
            subnet = getIPSubnet(host, mask);
            interfaceList.add(new NetInterface(toId(ifName), ifName, host, mask, subnet, null, null));
          }
        }
      }
    } catch (IOException ex) {
      Logger.getLogger(NetNode.class.getName()).log(Level.SEVERE, null, ex);
    }

    return interfaceList;
  }

  /**
   * Vráti podsieť, do ktorej patrí hosť so zadanou IP adresou a maskou
   * podsiete.
   *
   * @param host IPv4 adresa hosťa
   * @param mask IPv4 maska podsiete hosťa
   * @return IPv4 adresa podsiete, do ktorej hosť patrí
   * @throws UnknownHostException
   * @see Inet4Address, InetAddress
   */
  public static Inet4Address getIPSubnet(Inet4Address host, Inet4Address mask) throws UnknownHostException {
    Inet4Address subnet;
    byte[] byteHost = host.getAddress();
    byte[] byteMask = mask.getAddress();
    byte[] byteSubnet = new byte[4];

    for (int i = 0; i < 4; i++) {
      byteSubnet[i] = (byte) (byteHost[i] & byteMask[i]);
    }
    subnet = (Inet4Address) InetAddress.getByAddress(byteSubnet);
    return subnet;
  }

  private String HostName;
  private String Type;
  private List<NetInterface> InterfaceList;

  public NetNode(String hostName, String type) {
    super();
    HostName = hostName;
    Type = type;
    InterfaceList = new ArrayList<>();
  }

  /**
   * Vráti zoznam aktívnych fyzických sieťových rozhraní cisco zariadenia
   * konfigurovaného vstupným súborom typu mac-address-table.
   *
   * @param inputFile vstupný textový súbor
   * @throws IOException
   * @see BufferedReader, Inet4Address, InetAddress, List, Pattern, Matcher
   */
  public void parseMacAddressTable(File inputFile) throws IOException {
    List<String> alreadyParsed = new ArrayList<>();
    Pattern macPattern = Pattern.compile(CNetMap.PATTERN_MAC);
    Matcher macMatch;
    String id, gateway;

    try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {
      String line;
      boolean isNew = true;
      while ((line = reader.readLine()) != null) {
        macMatch = macPattern.matcher(line);
        if (macMatch.find()) {
          gateway = line.split("\\s")[7];
          id = line.split("\\s")[16];
          if (!alreadyParsed.contains(id)) {
            if (this.getInterfaceList().isEmpty()) {
              isNew = true;
            } else {
              for (NetInterface interfaces : this.getInterfaceList()) {
                if (interfaces.getId().equals(id)) {
                  isNew = false;
                  interfaces.setGateway(gateway);
                } else {
                  isNew = true;
                }
              }
            }
            if (isNew) {
              this.InterfaceList.add(new NetInterface(id, toName(id), null, null, null, null, gateway));
            }
            alreadyParsed.add(id);
          }
        }
      }
    } catch (IOException ex) {
      Logger.getLogger(NetNode.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

  /**
   * Aktívnym rozhraniam uzlu typu NetNode nastaví fyzické adresy získané zo
   * vstupného súboru typu interfaces.
   *
   * @param inputFile vstupný textový súbor
   * @throws IOException
   * @see BufferedReader, Inet4Address, InetAddress, List, Pattern, Matcher
   */
  public void parseInterfaces(File inputFile) throws IOException {
    Pattern connectedPattern = Pattern.compile(CNetMap.PATTERN_CONNECTED);
    Pattern macPattern = Pattern.compile(CNetMap.PATTERN_MAC);
    Matcher connectedMatch;
    Matcher macMatch;
    String[] tokens;
    String name, mac;

    try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {
      String line;
      while ((line = reader.readLine()) != null) {
        connectedMatch = connectedPattern.matcher(line);
        if (connectedMatch.find()) {
          tokens = line.split("\\s");
          name = tokens[0];
          line = reader.readLine();
          if (line != null) {
            macMatch = macPattern.matcher(line);
            if (macMatch.find()) {
              tokens = line.split("\\s");
              mac = tokens[tokens.length - 1].substring(0, 14);
              if (this.InterfaceList.isEmpty()) {
                this.InterfaceList.add(new NetInterface(toId(name), name, null, null, null, mac, null));
              } else {
                for (NetInterface interfaces : this.getInterfaceList()) {
                  if (interfaces.getName().equals(name)) {
                    interfaces.setHwAddress(mac);
                  }
                }
              }
            }
          }
        }
      }
    } catch (IOException ex) {
      Logger.getLogger(NetNode.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

  /**
   * Vytvorí spjenia medzi jednotlivými uzlami zo zoznamu uzlov
   * @param nodeList vstupný zoznam uzlov
   */
  public void setConnections(List<NetNode> nodeList) {
    if (this.getType().equals(CNetMap.TYPE_SWITCH)) {
      for (NetInterface localInterface : this.getInterfaceList()) {
        for (NetNode remoteNode : nodeList) {
          if (!remoteNode.equals(this)) {
            for (NetInterface remoteInterface : remoteNode.getInterfaceList()) {
              if (localInterface.getGateway().equals(remoteInterface.getHwAddress())) {
                if (null == localInterface.getConnectedNode()) {
                  localInterface.setConnectedNode(remoteNode);
                }
                if (null == localInterface.getConnectedInterface()) {
                  localInterface.setConnectedInterface(remoteInterface);
                }
                if (null == remoteInterface.getConnectedNode()) {
                  remoteInterface.setConnectedNode(this);
                }
                if (null == remoteInterface.getConnectedInterface()) {
                  remoteInterface.setConnectedInterface(localInterface);
                }
                break;
              }
            }
          }
        }
      }
    } else if (this.getType().equals(CNetMap.TYPE_ROUTER)) {
      for (NetInterface localInterface : this.getInterfaceList()) {
        for (NetNode remoteRouter : nodeList) {
          if (!remoteRouter.equals(this) && remoteRouter.getType().equals(CNetMap.TYPE_ROUTER)) {
            for (NetInterface remoteInterface : remoteRouter.getInterfaceList()) {
              if (localInterface.getIpNetwork().hashCode() == remoteInterface.getIpNetwork().hashCode()) {
                if (null == localInterface.getConnectedNode()) {
                  localInterface.setConnectedNode(remoteRouter);
                }
                if (null == localInterface.getConnectedInterface()) {
                  localInterface.setConnectedInterface(remoteInterface);
                }
                if (null == remoteInterface.getConnectedNode()) {
                  remoteInterface.setConnectedNode(this);
                }
                if (null == remoteInterface.getConnectedInterface()) {
                  remoteInterface.setConnectedInterface(localInterface);
                }
                break;
              }
            }
          }
        }
      }
    }
  }

  public String getHostName() {
    return this.HostName;
  }

  public void setHostName(String hostName) {
    this.HostName = hostName;
  }

  public String getType() {
    return this.Type;
  }

  public void setType(String type) {
    this.Type = type;
  }

  public List<NetInterface> getInterfaceList() {
    return this.InterfaceList;
  }

  public void setInterfaceList(List<NetInterface> interfaceList) {
    this.InterfaceList = interfaceList;
  }

  @Override
  public String toString() {
    return this.Type + " " + this.HostName;
  }

}