/**
 * Copyright 2013 Matt Corallo
 *
 * 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 com.google.bitcoin.core;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * A message sent by nodes when a message we sent was rejected (ie a transaction had too little fee/was invalid/etc)
 */
public class RejectMessage extends Message {
    private static final long serialVersionUID = -5246995579800334336L;

    private String message, reason;
    public static enum RejectCode {
        /** The message was not able to be parsed */
        MALFORMED((byte) 0x01),
        /** The message described an invalid object */
        INVALID((byte) 0x10),
        /** The message was obsolete or described an object which is obsolete (eg unsupported, old version, v1 block) */
        OBSOLETE((byte) 0x11),
        /**
         * The message was relayed multiple times or described an object which is in conflict with another.
         * This message can describe errors in protocol implementation or the presence of an attempt to DOUBLE SPEND.
         */
        DUPLICATE((byte) 0x12),
        /**
         * The message described an object was not standard and was thus not accepted.
         * The reference client has a concept of standard transaction forms, which describe scripts and encodings which
         * it is willing to relay further. Other transactions are neither relayed nor mined, though they are considered
         * valid if they appear in a block.
         */
        NONSTANDARD((byte) 0x40),
        /**
         * This refers to a specific form of NONSTANDARD transactions, which have an output smaller than some constant
         * defining them as dust (this is no longer used).
         */
        DUST((byte) 0x41),
        /** The messages described an object which did not have sufficient fee to be relayed further. */
        INSUFFICIENTFEE((byte) 0x42),
        /** The message described a block which was invalid according to hard-coded checkpoint blocks. */
        CHECKPOINT((byte) 0x43),
        OTHER((byte) 0xff);

        byte code;
        RejectCode(byte code) { this.code = code; }
        static RejectCode fromCode(byte code) {
            for (RejectCode rejectCode : RejectCode.values())
                if (rejectCode.code == code)
                    return rejectCode;
            return OTHER;
        }
    }
    private RejectCode code;
    private Sha256Hash messageHash;

    public RejectMessage(NetworkParameters params, byte[] bytes) throws ProtocolException {
        super(params, bytes, 0);
    }

    public RejectMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain, int length)
            throws ProtocolException {
        super(params, msg, 0, parseLazy, parseRetain, length);
    }

    @Override
    protected void parseLite() throws ProtocolException {
        message = readStr();
        code = RejectCode.fromCode(readBytes(1)[0]);
        reason = readStr();
        if (message.equals("block") || message.equals("tx"))
            messageHash = readHash();
        length = cursor - offset;
    }

    @Override
    public void parse() throws ProtocolException {
        if (length == UNKNOWN_LENGTH)
            parseLite();
    }

    @Override
    public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
        byte[] messageBytes = message.getBytes("UTF-8");
        stream.write(new VarInt(messageBytes.length).encode());
        stream.write(messageBytes);
        stream.write(code.code);
        byte[] reasonBytes = reason.getBytes("UTF-8");
        stream.write(new VarInt(reasonBytes.length).encode());
        stream.write(reasonBytes);
        if (message.equals("block") || message.equals("tx"))
            stream.write(messageHash.getBytes());
    }

    /**
     * Provides the type of message which was rejected by the peer.
     * Note that this is ENTIRELY UNTRUSTED and should be sanity-checked before it is printed or processed.
     */
    public String getRejectedMessage() {
        ensureParsed();
        return message;
    }

    /**
     * Provides the hash of the rejected object (if getRejectedMessage() is either "tx" or "block"), otherwise null.
     */
    public Sha256Hash getRejectedObjectHash() {
        ensureParsed();
        return messageHash;
    }

    /**
     * The reason code given for why the peer rejected the message.
     */
    public RejectCode getReasonCode() {
        return code;
    }

    /**
     * The reason message given for rejection.
     * Note that this is ENTIRELY UNTRUSTED and should be sanity-checked before it is printed or processed.
     */
    public String getReasonString() {
        return reason;
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof RejectMessage &&
                ((RejectMessage) o).message.equals(message) &&
                ((RejectMessage) o).code.equals(code) &&
                ((RejectMessage) o).reason.equals(reason) &&
                ((RejectMessage) o).messageHash.equals(messageHash);
    }
}

@@ -684,89 +684,8 @@
 eam;
-%0Aimport java.util.ArrayList;%0Aimport java.util.Collections;%0Aimport java.util.List;
 %0A%0A/*
@@ -5225,32 +5225,502 @@
  reason;%0A    %7D%0A%0A
+    @Override%0A    public String toString() %7B%0A        Sha256Hash hash = getRejectedObjectHash();%0A        if (hash != null)%0A            return String.format(%22Reject: %25s %25s for reason '%25s' (%25d)%22, getRejectedMessage(), getRejectedObjectHash(),%0A                getReasonString(), getReasonCode().code);%0A        else%0A            return String.format(%22Reject: %25s for reason '%25s' (%25d)%22, getRejectedMessage(),%0A                    getReasonString(), getReasonCode().code);%0A    %7D%0A%0A
     @Override%0A  
@@ -6055,11 +6055,277 @@
 );%0A    %7D
+%0A%0A    @Override%0A    public int hashCode() %7B%0A        int result = message.hashCode();%0A        result = 31 * result + reason.hashCode();%0A        result = 31 * result + code.hashCode();%0A        result = 31 * result + messageHash.hashCode();%0A        return result;%0A    %7D
 %0A%7D%0A

Add a toString for reject messages.