package net.fs.cap;

import net.fs.utils.MLog;
import org.pcap4j.core.NotOpenException;
import org.pcap4j.core.PcapHandle;
import org.pcap4j.core.PcapNativeException;
import org.pcap4j.packet.EthernetPacket.EthernetHeader;
import org.pcap4j.packet.IpV4Packet.IpV4Header;
import org.pcap4j.packet.Packet;
import org.pcap4j.packet.TcpPacket;
import org.pcap4j.packet.TcpPacket.TcpHeader;
import org.pcap4j.util.MacAddress;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;


public class TCPTun {

    static Random random = new Random();
    final Object syn_send_data = new Object();
    final Object syn_ident = new Object();
    public Inet4Address remoteAddress;
    public short remotePort;
    HashMap<Integer, TcpPacket> sendedTable_server = new HashMap<>();
    HashMap<Integer, TcpPacket> sendedTable_history_server = new HashMap<>();
    int clientSequence = Integer.MIN_VALUE;
    PcapHandle sendHandle;
    HashSet<Short> selfAckTable = new HashSet<>();
    HashMap<Integer, SendRecord> sendrecordTable = new HashMap<>();
    MacAddress dstMacaAddress;
    int sequenceNum = -1;
    Thread sendThread;
    boolean sended = false;
    Packet basePacket_server;
    short baseIdent = 100;
    IPacket dst_readed_packet, last_send_packet;
    int presend_server;
    ArrayList<IPacket> packetList = new ArrayList<>();
    HashMap<Integer, IPacket> packetTable_l = new HashMap<>();
    HashMap<Integer, IPacket> packetTable = new HashMap<>();
    ArrayList<IPacket> unacked_list = new ArrayList<>();
    Object syn_packetList = new Object();
    int max_client_ack = Integer.MIN_VALUE;
    int sendIndex = 0;
    long lasSetDelayTime = 0;
    long lastDelay = 300;
    Object syn_delay = new Object();
    Thread resendScanThread;
    boolean connectReady = false;
    boolean preDataReady = false;
    CapEnv capEnv;
    int remoteStartSequence;
    int remoteSequence;
    int remoteIdent;
    int remoteSequence_max;
    Inet4Address localAddress;
    short localPort;
    int localStartSequence = random.nextInt();
    int localSequence;
    int localIdent = random.nextInt(Short.MAX_VALUE - 100);
    long lastSendAckTime;
    long lastReceiveDataTime;
    long createTime = System.currentTimeMillis();
    String key;

    //客户端发起
    TCPTun(CapEnv capEnv,
           Inet4Address serverAddress, short serverPort,
           MacAddress srcAddress_mac, MacAddress dstAddrress_mac) {
        this.capEnv = capEnv;
        sendHandle = capEnv.sendHandle;
        this.remoteAddress = serverAddress;
        this.remotePort = serverPort;
        localAddress = capEnv.local_ipv4;
        localPort = (short) (random.nextInt(64 * 1024 - 1 - 10000) + 10000);
        Packet syncPacket = null;
        try {
            syncPacket = PacketUtils.createSync(srcAddress_mac, dstAddrress_mac, localAddress, localPort,
                    serverAddress, serverPort, localStartSequence, getIdent());
            try {
                sendHandle.sendPacket(syncPacket);
                localSequence = localStartSequence + 1;
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        MLog.println("发送第一次握手 " + " ident " + localIdent);
        MLog.println("" + syncPacket);

    }

    //服务端接收
    TCPTun(CapEnv capServerEnv,
           Inet4Address remoteAddress, short remotePort) {
        this.capEnv = capServerEnv;
        this.remoteAddress = remoteAddress;
        this.remotePort = remotePort;
        sendHandle = capEnv.sendHandle;
        localPort = capServerEnv.listenPort;
        localAddress = capEnv.local_ipv4;
    }

    public static byte[] getSimResponeHead() {

        String simRequest = ("HTTP/1.1 200 OK" + "\r\n") +
                "Server: Apache/2.2.15 (CentOS)" + "\r\n" +
                "Accept-Ranges: bytes" + "\r\n" +
                "Content-Length: " + (Math.abs(random.nextInt())) + "\r\n" +
                "Connection: Keep-Alive" + "\r\n" +
                "Content-Type: application/octet-stream" + "\r\n" +
                "\r\n";
        return simRequest.getBytes();
    }

    public static byte[] getSimRequestHead(int port) {
        StringBuilder sb = new StringBuilder();
        String domainName = getRandomString(5 + random.nextInt(10)) + ".com";
        sb.append("GET /").append(getRandomString(8 + random.nextInt(10))).append(".").append(getRandomString(2 +
                random.nextInt(5))).append(" HTTP/1.1").append("\r\n");
        sb.append("Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, " +
                "application/x-ms-xbap, */*" + "\r\n");
        sb.append("Accept-Language: zh-CN" + "\r\n");
        sb.append("Accept-Encoding: gzip, deflate" + "\r\n");
        sb.append("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:36.0) Gecko/20100101 Firefox/36.0" + "\r\n");
        sb.append("Host: ").append(domainName).append("\r\n");
        sb.append("Connection: Keep-Alive" + "\r\n");
        sb.append("\r\n");
        String simRequest = sb.toString();
        return simRequest.getBytes();
    }

    public static String getRandomString(int length) { //length表示生成字符串的长度
        String base = "abcdefghkmnopqrstuvwxyz";
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    void init_client(Inet4Address clientAddress, int clientPort,
                     Inet4Address serverAddress, int serverPort,
                     int client_start_sequence) {

    }

    void init_server(Inet4Address clientAddress, int clientPort,
                     Inet4Address serverAddress, int serverPort,
                     int client_start_sequence, int server_start_sequence) {

    }

    public void process_server(final Packet packet, EthernetHeader ethernetHeader, IpV4Header ipV4Header, TcpPacket
            tcpPacket, boolean client) {
        TcpHeader tcpHeader = tcpPacket.getHeader();

        if (!preDataReady) {
            if (!connectReady) {
                //第一次握手
                dstMacaAddress = ethernetHeader.getSrcAddr();
                if (tcpHeader.getSyn() && !tcpHeader.getAck()) {
                    remoteStartSequence = tcpHeader.getSequenceNumber();
                    remoteSequence = remoteStartSequence + 1;
                    remoteSequence_max = remoteSequence;
                    MLog.println("接收第一次握手 " + remoteAddress.getHostAddress() + ":" + remotePort + "->" + localAddress
                            .getHostAddress() + ":" + localPort + " ident " + ipV4Header.getIdentification());
                    MLog.println("" + packet);
                    Packet responePacket = PacketUtils.createSyncAck(
                            capEnv.local_mac,
                            capEnv.gateway_mac,
                            localAddress, localPort,
                            ipV4Header.getSrcAddr(), tcpHeader.getSrcPort().value(),
                            tcpHeader.getSequenceNumber() + 1, localStartSequence, (short) 0
                    );
                    try {
                        sendHandle.sendPacket(responePacket);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    localSequence = localStartSequence + 1;
                    MLog.println("发送第二次握手 " + capEnv.local_mac + "->" + capEnv.gateway_mac + " " + localAddress +
                            "->" + " ident " + 0);

                    MLog.println("" + responePacket);
                }

                if (!tcpHeader.getSyn() && tcpHeader.getAck()) {
                    if (tcpPacket.getPayload() == null) {
                        //第三次握手,客户端确认
                        if (tcpHeader.getAcknowledgmentNumber() == localSequence) {
                            MLog.println("接收第三次握手 " + " ident " + ipV4Header.getIdentification());
                            MLog.println(packet + "");
                            Thread t1 = new Thread() {
                                public void run() {
                                    //startSend(basePacket_server,syc_sequence_client+1);
                                }
                            };
                            //t1.start();
                            connectReady = true;
                        }
                    }
                    //MLog.println("客户端响应preview\n "+packet);
                    //MLog.println("request "+tcp.ack());
                    sendedTable_server.remove(tcpHeader.getAcknowledgmentNumber());
                    boolean selfAck = selfAckTable.contains(ipV4Header.getIdentification());
                    //MLog.println("客户端确认 "+"selfack "+selfAck+" id "+ipV4Header.getIdentification()+" ack_sequence
                    // "+tcpHeader.getAcknowledgmentNumberAsLong()+" "+sendedTable_server.size()+"ppppppp "+tcpHeader);
                }

            } else {
                if (tcpPacket.getPayload() != null) {
                    preDataReady = true;
                    onReceiveDataPacket(tcpPacket, tcpHeader, ipV4Header);
                    byte[] sim = getSimResponeHead();
                    sendData(sim);
                }
            }
        } else {
            if (tcpPacket.getPayload() != null) {
                onReceiveDataPacket(tcpPacket, tcpHeader, ipV4Header);
                TunData td = new TunData();
                td.tun = this;
                td.data = tcpPacket.getPayload().getRawData();
                capEnv.vDatagramSocket.onReceinveFromTun(td);
            }
        }
        if (tcpHeader.getRst()) {
            MLog.println("reset packet " + ipV4Header.getIdentification() + " " + tcpHeader.getSequenceNumber() + " "
                    + remoteAddress.getHostAddress() + ":" + remotePort + "->" + localAddress.getHostAddress() + ":" +
                    localPort + " " + " ident " + ipV4Header.getIdentification());
        }

    }

    public void process_client(CapEnv capEnv, final Packet packet, EthernetHeader ethernetHeader, IpV4Header
            ipV4Header, TcpPacket tcpPacket, boolean client) {

        TcpHeader tcpHeader = tcpPacket.getHeader();
        if (!preDataReady) {
            if (!connectReady) {
                if (tcpHeader.getAck() && tcpHeader.getSyn()) {
                    if (tcpHeader.getAcknowledgmentNumber() == (localStartSequence + 1)) {
                        MLog.println("接收第二次握手 " + " ident " + ipV4Header.getIdentification());
                        MLog.println("" + packet);
                        remoteStartSequence = tcpHeader.getSequenceNumber();
                        remoteSequence = remoteStartSequence + 1;
                        remoteSequence_max = remoteSequence;
                        Packet p3 = PacketUtils.createAck(capEnv.local_mac, capEnv.gateway_mac, capEnv.local_ipv4,
                                localPort, remoteAddress, remotePort, remoteSequence, localSequence, getIdent());
                        try {
                            sendHandle.sendPacket(p3);
                            MLog.println("发送第三次握手 " + " ident " + localIdent);
                            MLog.println("" + p3);
                            connectReady = true;

                            byte[] sim = getSimRequestHead(remotePort);
                            sendData(sim);
                            MLog.println("发送请求 " + " ident " + localIdent);
                        } catch (PcapNativeException | NotOpenException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } else {
                if (tcpPacket.getPayload() != null) {
                    preDataReady = true;
                    onReceiveDataPacket(tcpPacket, tcpHeader, ipV4Header);
                    MLog.println("接收响应 " + " ident " + ipV4Header.getIdentification());
                }
            }

        } else {
            if (tcpPacket.getPayload() != null) {
                //MLog.println("客户端正式接收数据 "+capClientEnv.vDatagramSocket);
                onReceiveDataPacket(tcpPacket, tcpHeader, ipV4Header);
                TunData td = new TunData();
                td.tun = this;
                td.data = tcpPacket.getPayload().getRawData();
                capEnv.vDatagramSocket.
                        onReceinveFromTun(td);
            }
        }
        if (tcpHeader.getRst()) {
            MLog.println("reset packet " + ipV4Header.getIdentification() + " " + tcpHeader.getSequenceNumber() + " "
                    + remoteAddress.getHostAddress() + ":" + remotePort + "->" + localAddress.getHostAddress() + ":" +
                    localPort);
        }

    }

    void onReceiveDataPacket(TcpPacket tcpPacket, TcpHeader tcpHeader, IpV4Header ipV4Header) {
        if (System.currentTimeMillis() - lastSendAckTime > 1000) {
            int rs = tcpHeader.getSequenceNumber() + tcpPacket.getPayload().getRawData().length;
            if (rs > remoteSequence_max) {
                remoteSequence_max = rs;
            }
            Packet ackPacket = PacketUtils.createAck(
                    capEnv.local_mac,
                    capEnv.gateway_mac,
                    localAddress, localPort,
                    ipV4Header.getSrcAddr(), tcpHeader.getSrcPort().value(),
                    remoteSequence_max, localSequence, getIdent());
            try {
                sendHandle.sendPacket(ackPacket);
            } catch (Exception e) {
                e.printStackTrace();

            }
            lastSendAckTime = System.currentTimeMillis();
            lastReceiveDataTime = System.currentTimeMillis();
        }
    }

    void sendData(byte[] data) {
        Packet dataPacket = PacketUtils.createDataPacket(capEnv.local_mac,
                capEnv.gateway_mac,
                localAddress, localPort,
                remoteAddress, remotePort,
                localSequence, remoteSequence_max, data, getIdent());
        synchronized (syn_send_data) {
            try {
                sendHandle.sendPacket(dataPacket);
                localSequence += data.length;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    short getIdent() {
        synchronized (syn_ident) {
            localIdent++;
            if (localIdent >= Short.MAX_VALUE) {
                localIdent = 0;
            }
        }
        return (short) localIdent;
    }

    public InetAddress getSourcrAddress() {
        return localAddress;
    }

    public int getSourcePort() {
        return localPort;
    }

    public void setSourcePort(short sourcePort) {
        this.localPort = sourcePort;
    }

    public boolean isConnectReady() {
        return connectReady;
    }

    public void setConnectReady(boolean connectReady) {
        this.connectReady = connectReady;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

}