/*
 * Copyright (c) 2002-2009 "Neo Technology,"
 *     Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 * 
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.neo4j.index.lucene;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;

import org.neo4j.kernel.impl.transaction.xaframework.LogBuffer;
import org.neo4j.kernel.impl.transaction.xaframework.XaCommand;

abstract class LuceneCommand extends XaCommand
{
    private final Long nodeId;
    private final String key;
    private final String value;
    
    private static final byte ADD_COMMAND = (byte) 1;
    private static final byte REMOVE_COMMAND = (byte) 2;
    
    LuceneCommand( Long nodeId, String key, String value )
    {
        this.nodeId = nodeId;
        this.key = key;
        this.value = value;
    }
    
    LuceneCommand( CommandData data )
    {
        this.nodeId = data.nodeId;
        this.key = data.key;
        this.value = data.value;
    }
    
    public Long getNodeId()
    {
        return nodeId;
    }
    
    public String getKey()
    {
        return key;
    }
    
    public String getValue()
    {
        return value;
    }
    
    @Override
    public void execute()
    {
        // TODO Auto-generated method stub
    }

    @Override
    public void writeToFile( LogBuffer buffer ) throws IOException
    {
        buffer.put( getCommandValue() );
        buffer.putLong( getNodeId() != null ? getNodeId() : -1L );
        char[] keyChars = getKey().toCharArray();
        buffer.putInt( keyChars.length );
        char[] valueChars = getValue() != null ?
            getValue().toCharArray() : null;
        buffer.putInt( valueChars != null ? valueChars.length : -1 );
        buffer.put( keyChars );
        if ( valueChars != null )
        {
            buffer.put( valueChars );
        }
    }
    
    protected abstract byte getCommandValue();
    
    static class AddCommand extends LuceneCommand
    {
        AddCommand( Long nodeId, String key, String value )
        {
            super( nodeId, key, value );
        }
        
        AddCommand( CommandData data )
        {
            super( data );
        }

        @Override
        protected byte getCommandValue()
        {
            return ADD_COMMAND;
        }
    }
    
    static class RemoveCommand extends LuceneCommand
    {
        RemoveCommand( Long nodeId, String key, String value )
        {
            super( nodeId, key, value );
        }
        
        RemoveCommand( CommandData data )
        {
            super( data );
        }

        @Override
        protected byte getCommandValue()
        {
            return REMOVE_COMMAND;
        }
    }

    private static class CommandData
    {
        private final Long nodeId;
        private final String key;
        private final String value;
        
        CommandData( Long nodeId, String key, String value )
        {
            this.nodeId = nodeId;
            this.key = key;
            this.value = value;
        }
    }
    
    static CommandData readCommandData( ReadableByteChannel channel, 
        ByteBuffer buffer ) throws IOException
    {
        buffer.clear(); buffer.limit( 16 );
        if ( channel.read( buffer ) != buffer.limit() )
        {
            return null;
        }
        buffer.flip();
        long nodeId = buffer.getLong();
        int keyCharLength = buffer.getInt();
        int valueCharLength = buffer.getInt();

        char[] keyChars = new char[keyCharLength];
        keyChars = readCharArray( channel, buffer, keyChars );
        if ( keyChars == null )
        {
            return null;
        }
        String key = new String( keyChars );

        String value = null;
        if ( valueCharLength != -1 )
        {
            char[] valueChars = new char[valueCharLength];
            valueChars = readCharArray( channel, buffer, valueChars );
            value = new String( valueChars );
        }
        return new CommandData( nodeId != -1 ? nodeId : null, key, value );
    }
    
    private static char[] readCharArray( ReadableByteChannel channel, 
        ByteBuffer buffer, char[] charArray ) throws IOException
    {
        buffer.clear();
        int charsLeft = charArray.length;
        int maxSize = buffer.capacity() / 2;
        int offset = 0; // offset in chars
        while ( charsLeft > 0 )
        {
            if ( charsLeft > maxSize )
            {
                buffer.limit( maxSize * 2 );
                charsLeft -= maxSize;
            }
            else
            {
                buffer.limit( charsLeft * 2 );
                charsLeft = 0;
            }
            if ( channel.read( buffer ) != buffer.limit() )
            {
                return null;
            }
            buffer.flip();
            int length = buffer.limit() / 2;
            buffer.asCharBuffer().get( charArray, offset, length ); 
            offset += length;
            buffer.clear();
        }
        return charArray;
    }
    
    static XaCommand readCommand( ReadableByteChannel channel, 
        ByteBuffer buffer )
        throws IOException
    {
        buffer.clear(); buffer.limit( 1 );
        if ( channel.read( buffer ) != buffer.limit() )
        {
            return null;
        }
        buffer.flip();
        byte commandType = buffer.get();
        CommandData data = readCommandData( channel, buffer );
        if ( data == null )
        {
            return null;
        }
        switch ( commandType )
        {
            case ADD_COMMAND: return new AddCommand( data ); 
            case REMOVE_COMMAND: return new RemoveCommand( data );
            default:
                throw new IOException( "Unknown command type[" + 
                    commandType + "]" );
        }
    }
}

@@ -6376,114 +6376,20 @@
 ult:
-%0A                throw new IOException( %22Unknown command type%5B%22 + %0A                    commandType + %22%5D%22 )
+ return null
 ;%0A  

Makes the reading of commands more flexible for errors. In this case: if the command type is unrecognized it will be ignored.