/*
 * Copyright 2015 Christian Basler
 *
 * 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 ch.dissem.bitmessage.entity.payload;

import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Objects;

/**
 * A version 3 public key.
 */
public class V3Pubkey extends V2Pubkey {
    long nonceTrialsPerByte;
    long extraBytes;
    byte[] signature;

    protected V3Pubkey(long version, Builder builder) {
        super(version);
        stream = builder.streamNumber;
        behaviorBitfield = builder.behaviorBitfield;
        publicSigningKey = add0x04(builder.publicSigningKey);
        publicEncryptionKey = add0x04(builder.publicEncryptionKey);
        nonceTrialsPerByte = builder.nonceTrialsPerByte;
        extraBytes = builder.extraBytes;
        signature = builder.signature;
    }

    public static V3Pubkey read(InputStream is, long stream) throws IOException {
        return new V3Pubkey.Builder()
                .stream(stream)
                .behaviorBitfield(Decode.int32(is))
                .publicSigningKey(Decode.bytes(is, 64))
                .publicEncryptionKey(Decode.bytes(is, 64))
                .nonceTrialsPerByte(Decode.varInt(is))
                .extraBytes(Decode.varInt(is))
                .signature(Decode.varBytes(is))
                .build();
    }

    @Override
    public void write(OutputStream out) throws IOException {
        writeBytesToSign(out);
        Encode.varInt(signature.length, out);
        out.write(signature);
    }

    @Override
    public long getVersion() {
        return 3;
    }

    public long getNonceTrialsPerByte() {
        return nonceTrialsPerByte;
    }

    public long getExtraBytes() {
        return extraBytes;
    }

    public boolean isSigned() {
        return true;
    }

    public void writeBytesToSign(OutputStream out) throws IOException {
        super.write(out);
        Encode.varInt(nonceTrialsPerByte, out);
        Encode.varInt(extraBytes, out);
    }

    @Override
    public byte[] getSignature() {
        return signature;
    }

    @Override
    public void setSignature(byte[] signature) {
        this.signature = signature;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        V3Pubkey pubkey = (V3Pubkey) o;
        return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) &&
                Objects.equals(extraBytes, pubkey.extraBytes) &&
                stream == pubkey.stream &&
                behaviorBitfield == pubkey.behaviorBitfield &&
                Arrays.equals(publicSigningKey, pubkey.publicSigningKey) &&
                Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey);
    }

    @Override
    public int hashCode() {
        return Objects.hash(nonceTrialsPerByte, extraBytes);
    }

    public static class Builder {
        private long streamNumber;
        private int behaviorBitfield;
        private byte[] publicSigningKey;
        private byte[] publicEncryptionKey;
        private long nonceTrialsPerByte;
        private long extraBytes;
        private byte[] signature = new byte[0];

        public Builder() {
        }

        public Builder stream(long streamNumber) {
            this.streamNumber = streamNumber;
            return this;
        }

        public Builder behaviorBitfield(int behaviorBitfield) {
            this.behaviorBitfield = behaviorBitfield;
            return this;
        }

        public Builder publicSigningKey(byte[] publicSigningKey) {
            this.publicSigningKey = publicSigningKey;
            return this;
        }

        public Builder publicEncryptionKey(byte[] publicEncryptionKey) {
            this.publicEncryptionKey = publicEncryptionKey;
            return this;
        }

        public Builder nonceTrialsPerByte(long nonceTrialsPerByte) {
            this.nonceTrialsPerByte = nonceTrialsPerByte;
            return this;
        }

        public Builder extraBytes(long extraBytes) {
            this.extraBytes = extraBytes;
            return this;
        }

        public Builder signature(byte[] signature) {
            this.signature = signature;
            return this;
        }

        public V3Pubkey build() {
            return new V3Pubkey(3, this);
        }
    }
}
