package cz.feec.vutbr.WebApp;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.support.CronSequenceGenerator;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.net.ssl.*;
import javax.xml.bind.DatatypeConverter;
import javax.xml.bind.SchemaOutputResolver;
import java.io.*;
import java.net.SocketTimeoutException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Controller
public class WebController {


    @Value("${tls.port}")
    private int PORT;  // CKP.40

    private static int PORT_STATIC;

    private static byte[] cardID      = new byte[]{(byte) 0x00,(byte) 0x00,(byte) 0x08,
            (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08};

    private static byte[] terminalID  = new byte[]{(byte) 0x00,(byte) 0x00,(byte) 0x07,
            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
    private static byte[] TLV_OK      = new byte[]{(byte) 0x00,(byte) 0x00,(byte) 0x01, (byte) 0x06};
    private static byte[] TLV_ERR      = new byte[]{(byte) 0x00,(byte) 0x00,(byte) 0x01, (byte) 0x15};
    private static byte[] TLV2_OK      = new byte[]{(byte) 0x01,(byte) 0x00,(byte) 0x01, (byte) 0x06};
    private static byte[] TLV2_ERR      = new byte[]{(byte) 0x01,(byte) 0x00,(byte) 0x01, (byte) 0x15};


    private static String container;
    private static String longHash;
    public static Boolean inCycles = true;
    private static DataOutputStream outCkp40;
    private static DataInputStream inCkp40;
    public static states state = states.None;
    public static Student active;
    private static int verCount = 0;
    public static String cr;

    @Value("${tls.port}")
    public void setPortStatic(int PORT){
        WebController.PORT_STATIC = PORT;
    }


    /*    @GetMapping("/personalization")
        public String getPersonalization(Model model) throws IOException {
            ArrayList arr;
            arr = Read_file.read("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/user_list.csv");
            //arr = Read_file.read("C:\\Users\\Bromy\\Documents\\user_list.txt");
            model.addAttribute("list",arr);
            return "personalization";
        }

        @GetMapping("/issue-attributes")
        public String getAttributes(Model model) throws IOException {
            ArrayList arr;
            arr = Read_file.read("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/user_list.csv");
            //arr = Read_file.read("C:\\Users\\Bromy\\Documents\\user_list.txt");
            model.addAttribute("list",arr);
            return "issue-attributes";
        }

        @GetMapping("/issue-attributes-ticket")
        public String getAttributes_ticket(Model model) throws IOException {
            ArrayList arr;
            arr = Read_file.read("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/user_list.csv");
            //arr = Read_file.read("C:\\Users\\Bromy\\Documents\\user_list.txt");
            model.addAttribute("list",arr);
            return "issue-attributes-ticket";
        }

        @GetMapping("/issue-attributes-eid")
        public String getAttributes_eid(Model model) throws IOException {
            ArrayList arr;
            arr = Read_file.read("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/user_list.csv");
            //arr = Read_file.read("C:\\Users\\Bromy\\Documents\\user_list.txt");
            model.addAttribute("list",arr);
            return "issue-attributes-eid";
        }
    */
    @GetMapping("/index")
    public String getIndex() {
        return "verifier";
    }

    @GetMapping("/home")
    public String getHome() {
        return "index";
    }
    @GetMapping("/login")
    public String getLogin() {
        return "login";
    }

/*    @GetMapping("/RA")
    public String getRA(Model model) throws IOException {
        ArrayList arr;
        arr = Read_file.read("/home/rkvac/rkvac_web/rkvac-protocol/build/data/RA/ra_rh.csv");
        //arr = Read_file.read("C:\\Users\\Bromy\\Documents\\test.txt");
        model.addAttribute("list",arr);

        return "RA";
    }
*/
    @GetMapping("/Verifier")
    public String getVerifier(Model model) {
        /*if (inCycles == true){
            return "verifier";
        }
        else{
            return "verifier-disabled";
        }         */
        switch (WebController.state){
            case AccessDenied:
                return "access_denied";
            case AccessGranted:
                return "access_allowed";
            case None:
                ArrayList atts = new ArrayList();
                if (!active.getAtt1().equals("")) atts.add(active.getAtt1());
                if (!active.getAtt2().equals("")) atts.add(active.getAtt2());
                if (!active.getAtt3().equals("")) atts.add(active.getAtt3());
                if (!active.getAtt4().equals("")) atts.add(active.getAtt4());
                if (!active.getAtt5().equals("")) atts.add(active.getAtt5());
                if (!active.getAtt6().equals("")) atts.add(active.getAtt6());
                if (!active.getAtt7().equals("")) atts.add(active.getAtt7());
                if (!active.getAtt8().equals("")) atts.add(active.getAtt8());
                if (!active.getAtt9().equals("")) atts.add(active.getAtt9());

                if (inCycles){
                    model.addAttribute("inCycles", "Status: Active");
                }else{
                    model.addAttribute("inCycles","Status: Disabled");
                }
                model.addAttribute("attributes",atts);
                return "verifier";
        }
        return "verifier";
    }

//    @GetMapping("/Verifier/verification")
//    public String getVerification(Model model) throws IOException, InterruptedException {
        //java.util.Date date = new Date();
        //System.out.println(date + " ######## NEW AUTHORIZATON REQUEST ########");

    /*
        //načtení atributů ze souboru active
        File f = new File("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Verifier/active");
        if(f.exists() && !f.isDirectory()) {
            int count = 0;
            ArrayList attributes = new ArrayList();
            ArrayList<String> attPositions = new ArrayList<String>();
            BufferedReader bufferedReader = new BufferedReader(new FileReader(f));
            String curLine;
            while ((curLine = bufferedReader.readLine()) != null){
                String[] line = curLine.split(";");
                for (var l:line
                ) {
                    System.out.print(l + " ");
                }
                attributes.add(line[1]); // přidání hodnoty atributu do seznamu
                count++;
                if (!line[1].equals("0")){
                    attPositions.add(String.valueOf(count)); //zaznamenání pozice neprázdného atributu
                    attPositions.add(",");
                }
            }
            attPositions.remove(attPositions.size()-1);
            StringBuilder sb = new StringBuilder();
            for (String item:attPositions
            ) {
                sb.append(item);
            }
            String attributePositions = sb.toString();
            bufferedReader.close();

            //spuštění ověřovací fáze

                RKVAC_handler rkvac = new RKVAC_handler();

                rkvac.addAttributes("-v");

                rkvac.addUser_input(attributePositions);

                this.container = rkvac.start();



        }
        else {
            model.addAttribute("container", "No active attributes set. Please set active attributes in Administration.");
            return "fail";

        }
*/
/*        while (!contactCKP40){
            Thread.sleep(10);
        }*/


//        return "verifier";
//    }

/*    @GetMapping("/Verifier/authorization")
    public String getAuthorization(Model model){



        System.out.println("TLS server -> CKP.40 "+bytesToHex(terminalID));
        dOut.write(terminalID);


        // read data TLV, see https://www.baeldung.com/java-inputstream-server-socket
        DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));

        // GET CKP40 ACK
        byte[] messageByte = readData(in, 4);
        System.out.println("TLS server <- CKP.40 "+bytesToHex(messageByte));

        // GET CKP40 RESULT
        byte[] messageByte2 = readData(in, 4);
        System.out.println("TLS server <- CKP.40 "+bytesToHex(messageByte2));

        // SEND ACK
        System.out.println("TLS server -> CKP.40 "+bytesToHex(TLV2_OK));
        dOut.write(TLV2_OK);

        while (this.authorizationResult.equals("")){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (this.authorizationResult.equals("ACCESS ALLOWED")){
            return "access_allowed";
        } else if (this.authorizationResult.equals("ACCESS DENIED")){
            return "access_denied";
        }else {
            return "redirect:/Verifier";
        }
        
    }
*/
    /*
    @GetMapping("/admin")
    public String getAdmin(Model model) throws IOException {
        ArrayList arr;
        arr = Read_file.read("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Verifier/ve_requests.log");
        //arr = Read_file.read("C:\\Users\\Bromy\\Documents\\user_list.txt");
        model.addAttribute("list",arr);
        return "admin";
    }
*/


/*    @GetMapping("/access-allowed")
    public String getAllowed() {
        return "access_allowed";
    }

    @GetMapping("/access-denied")
    public String getDenied() {
        return "access_denied";
    }

    @GetMapping("/identity-check")
    public String getICheck() {
        return "identity-check";
    }

    @GetMapping("/identity-check-ticket")
    public String getICheck_ticket() {
        return "identity-check-ticket";
    }

    @GetMapping("/identity-check-eid")
    public String getICheck_eid() {
        return "identity-check-eid";
    }

    @GetMapping("/success")
    public String getSuccess() {
        return "success";
    }

    @GetMapping("/fail")
    public String getFail() {
        return "fail";
    }

*/

/*
    @RequestMapping(value = "personalisation", method = {RequestMethod.GET, RequestMethod.POST})
    public String personalization(@RequestParam String firstname, @RequestParam String lastname, Model model) throws IOException, InterruptedException {

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-p");
        rkvac.addUser_input(firstname);
        rkvac.addUser_input(lastname);

        String container = rkvac.start();

        if (!container.contains("Card personalization successful")){
            model.addAttribute("container", container);
            return "fail";
        }

        model.addAttribute("url","/personalization");
        return "success";
    }

    @GetMapping("/RA-handlers")
    public String ra_handlers(Model model, Model list) throws IOException, InterruptedException {

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-r");

        String container = rkvac.start();

        if (!container.contains("Generate pseudonyms for current revocation handler")){
            model.addAttribute("container", container);
            return "fail";
        }
        model.addAttribute("url","/RA");
        return "success";
    }




    @RequestMapping(value = "issuing-attributes", method = {RequestMethod.GET, RequestMethod.POST})
    public String issuing_attributes(@RequestParam String names, @RequestParam String employer, @RequestParam String employee_id, @RequestParam String employee_position, Model model) throws IOException, InterruptedException {
        String write = "N";
        String database = "web_cache";

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-i");
        rkvac.addAttributes("-a");
        rkvac.addAttributes(database);

        rkvac.addUser_input("3");
        rkvac.addUser_input(names);
        rkvac.addUser_input(employee_id);
        rkvac.addUser_input(employer);
        rkvac.addUser_input(employee_position);
        rkvac.addUser_input(write);


        String container = rkvac.start();

        //Search for verify cardholder line + err handling
        //Scanner scanner = new Scanner(container);
        //String test = "err";
        //while (scanner.hasNextLine()) {
        //    String line = scanner.nextLine();
       //     if (line.contains("Verify cardholder")) {
        //        model.addAttribute("line", line);
        //        test = line;
        //    }
        //}
        //scanner.close();

        String line = "err";
        line = StringUtils.substringBetween(container, "Verify cardholder", "identity");
        model.addAttribute("line", line);

        if (line == "err") {
            model.addAttribute("container", container);
            return "fail";
        }

        //kopirovani databaze
        //Copy_File cf = new Copy_File("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/"+database,"/home/rkvac/rkvac_web/rkvac-protocol/build/data/Verifier/"+database);
        //cf.start();

        model.addAttribute("database",database);
        model.addAttribute("names",names);
        model.addAttribute("employee_id",employee_id);
        model.addAttribute("employer",employer);
        model.addAttribute("employee_position",employee_position);

        System.out.println("Deleting ..Issuer/" + database);
        File myObj = new File("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/"+database);
        myObj.delete();

        return "identity-check";
    }

    @RequestMapping(value = "issuing-attributes-ecard", method = {RequestMethod.GET, RequestMethod.POST})
    public String issuing_attributes_ecard(@RequestParam String names, @RequestParam String employer, @RequestParam String employee_id, @RequestParam String employee_position, @RequestParam String database, Model model) throws IOException, InterruptedException {
        String write = "Y";


        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-i");
        rkvac.addAttributes("-a");
        rkvac.addAttributes(database);

        rkvac.addUser_input("3");
        rkvac.addUser_input(names);
        rkvac.addUser_input(employee_id);
        rkvac.addUser_input(employer);
        rkvac.addUser_input(employee_position);
        rkvac.addUser_input(write);


        String container = rkvac.start();


        //kopirovani databaze
        //Copy_File cf = new Copy_File("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/"+database,"/home/rkvac/rkvac_web/rkvac-protocol/build/data/Verifier/"+database);
        //cf.start();


        if (!container.contains("Issuer part complete")){
            model.addAttribute("container", container);
            return "fail";
        }

        model.addAttribute("url","/issue-attributes");
        return "success";

    }

    @RequestMapping(value = "issuing-attributes-ticket", method = {RequestMethod.GET, RequestMethod.POST})
    public String issuing_attributes_ticket(@RequestParam String names, @RequestParam String cnumber, @RequestParam String ticket, Model model) throws IOException, InterruptedException {
        String write = "N";
        String database = "web_cache";

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-i");
        rkvac.addAttributes("-a");
        rkvac.addAttributes(database);

        rkvac.addUser_input("2");
        rkvac.addUser_input(names);
        rkvac.addUser_input(cnumber);
        rkvac.addUser_input(ticket);
        rkvac.addUser_input(write);


        String container = rkvac.start();


        String line = "err";
        line = StringUtils.substringBetween(container, "Verify cardholder", "identity");
        model.addAttribute("line", line);

        if (line == "err") {
            model.addAttribute("container", container);
            return "fail";
        }

        //kopirovani databaze
        //Copy_File cf = new Copy_File("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/"+database,"/home/rkvac/rkvac_web/rkvac-protocol/build/data/Verifier/"+database);
        //cf.start();

        model.addAttribute("database",database);
        model.addAttribute("names",names);
        model.addAttribute("ticket",ticket);
        model.addAttribute("cnumber",cnumber);


        System.out.println("Deleting ..Issuer/" + database);
        File myObj = new File("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/"+database);
        myObj.delete();

        return "identity-check-ticket";
    }

    @RequestMapping(value = "issuing-attributes-ticket-ver", method = {RequestMethod.GET, RequestMethod.POST})
    public String issuing_attributes_ticket_ver(@RequestParam String names, @RequestParam String cnumber, @RequestParam String ticket, @RequestParam String database, Model model) throws IOException, InterruptedException {
        String write = "Y";

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-i");
        rkvac.addAttributes("-a");
        rkvac.addAttributes(database);

        rkvac.addUser_input("2");
        rkvac.addUser_input(names);
        rkvac.addUser_input(cnumber);
        rkvac.addUser_input(ticket);
        rkvac.addUser_input(write);


        String container = rkvac.start();


        if (!container.contains("Issuer part complete")){
            model.addAttribute("container", container);
            return "fail";
        }

        model.addAttribute("url","/issue-attributes-ticket");
        return "success";
    }

    @RequestMapping(value = "issuing-attributes-eid", method = {RequestMethod.GET, RequestMethod.POST})
    public String issuing_attributes_eid(@RequestParam String names, @RequestParam String birthdate, @RequestParam String nationality, @RequestParam String residence, @RequestParam String sex, Model model) throws IOException, InterruptedException {
        String write = "N";
        String database = "web_cache";

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-i");
        rkvac.addAttributes("-a");
        rkvac.addAttributes(database);

        rkvac.addUser_input("1");
        rkvac.addUser_input(names);
        rkvac.addUser_input(birthdate);
        rkvac.addUser_input(nationality);
        rkvac.addUser_input(residence);
        rkvac.addUser_input(sex);
        rkvac.addUser_input(write);


        String container = rkvac.start();


        String line = "err";
        line = StringUtils.substringBetween(container, "Verify cardholder", "identity");
        model.addAttribute("line", line);

        if (line == "err") {
            model.addAttribute("container", container);
            return "fail";
        }

        //kopirovani databaze
        //Copy_File cf = new Copy_File("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/"+database,"/home/rkvac/rkvac_web/rkvac-protocol/build/data/Verifier/"+database);
        //cf.start();

        model.addAttribute("database",database);
        model.addAttribute("names",names);
        model.addAttribute("birthdate",birthdate);
        model.addAttribute("nationality",nationality);
        model.addAttribute("residence", residence);
        model.addAttribute("sex", sex);


        System.out.println("Deleting ..Issuer/" + database);
        File myObj = new File("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Issuer/"+database);
        myObj.delete();

        return "identity-check-eid";
    }

    @RequestMapping(value = "issuing-attributes-eid-ver", method = {RequestMethod.GET, RequestMethod.POST})
    public String issuing_attributes_eid_ver(@RequestParam String names, @RequestParam String birthdate, @RequestParam String nationality, @RequestParam String residence, @RequestParam String database, @RequestParam String sex, Model model) throws IOException, InterruptedException {
        String write = "Y";

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-i");
        rkvac.addAttributes("-a");
        rkvac.addAttributes(database);

        rkvac.addUser_input("1");
        rkvac.addUser_input(names);
        rkvac.addUser_input(birthdate);
        rkvac.addUser_input(nationality);
        rkvac.addUser_input(residence);
        rkvac.addUser_input(sex);
        rkvac.addUser_input(write);


        String container = rkvac.start();


        if (!container.contains("Issuer part complete")){
            model.addAttribute("container", container);
            return "fail";
        }

        model.addAttribute("url","/issue-attributes-eid");
        return "success";
    }



    @RequestMapping(value = "verify-attributes", method = {RequestMethod.GET, RequestMethod.POST})
    public String verify_attributes(@RequestParam String names, @RequestParam String employer, @RequestParam String employee_id, @RequestParam String employee_position, @RequestParam String number, @RequestParam String database) throws IOException, InterruptedException {

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-v");
        rkvac.addAttributes("-a");
        rkvac.addAttributes(database);

        //kdyz databaze existuje nacti atributy z ni
        File f = new File("/home/rkvac/rkvac_web/rkvac-protocol/build/data/Verifier/"+database);
        if(f.exists() && !f.isDirectory()) {
            rkvac.addUser_input(number);
        }
        else {
            rkvac.addUser_input("3");
            rkvac.addUser_input(names);
            rkvac.addUser_input(employee_id);
            rkvac.addUser_input(employer);
            rkvac.addUser_input(employee_position);
            rkvac.addUser_input(number);

        }
        String container = rkvac.start();

        return "done";
    }

    @RequestMapping(value = "verify-ecard", method = {RequestMethod.GET, RequestMethod.POST})
    public String verify_ecard(Model model) throws IOException, InterruptedException {
        String database = "ecard";

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-v");
        rkvac.addAttributes("-a");
        rkvac.addAttributes(database);

        rkvac.addUser_input("4");

        String container = rkvac.start();
        if (!container.contains("ACCESS ALLOWED")){
            model.addAttribute("container", container);
            return "access_denied";
        }
        return "access_allowed";
    }

    @RequestMapping(value = "verify-ticket", method = {RequestMethod.GET, RequestMethod.POST})
    public String verify_ticket(Model model) throws IOException, InterruptedException {
        String database = "ticket";

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-v");
        rkvac.addAttributes("-a");
        rkvac.addAttributes(database);

        rkvac.addUser_input("3");

        String container = rkvac.start();

        if (!container.contains("ACCESS ALLOWED")){
            model.addAttribute("container", container);
            return "access_denied";
        }
        return "access_allowed";
    }

    @RequestMapping(value = "verify-eid", method = {RequestMethod.GET, RequestMethod.POST})
    public String verify_eid(Model model) throws IOException, InterruptedException {
        String database = "eid";

        RKVAC_handler rkvac = new RKVAC_handler();

        rkvac.addAttributes("-v");
        rkvac.addAttributes("-a");
        rkvac.addAttributes(database);

        rkvac.addUser_input("3");

        String container = rkvac.start();
        if (!container.contains("ACCESS ALLOWED")){
            model.addAttribute("container", container);
            return "access_denied";
        }
        return "access_allowed";
    }*/

    @PostConstruct
    public static void run(){

        try {

            // Create TLS server instance, TLS 1.2
            SSLContext context = SSLContext.getInstance("TLSv1.2");
            System.out.println(new Date()+" Loading terminal certificates and secret keys from keystore tacrEta.jks");

            // connect java keystore with terminal certificate and secret key
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(new FileInputStream("/home/rkvac/rkvac_web/tacrEta.jks"), "butattributte2020".toCharArray());
            // FileInputStream inputStream = (FileInputStream) ClassLoader.getSystemClassLoader().getResourceAsStream("tacrEta.jks");
            // ks.load(inputStream, "butattributte2020".toCharArray());

            kmf.init(ks, "1234".toCharArray());

            KeyStore trustKeyStore = KeyStore.getInstance("JKS");
            trustKeyStore.load(new FileInputStream("/home/rkvac/rkvac_web/tacrEta.jks"), "butattributte2020".toCharArray());
            TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance("SUNX509");
            trustMgrFactory.init(trustKeyStore);

            context.init(kmf.getKeyManagers(), trustMgrFactory.getTrustManagers(), null);

            // run TLS server
            SSLServerSocketFactory serverSocketFactory = context.getServerSocketFactory();
            SSLServerSocket server = (SSLServerSocket)serverSocketFactory.createServerSocket(PORT_STATIC);
            System.out.println(new Date()+" TLS 1.2 server started, listening on "+server.getLocalSocketAddress());


            // waiting for client connection
            SSLSocket socket = (SSLSocket)server.accept();
            System.out.println(new Date()+" CKP.40 modul connected, connection from:"+socket.getInetAddress()+":"+socket.getPort());
            socket.setSoTimeout(90*1000);


            System.out.println(new Date() + " Establishing TLS connection with CKP.40 ...");
            DataOutputStream dOut = new DataOutputStream(socket.getOutputStream());

            // write data TLV, see https://www.baeldung.com/java-inputstream-server-socket

            // SEND TERMINAL ID (ACCESS DENY)
            System.out.println(new Date()+" TLS server -> CKP.40 "+bytesToHex(terminalID));
            dOut.write(terminalID);


            // read data TLV, see https://www.baeldung.com/java-inputstream-server-socket
            DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));

            // GET CKP40 ACK
            long startSecond = System.currentTimeMillis();
            byte[] messageByte = readData(in, 4);
            long second = System.currentTimeMillis() - startSecond;
            System.out.println(new Date()+" TLS server <- CKP.40 "+bytesToHex(messageByte)+" "+second+"ms");

            // GET CKP40 RESULT
            long startThird = System.currentTimeMillis();
            byte[] messageByte2 = readData(in, 4);
            long third = System.currentTimeMillis() - startThird;
            System.out.println(new Date()+" TLS server <- CKP.40 "+bytesToHex(messageByte2)+" "+third+"ms");

            // SEND ACK
            System.out.println(new Date()+" TLS server -> CKP.40 "+bytesToHex(TLV2_OK));
            dOut.write(TLV2_OK);

            WebController.outCkp40 = dOut;
            WebController.inCkp40 = in;

            System.out.println(new Date()+" TLS connection with CKP.40 established!");
            socket.setSoTimeout(10000);



        } catch (SocketTimeoutException ex){
            System.out.println("\n"+ new Date()+" Socket timeout, CKP.40 took too long to answer. Moving on.");
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        } catch (KeyManagementException ex) {
            ex.printStackTrace();
        } catch (CertificateException ex) {
            ex.printStackTrace();
        } catch (KeyStoreException ex) {
            ex.printStackTrace();
        } catch (UnrecoverableKeyException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            System.out.println(new Date()+" Soubor tacrEta.jks nenalezen.");
        } catch (NullPointerException ex) {
            System.out.println(new Date()+" Pokus o komunikaci s CKP.40 neúspěšný.");
            ex.printStackTrace();
        }

    }

    public static void verificationLoop() throws InterruptedException {

        /*
        //Init epoch change
        System.out.println(new Date()+" Initial epoch change.");
        offlineEpochChange();
        */
        CronSequenceGenerator generator = new CronSequenceGenerator(cr);
        Date nextRunDate= generator.next(new Date());
        System.out.println(new Date()+" Next epoch change scheduled to: "+nextRunDate);

        if (WebController.inCycles){
            System.out.println(new Date()+ " Verification loop is enabled.");
        } else{
            System.out.println(new Date()+ " Verification loop is disabled.");
        }

        do {
            //Verification loop control
            while (!WebController.inCycles){
                Thread.sleep(500);
            }

            try {
                WebController.state = states.None;

                /*
                //authentication count check
                WebController.verCount++;
                System.out.println("\n"+new Date()+" Iteration #"+WebController.verCount+" started.");
                if (WebController.verCount >= 100){
                    System.out.println("\n"+new Date()+" 100th authentication attempted recorded. Switching to a new epoch.");
                    offlineEpochChange();
                    WebController.verCount = 1;
                    System.out.println(new Date()+" Verification loop counter reset. Iteration #"+WebController.verCount+" started.");
                }

                 */
/*              FileInputStream fi = new FileInputStream(new File("/home/rkvac/rkvac_web/credentials.dat"));
                ObjectInputStream oi = new ObjectInputStream(fi);
                Student active = null;

                // najít aktivní pověření
                ArrayList<Student> list = (ArrayList<Student>) oi.readObject();
                for (Student cred: list
                ) {
                    if (cred.getStatus().equals("ACTIVE")){
                        active = cred;
                        break;
                    }
                }
                oi.close();
                fi.close();

 */

                //inicializace revokační části
                RKVAC_handler rkvac = new RKVAC_handler();
                rkvac.addAttributes("-v");
                rkvac.addUser_input(WebController.active.getActivePositions());
                WebController.container = rkvac.start();

                //nalezení hashů
                ArrayList<String> matches = new ArrayList<String>();
                Pattern r = Pattern.compile("\\[[0-9]\\]\\s([a-zA-Z0-9]{64})");
                Matcher m = r.matcher(WebController.container);
                while (m.find()){
                    matches.add(m.group(1));
                }

                String joined = String.join("", matches);

                //pokud byly nalezeny hashe, přejde se ke kontaktování CKP.40
                if (!matches.isEmpty()){

                    // Pokud je ACCESS allowed, pošlou se hashe
                    if (container.contains("ACCESS ALLOWED")){

                        WebController.longHash = joined;

                        String hashcode = JavaHash.getCryptoHash(WebController.longHash,"SHA-256").toUpperCase(Locale.ROOT);
                        String cuttedHash = hashcode.substring(0,14);

                        System.out.println("\n"+new Date()+ " Starting communication with CKP.40.");
                        System.out.println(new Date()+" TLS server -> CKP.40 "+"000007"+ cuttedHash);
                        outCkp40.write(hexStringToByteArray("000007"+cuttedHash));

                        // GET CKP40 ACK
                        long startSecond = System.currentTimeMillis();
                        byte[] messageByteAuthorization = readData(inCkp40, 4);
                        long second = System.currentTimeMillis() - startSecond;
                        System.out.println(new Date()+" TLS server <- CKP.40 "+bytesToHex(messageByteAuthorization)+ " "+second+"ms");

                        // GET CKP40 RESULT
                        long startThird = System.currentTimeMillis();
                        byte[] messageByte2Authorization = readData(inCkp40, 4);
                        long third = System.currentTimeMillis() - startThird;
                        System.out.println(new Date()+" TLS server <- CKP.40 "+bytesToHex(messageByte2Authorization)+" "+third+"ms");

                        // SEND ACK
                        System.out.println(new Date()+" TLS server -> CKP.40 "+bytesToHex(TLV2_OK));
                        outCkp40.write(TLV2_OK);


                        byte[] accessGranted      = new byte[]{(byte) 0x01,(byte) 0x01,(byte) 0x00, (byte) 0x06};
                        // SHOW AUTHORIZATION RESULT
                        if (Arrays.equals(messageByte2Authorization,accessGranted)){
                            System.out.println("\n" +new Date() +" CKP.40 ACCESS ALLOWED");
                            System.out.println("\n"+new Date()+" Waiting 3 seconds");

                            WebController.state = states.AccessGranted;
                            Thread.sleep(3000);

                        }
                        else{
                            System.out.println("\n" +new Date() +" CKP.40 ACCESS DENIED");
                            System.out.println("\n"+new Date()+" Waiting 3 seconds");

                            WebController.state = states.AccessDenied;
                            Thread.sleep(3000);

                        }
                    } else{

                        System.out.println(new Date() + " Sending Terminal ID");
                        System.out.println("TLS server -> CKP.40 "+bytesToHex(terminalID));
                        outCkp40.write(terminalID);


                        // GET CKP40 ACK
                        long startSecond = System.currentTimeMillis();
                        byte[] messageByte = readData(inCkp40, 4);
                        long second = System.currentTimeMillis() - startSecond;
                        System.out.println(new Date()+" TLS server <- CKP.40 "+bytesToHex(messageByte)+ " "+second+"ms");

                        // GET CKP40 RESULT
                        long startThird = System.currentTimeMillis();;
                        byte[] messageByte2 = readData(inCkp40, 4);
                        long third = System.currentTimeMillis() - startThird;
                        System.out.println(new Date()+" TLS server <- CKP.40 "+bytesToHex(messageByte2)+ " "+third+"ms");

                        // SEND ACK
                        System.out.println(new Date()+" TLS server -> CKP.40 "+bytesToHex(TLV2_OK));
                        outCkp40.write(TLV2_OK);

                        WebController.state = states.AccessDenied;
                        System.out.println("\n"+new Date()+" Waiting 3 seconds");

                        Thread.sleep(3000);
                    }


                }else {
                    /*System.out.println(new Date()+" No Hashes Found.");
                    System.out.println("\n"+new Date()+" Waiting 3 seconds");

                    WebController.state = states.None;
                    Thread.sleep(3000);
                    */
                    // model.addAttribute("container", "No Hashes Found. Please Check the Card-reader Connection.");
                    // return "fail";
                }


            } catch (NullPointerException e){
                Thread.sleep(2000);
                System.out.println("\n"+new Date()+" Connection to CKP.40 NOT established. Trying to establish a new connection.");
                run();
            }
            catch (SocketTimeoutException ex){
                System.out.println("\n"+ new Date()+" Socket timeout, CKP.40 took too long to answer. Moving on.");
            }
            catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("\n"+new Date()+" Waiting 3 seconds");
                Thread.sleep(3000);
            }
            catch (IOException e) {
                e.printStackTrace();
                System.out.println("\n"+new Date()+" Waiting 3 seconds");
                Thread.sleep(3000);
            }
        }while (true);
    }



    public static String bytesToHex(byte[] bytes) {

        char[] hexArray = "0123456789ABCDEF".toCharArray();
        char[] hexChars = new char[bytes.length * 2];
        for ( int j = 0; j < bytes.length; j++ ) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    static byte[] readData(DataInputStream in, int size) throws IOException{

        byte[] messageByte = new byte[4];
        boolean end = false;

        int totalBytesRead = 0;
        while(!end) {
            int currentBytesRead = in.read(messageByte);
            totalBytesRead = currentBytesRead + totalBytesRead;

            if(totalBytesRead>=4) {
                end = true;
            }
        }

        return messageByte;
    }

    public static byte[] toByteArray(String s) {
        return DatatypeConverter.parseHexBinary(s);
    }
    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }

    public enum states{
        ContactingCKP40,
        AccessGranted,
        AccessDenied,
        None,
    }




}