package cz.moneta.test.harness.connectors;

import com.bytezone.dm3270.ConnectionListener;
import com.bytezone.dm3270.TerminalClient;
import com.bytezone.dm3270.commands.AIDCommand;
import com.bytezone.dm3270.display.ScreenDimensions;
import cz.moneta.test.harness.support.greenscreen.Key;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.glassfish.jersey.SslConfigurator;

import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class GreenScreenConnector implements Connector {

    public static final String CLIENT = "08548";
    private static final Logger LOG = LogManager.getLogger("GreenScreenConnector");
    private final Semaphore lock = new Semaphore(0);
    private final TerminalClient terminal;
    private final String url;
    private final String user;
    private final String password;

    public GreenScreenConnector(String url, String user, String password) {
        this.url = url;
        this.user = user;
        this.password = password;

        this.terminal = new TerminalClient(0, new ScreenDimensions(24, 80));
        terminal.setConnectionTimeoutMillis(20_000);
        terminal.setConnectionListener(buildConnectionListener());
        terminal.addScreenChangeListener(sw -> {
            LOG.debug(() -> "screen changed: \n" + toCp870(terminal.getScreenText()));
            lock.release();
        });
        terminal.setSocketFactory(buildSocketFactory());
    }

    private static ConnectionListener buildConnectionListener() {
        return new ConnectionListener() {
            @Override
            public void onConnection() {
                LOG.info("CONNECTED!!!");
            }

            @Override
            public void onException(Exception e) {
                LOG.error("Green screen connection failed", e);
            }

            @Override
            public void onConnectionClosed() {
                LOG.info("CONNECTION CLOSED!!!");
            }
        };
    }

    private static String toCp870(String cp1047) {
        try {
            return new String(cp1047.getBytes("CP1047"), "CP870");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("Error converting CP1047 to CP870");
        }
    }

    private static String toCp1047(String cp870) {
        try {
            return new String(cp870.getBytes("CP870"), "CP1047");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("Error converting CP870 to CP1047");
        }
    }

    public void connectAndLogin() {
        terminal.connect(url, 992);
        waitForScreenChange();
        terminal.setFieldTextByCoord(24, 1, "atrf");
        terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
        waitForScreenChange();
        terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
        waitForScreenChange();
        terminal.setFieldTextByCoord(6, 25, CLIENT);
        terminal.setFieldTextByCoord(8, 24, user);
        terminal.setFieldTextByCoord(10, 24, password);
        terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
        waitForScreenChange();
        waitForScreenChange();
        terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
        waitForScreenChange();
    }

    private void waitForScreenChange() {
        boolean success = false;
        try {
            success = lock.tryAcquire(5L, TimeUnit.SECONDS);
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            LOG.error(e);
        }
        if (!success) {
            throw new IllegalStateException("Failed to load screen within 5 second timeout");
        }
    }

    private SSLSocketFactory buildSocketFactory() {
        try (InputStream truststoreIs = this.getClass().getClassLoader()
                .getResourceAsStream("keystores/greenScreenTrustStore")) {

            KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustedStore.load(truststoreIs, "changeit".toCharArray());

            return SslConfigurator.newInstance()
                    .trustStore(trustedStore)
                    .keyStorePassword("changeit".toCharArray())
                    .createSSLContext()
                    .getSocketFactory();

        } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) {
            throw new IllegalStateException("Failed to initialize SSL context", e);
        }
    }

    public String getText(int row, int column, int length) {
        String screenText = toCp870(terminal.getScreenText());
        if (length == 0) {
            return screenText;
        } else {
            int beginIndex = (row - 1) * 81 + column - 1;
            return screenText.substring(beginIndex, beginIndex + length);
        }
    }

    public void pressKey(Key key) {
        switch (key) {
            case ENTER:
                terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
                waitForScreenChange();
                break;
            default:
                throw new IllegalArgumentException("Unknown key " + key.name());
        }
    }

    public void type(int row, int column, String text) {
        terminal.setFieldTextByCoord(row, column, toCp1047(text));
    }
}
