/*
 * Copyright (C)2009 - SSHJ Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.schmizz.sshj.transport;

import net.schmizz.sshj.Config;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.Message;
import net.schmizz.sshj.common.SSHPacket;

import java.util.Arrays;
import java.util.List;

class Proposal {

    private final List<String> kex;
    private final List<String> sig;
    private final List<String> c2sCipher;
    private final List<String> s2cCipher;
    private final List<String> c2sMAC;
    private final List<String> s2cMAC;
    private final List<String> c2sComp;
    private final List<String> s2cComp;
    private final SSHPacket packet;

    public Proposal(Config config) {
        kex = Factory.Named.Util.getNames(config.getKeyExchangeFactories());
        sig = Factory.Named.Util.getNames(config.getSignatureFactories());
        c2sCipher = s2cCipher = Factory.Named.Util.getNames(config.getCipherFactories());
        c2sMAC = s2cMAC = Factory.Named.Util.getNames(config.getMACFactories());
        c2sComp = s2cComp = Factory.Named.Util.getNames(config.getCompressionFactories());

        packet = new SSHPacket(Message.KEXINIT);

        // Put cookie
        packet.ensureCapacity(16);
        config.getRandomFactory().create().fill(packet.array(), packet.wpos(), 16);
        packet.wpos(packet.wpos() + 16);

        // Put algorithm lists
        packet.putString(toCommaString(kex));
        packet.putString(toCommaString(sig));
        packet.putString(toCommaString(c2sCipher));
        packet.putString(toCommaString(s2cCipher));
        packet.putString(toCommaString(c2sMAC));
        packet.putString(toCommaString(s2cMAC));
        packet.putString(toCommaString(c2sComp));
        packet.putString(toCommaString(s2cComp));
        packet.putString("");
        packet.putString("");

        packet.putBoolean(false); // Optimistic next packet does not follow
        packet.putUInt32(0); // "Reserved" for future by spec
    }

    public Proposal(SSHPacket packet)
            throws TransportException {
        this.packet = packet;
        final int savedPos = packet.rpos();
        packet.rpos(packet.rpos() + 17); // Skip message ID & cookie
        try {
            kex = fromCommaString(packet.readString());
            sig = fromCommaString(packet.readString());
            c2sCipher = fromCommaString(packet.readString());
            s2cCipher = fromCommaString(packet.readString());
            c2sMAC = fromCommaString(packet.readString());
            s2cMAC = fromCommaString(packet.readString());
            c2sComp = fromCommaString(packet.readString());
            s2cComp = fromCommaString(packet.readString());
        } catch (Buffer.BufferException be) {
            throw new TransportException(be);
        }
        packet.rpos(savedPos);
    }

    public List<String> getKeyExchangeAlgorithms() {
        return kex;
    }

    public List<String> getSignatureAlgorithms() {
        return sig;
    }

    public List<String> getClient2ServerCipherAlgorithms() {
        return c2sCipher;
    }

    public List<String> getServer2ClientCipherAlgorithms() {
        return s2cCipher;
    }

    public List<String> getClient2ServerMACAlgorithms() {
        return c2sMAC;
    }

    public List<String> getServer2ClientMACAlgorithms() {
        return s2cMAC;
    }

    public List<String> getClient2ServerCompressionAlgorithms() {
        return c2sComp;
    }

    public List<String> getServer2ClientCompressionAlgorithms() {
        return s2cComp;
    }

    public SSHPacket getPacket() {
        return new SSHPacket(packet);
    }

    public NegotiatedAlgorithms negotiate(Proposal other)
            throws TransportException {
        return new NegotiatedAlgorithms(
                firstMatch(this.getKeyExchangeAlgorithms(), other.getKeyExchangeAlgorithms()),
                firstMatch(this.getSignatureAlgorithms(), other.getSignatureAlgorithms()),
                firstMatch(this.getClient2ServerCipherAlgorithms(), other.getClient2ServerCipherAlgorithms()),
                firstMatch(this.getServer2ClientCipherAlgorithms(), other.getServer2ClientCipherAlgorithms()),
                firstMatch(this.getClient2ServerMACAlgorithms(), other.getClient2ServerMACAlgorithms()),
                firstMatch(this.getServer2ClientMACAlgorithms(), other.getServer2ClientMACAlgorithms()),
                firstMatch(this.getClient2ServerCompressionAlgorithms(), other.getClient2ServerCompressionAlgorithms()),
                firstMatch(this.getServer2ClientCompressionAlgorithms(), other.getServer2ClientCompressionAlgorithms())
        );
    }

    private static String firstMatch(List<String> a, List<String> b)
            throws TransportException {
        for (String aa : a)
            for (String bb : b)
                if (aa.equals(bb))
                    return aa;
        throw new TransportException("Unable to reach a settlement: " + a + " and " + b);
    }

    private static String toCommaString(List<String> sl) {
        StringBuilder sb = new StringBuilder();
        int i = 0;
        for (String s : sl) {
            if (i++ != 0)
                sb.append(",");
            sb.append(s);
        }
        return sb.toString();
    }

    private static List<String> fromCommaString(String s) {
        return Arrays.asList(s.split(","));
    }

}
