// TODO copyright header

package comments.user;

import static com.dyuproject.protostuffdb.EntityMetadata.ZERO_KEY;
import static com.dyuproject.protostuffdb.SerializedValueUtil.readByteArrayOffsetWithTypeAsSize;

import java.io.IOException;
import java.util.Arrays;

import com.dyuproject.protostuff.KeyBuilder;
import com.dyuproject.protostuff.Pipe;
import com.dyuproject.protostuff.RpcHeader;
import com.dyuproject.protostuff.RpcResponse;
import com.dyuproject.protostuffdb.DSRuntimeExceptions;
import com.dyuproject.protostuffdb.Datastore;
import com.dyuproject.protostuffdb.OpChain;
import com.dyuproject.protostuffdb.ProtostuffPipe;
import com.dyuproject.protostuffdb.RangeV;
import com.dyuproject.protostuffdb.ValueUtil;
import com.dyuproject.protostuffdb.WriteContext;

/**
 * Comment ops.
 */
public final class CommentOps
{
    private CommentOps() {}

    static byte[] append(byte[] data, int offset, int len, byte[] suffix)
    {
        byte[] buf = new byte[len+suffix.length];
        
        System.arraycopy(data, offset, buf, 0, len);
        
        System.arraycopy(suffix, 0, buf, len, suffix.length);
        
        return buf;
    }

    static Comment validateAndProvide(Comment param, long now, OpChain chain)
    {
        byte[] parentKey = param.parentKey;
        if (parentKey.length == 0 || Arrays.equals(parentKey, ZERO_KEY))
        {
            param.parentKey = ZERO_KEY;
            return param.provide(now, ZERO_KEY, 0);
        }
        
        final WriteContext context = chain.context;
        
        final byte[] parentValue = chain.vs().get(parentKey, Comment.EM, null);
        if (parentValue == null)
            throw DSRuntimeExceptions.operationFailure("Parent comment does not exist!");
        
        int offset = readByteArrayOffsetWithTypeAsSize(Comment.FN_KEY_CHAIN, parentValue, context),
                size = context.type,
                depth = size / 9;
        
        if (depth > 127)
            throw DSRuntimeExceptions.operationFailure("The nested replies are too deep and have exceeded the limit.");
        
        return param.provide(now, 
                append(parentValue, offset, size, parentKey), 
                depth);
    }
    
    static boolean create(Comment req, 
            Datastore store, RpcResponse res,
            Pipe.Schema<Comment.PList> resPipeSchema, 
            RpcHeader header) throws IOException
    {
        final byte[] lastSeenKey = req.key, 
                key = new byte[9];
        
        if (!store.chain(null, XCommentOps.OP_NEW, req, 0, res.context, key))
            return res.fail("Could not create.");
        
        req.key = key;
        
        if (req.parentKey != ZERO_KEY)
        {
            // user posted a reply
            final byte[] startKey;
            if (lastSeenKey == null)
            {
                startKey = ValueUtil.copy(req.keyChain, 0, req.keyChain.length - 8);
            }
            else
            {
                startKey = ValueUtil.copy(req.keyChain, 0, req.keyChain.length);
                // visit starting the entry after the last seen one
                lastSeenKey[lastSeenKey.length - 1] |= 0x02;
                System.arraycopy(lastSeenKey, 0, startKey, startKey.length - 9, 9);
            }
            
            KeyBuilder kb = res.context.kb()
                    .begin(Comment.IDX_POST_ID__KEY_CHAIN, Comment.EM)
                    .$append(req.postId)
                    .$append(startKey)
                    .$push()
                    .begin(Comment.IDX_POST_ID__KEY_CHAIN, Comment.EM)
                    .$append(req.postId)
                    .$push()
                    .begin(Comment.IDX_POST_ID__KEY_CHAIN, Comment.EM)
                    .$append(req.postId)
                    .$append8(0xFF)
                    .$push();
            
            final ProtostuffPipe pipe = res.context.pipe.init(
                    Comment.EM, Comment.getPipeSchema(), Comment.PList.FN_P, true);
            try
            {
                store.visitRange(false, -1, false, kb.copy(-2), res.context, 
                        RangeV.RES_PV, res, 
                        kb.buf(), kb.offset(-1), kb.len(-1), 
                        kb.buf(), kb.offset(), kb.len());
            }
            finally
            {
                pipe.clear();
            }
        }
        else if (lastSeenKey != null)
        {
            // visit starting the entry after the last seen one
            lastSeenKey[lastSeenKey.length - 1] |= 0x02;
            
            KeyBuilder kb = res.context.kb()
                    .begin(Comment.IDX_POST_ID__KEY_CHAIN, Comment.EM)
                    .$append(req.postId)
                    .$append(ZERO_KEY)
                    .$append(lastSeenKey)
                    .$push()
                    .begin(Comment.IDX_POST_ID__KEY_CHAIN, Comment.EM)
                    .$append(req.postId)
                    .$push()
                    .begin(Comment.IDX_POST_ID__KEY_CHAIN, Comment.EM)
                    .$append(req.postId)
                    .$append8(0xFF)
                    .$push();
            
            final ProtostuffPipe pipe = res.context.pipe.init(
                    Comment.EM, Comment.getPipeSchema(), Comment.PList.FN_P, true);
            try
            {
                store.visitRange(false, -1, false, kb.copy(-2), res.context, 
                        RangeV.RES_PV, res, 
                        kb.buf(), kb.offset(-1), kb.len(-1), 
                        kb.buf(), kb.offset(), kb.len());
            }
            finally
            {
                pipe.clear();
            }
        }
        else
        {
            CommentViews.visitByPostId(req.postId,
                    store, res.context,
                    RangeV.Store.ENTITY_PV,
                    RangeV.RES_PV, res);
        }

        return true;
    }
}

@@ -3530,32 +3530,34 @@
                 
+/*
 .begin(Comment.I
@@ -3583,32 +3583,32 @@
 IN, Comment.EM)%0A
-
                 
@@ -3652,32 +3652,34 @@
         .$push()
+*/
 %0A               
@@ -4074,32 +4074,38 @@
 lse, -1, false, 
+null/*
 kb.copy(-2), res
@@ -4091,32 +4091,34 @@
 ull/*kb.copy(-2)
+*/
 , res.context, %0A
@@ -4845,32 +4845,34 @@
                 
+/*
 .begin(Comment.I
@@ -4967,32 +4967,34 @@
         .$push()
+*/
 %0A               
@@ -5397,16 +5397,22 @@
  false, 
+null/*
 kb.copy(
@@ -5414,16 +5414,18 @@
 copy(-2)
+*/
 , res.co

simplify visitRange