/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.clients.fcp;

import java.io.IOException;
import java.util.ArrayList;

import junit.framework.TestCase;
import freenet.clients.fcp.FCPPluginConnectionImpl;
import freenet.clients.fcp.FCPPluginClientMessage;
import freenet.clients.fcp.FCPPluginServerMessage;
import freenet.node.FSParseException;
import freenet.support.SimpleFieldSet;

/**
 * Tests encoding and decoding of {@link FCPPluginMessage}s into the on-network format:
 * - Encodes them via {@link FCPPluginServerMessage}, which is the encoder for sending messages from
 *   a FCP server plugin to a client which is attached by network.<br>
 * - Decodes them via {@link FCPPluginClientMessage}, which is the decoder for decoding messages
 *   which we have received from a client which is attached by network.<br>
 * - Compares whether the decoded version matches the original {@link FCPPluginMessage}.<br><br>
 * 
 * Notice that this test is sort of an abuse: {@link FCPPluginClientMessage} is not meant to decode
 * {@link FCPPluginServerMessage}s as they are from the server, not from the client.<br>
 * It works because their format is very similar though.<br>
 * And it is the only way we have for testing encoding and decoding: There is no decoder for server
 * messages and no encoder for client messages because the current architecture of plugin FCP is to
 * always have the server plugin run locally in the same node as the FCP code and only allow clients
 * to be attached by network, not server plugins.<br>
 * See {@link FCPPluginConnectionImpl} for an overview of the architecture. 
 */
public final class FCPPluginMessageEncodeDecodeTest extends TestCase {

    /**
     * Creates different interesting types of {@link FCPPluginMessage}, whose actual encoding and
     * decoding is then tested using {@link #testEncodeDecode(FCPPluginMessage)}.
     */
    public final void testEncodeDecode() throws MessageInvalidException, IOException, FSParseException {
        ArrayList<FCPPluginMessage> messages = new ArrayList<FCPPluginMessage>();
        
        // Non-reply messages. Can either have a SimpleFieldSet, or a Bucket, or both. We don't use
        // Buckets because the parsing code for those is higher level code, so we only have to use
        // the constructor which creates a SimpleFieldSet and sets the Bucket to null.
        
        messages.add(FCPPluginMessage.construct());
        
        // Reply messages, with all possible allowed combinations of errorCode / errorMessage.
        
        messages.add(FCPPluginMessage.constructErrorReply(FCPPluginMessage.construct(),
                "TestErrorCode", "Test errorMessage"));
        messages.add(FCPPluginMessage.constructErrorReply(FCPPluginMessage.construct(),
                "TestErrorCode", null));
        messages.add(FCPPluginMessage.constructErrorReply(FCPPluginMessage.construct(),
                null, null));
        messages.add(FCPPluginMessage.constructSuccessReply(FCPPluginMessage.construct()));
        
        // Now the messages are created, but their SimpleFieldSet are empty. We test the
        // encode-decode cycle with various amount of data in the SFS.
        
        for(FCPPluginMessage message : messages) {
            // Non-reply messages cannot have an empty SFS and a null Bucket because then they have
            // no meaning at all.
            // (Reply messages can be empty because they still contain the boolean success)
            if(message.params != null && !message.isReplyMessage()) {
                message.params.putOverwrite("key1", "value1");
            }
            testEncodeDecode(message);
        }

        for(FCPPluginMessage message : messages) {
            if(message.params != null) {
                message.params.putOverwrite("key2", "value2");
            }
            testEncodeDecode(message);
        }
        
        for(FCPPluginMessage message : messages) {
            if(message.params != null) {
                message.params.putOverwrite("key3", "value3 with spaces");
            }
            testEncodeDecode(message);
        }
    }
    
    /**
     * @see FCPPluginMessageEncodeDecodeTest Explained at class-level JavaDoc of this class.
     */
    private final void testEncodeDecode(FCPPluginMessage message)
            throws MessageInvalidException, IOException, FSParseException {
        
        SimpleFieldSet encodedMessage
            = new FCPPluginServerMessage("testPlugin", message).getFieldSet();
        
        // The params have a different prefix in FCPPluginServerMessage and FCPPluginClientMessage.
        // So we have to rename them by removing the sub-SimpleFieldSet with the old prefix and
        // re-adding it with the new one.
        SimpleFieldSet params = encodedMessage.subset(FCPPluginServerMessage.PARAM_PREFIX);
        if(params != null) {
            encodedMessage.removeSubset(FCPPluginServerMessage.PARAM_PREFIX);
            encodedMessage.put(FCPPluginClientMessage.PARAM_PREFIX, params);
        }
        
        FCPPluginMessage decodedMessage
            = new FCPPluginClientMessage(encodedMessage).constructFCPPluginMessage();
 
        // Permissions are set by the FCPPluginConnectionImpl when the message is actually delivered
        assertEquals(null, decodedMessage.permissions);
        
        assertEquals(message.identifier, decodedMessage.identifier);
        
        // SimpleFieldSet offers no equals(). But its designed to be human readable, so we can
        // just encode them into Strings and compare those.
        if(message.params == null) {
            assertEquals(null, decodedMessage.params);
        } else {
            assertEquals(message.params.toOrderedString(), decodedMessage.params.toOrderedString());
        }
        
        if(message.data == null) {
            assertEquals(null, decodedMessage.data);
        } else {
            // Not implemented yet because the parsing of the data is higher level FCP code; i.e.
            // it is not implemented in FCPPluginClientMessage, but one of its parent classes.
            throw new UnsupportedOperationException("TODO: Implement");
        }
        
        assertEquals(message.success, decodedMessage.success);
        
        assertEquals(message.errorCode, decodedMessage.errorCode);
        
        assertEquals(message.errorMessage, decodedMessage.errorMessage);
    }

}
