/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.sshd.agent.common;

import java.io.IOException;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.List;

import org.apache.sshd.agent.SshAgent;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.util.Buffer;

import static org.apache.sshd.agent.SshAgentConstants.SSH2_AGENTC_ADD_IDENTITY;
import static org.apache.sshd.agent.SshAgentConstants.SSH2_AGENTC_REMOVE_ALL_IDENTITIES;
import static org.apache.sshd.agent.SshAgentConstants.SSH2_AGENTC_REMOVE_IDENTITY;
import static org.apache.sshd.agent.SshAgentConstants.SSH2_AGENTC_REQUEST_IDENTITIES;
import static org.apache.sshd.agent.SshAgentConstants.SSH2_AGENTC_SIGN_REQUEST;
import static org.apache.sshd.agent.SshAgentConstants.SSH2_AGENT_FAILURE;
import static org.apache.sshd.agent.SshAgentConstants.SSH2_AGENT_IDENTITIES_ANSWER;
import static org.apache.sshd.agent.SshAgentConstants.SSH2_AGENT_SIGN_RESPONSE;
import static org.apache.sshd.agent.SshAgentConstants.SSH_AGENT_SUCCESS;

public abstract class AbstractAgentClient {

    private final Buffer buffer = new Buffer();
    private final SshAgent agent;

    public AbstractAgentClient(SshAgent agent) {
        this.agent = agent;
    }

    public synchronized void messageReceived(Buffer message) throws IOException {
        buffer.putBuffer(message);
        if (buffer.available() < 4) {
            return;
        }
        int rpos = buffer.rpos();
        int len = buffer.getInt();
        buffer.rpos(rpos);
        if (buffer.available() < len + 4) {
            return;
        }
        Buffer rep = new Buffer();
        rep.putInt(0);
        rep.rpos(rep.wpos());
        try {
            process(new Buffer(buffer.getBytes()), rep);
        } catch (Exception e) {
            rep.clear();
            rep.putInt(0);
            rep.rpos(rep.wpos());
            rep.putInt(1);
            rep.putByte(SSH2_AGENT_FAILURE);
        }
        reply(prepare(rep));
    }

    protected void process(Buffer req, Buffer rep) throws Exception {
        int cmd = req.getByte();
        switch (cmd) {
            case SSH2_AGENTC_REQUEST_IDENTITIES:
            {
                List<SshAgent.Pair<PublicKey,String>> keys = agent.getIdentities();
                rep.putByte(SSH2_AGENT_IDENTITIES_ANSWER);
                rep.putInt(keys.size());
                for (SshAgent.Pair<PublicKey,String> key : keys) {
                    rep.putPublicKey(key.getFirst());
                    rep.putString(key.getSecond());
                }
                break;
            }
            case SSH2_AGENTC_SIGN_REQUEST:
            {
                PublicKey key = req.getPublicKey();
                byte[] data = req.getBytes();
                int flags = req.getInt();
                Buffer sig = new Buffer();
                sig.putString(key instanceof RSAPublicKey ? KeyPairProvider.SSH_RSA : KeyPairProvider.SSH_DSS);
                sig.putBytes(agent.sign(key, data));
                rep.putByte(SSH2_AGENT_SIGN_RESPONSE);
                rep.putBytes(sig.array(), sig.rpos(), sig.available());
                break;
            }
            case SSH2_AGENTC_ADD_IDENTITY:
            {
                agent.addIdentity(req.getKeyPair(), req.getString());
                rep.putByte(SSH_AGENT_SUCCESS);
                break;
            }
            case SSH2_AGENTC_REMOVE_IDENTITY:
            {
                PublicKey key = req.getPublicKey();
                agent.removeIdentity(key);
                rep.putByte(SSH_AGENT_SUCCESS);
                break;
            }
            case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
            {
                agent.removeAllIdentities();
                rep.putByte(SSH_AGENT_SUCCESS);
                break;
            }
            default:
            {
                rep.putByte(SSH2_AGENT_FAILURE);
                break;
            }
        }
    }

    protected Buffer prepare(Buffer buf) {
        int len  = buf.available();
        int rpos = buf.rpos();
        int wpos = buf.wpos();
        buf.rpos(rpos - 4);
        buf.wpos(rpos - 4);
        buf.putInt(len);
        buf.wpos(wpos);
        return buf;
    }

    protected abstract void reply(Buffer buf) throws IOException;

}
