/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.cassandra.service.accord.serializers;

import java.io.IOException;

import accord.api.RoutingKey;
import accord.messages.GetLatestDeps;
import accord.messages.GetLatestDeps.GetLatestDepsNack;
import accord.messages.GetLatestDeps.GetLatestDepsOk;
import accord.messages.GetLatestDeps.GetLatestDepsReply;
import accord.primitives.Ballot;
import accord.primitives.Deps;
import accord.primitives.Known;
import accord.primitives.LatestDeps;
import accord.primitives.Route;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;

import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.UnversionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.service.accord.serializers.CommandSerializers.ExecuteAtSerializer;

public class LatestDepsSerializers
{
    public static final UnversionedSerializer<LatestDeps> latestDeps = new UnversionedSerializer<>()
    {
        @Override
        public void serialize(LatestDeps t, DataOutputPlus out) throws IOException
        {
            out.writeUnsignedVInt32(t.size());
            serializeWithoutSize(t, out);
        }

        @Override
        public LatestDeps deserialize(DataInputPlus in) throws IOException
        {
            int size = in.readUnsignedVInt32();
            return deserializeWithSize(size, in);
        }

        @Override
        public long serializedSize(LatestDeps t)
        {
            return TypeSizes.sizeofUnsignedVInt(t.size())
                   + serializedSizeWithoutSize(t);
        }
    };

    public static final IVersionedSerializer<GetLatestDeps> request = new TxnRequestSerializer.WithUnsyncedSerializer<>()
    {
        @Override
        public void serializeBody(GetLatestDeps msg, DataOutputPlus out, Version version) throws IOException
        {
            CommandSerializers.ballot.serialize(msg.ballot, out);
            ExecuteAtSerializer.serialize(msg.executeAt, out);
        }

        @Override
        public GetLatestDeps deserializeBody(DataInputPlus in, Version version, TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch) throws IOException
        {
            Ballot ballot = CommandSerializers.ballot.deserialize(in);
            Timestamp executeAt = ExecuteAtSerializer.deserialize(in);
            return GetLatestDeps.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, ballot, executeAt);
        }

        @Override
        public long serializedBodySize(GetLatestDeps msg, Version version)
        {
            return CommandSerializers.ballot.serializedSize(msg.ballot)
                   + ExecuteAtSerializer.serializedSize(msg.executeAt);
        }
    };

    public static final UnversionedSerializer<GetLatestDepsReply> reply = new UnversionedSerializer<>()
    {
        static final long NACK = 0x80000000L;
        @Override
        public void serialize(GetLatestDepsReply reply, DataOutputPlus out) throws IOException
        {
            if (reply.isOk()) latestDeps.serialize(((GetLatestDepsOk)reply).deps, out);
            else out.writeUnsignedVInt(NACK);
        }

        @Override
        public GetLatestDepsReply deserialize(DataInputPlus in) throws IOException
        {
            long size = in.readUnsignedVInt();
            if (size == NACK)
                return GetLatestDepsNack.INSTANCE;
            return new GetLatestDepsOk(deserializeWithSize(Math.toIntExact(size), in));
        }

        @Override
        public long serializedSize(GetLatestDepsReply reply)
        {
            if (reply.isOk()) return latestDeps.serializedSize(((GetLatestDepsOk)reply).deps);
            else return TypeSizes.sizeofUnsignedVInt(NACK);
        }
    };

    private static void serializeWithoutSize(LatestDeps t, DataOutputPlus out) throws IOException
    {
        if (t.size() == 0)
            return;

        for (int i = 0 ; i < t.size() ; ++i)
        {
            RoutingKey start = t.startAt(i);
            KeySerializers.routingKey.serialize(start, out);
            LatestDeps.LatestEntry e = t.valueAt(i);
            if (e == null)
            {
                CommandSerializers.knownDeps.serialize(null, out);
            }
            else
            {
                CommandSerializers.knownDeps.serialize(e.known, out);
                CommandSerializers.ballot.serialize(e.ballot, out);
                DepsSerializers.nullableDeps.serialize(e.coordinatedDeps, out);
                DepsSerializers.nullableDeps.serialize(e.localDeps, out);
            }
        }
        KeySerializers.routingKey.serialize(t.startAt(t.size()), out);
    }

    public static LatestDeps deserializeWithSize(int size, DataInputPlus in) throws IOException
    {
        if (size == 0)
            return LatestDeps.EMPTY;

        RoutingKey[] starts = new RoutingKey[size + 1];
        LatestDeps.LatestEntry[] values = new LatestDeps.LatestEntry[size];
        for (int i = 0 ; i < size ; ++i)
        {
            starts[i] = KeySerializers.routingKey.deserialize(in);
            Known.KnownDeps knownDeps = CommandSerializers.knownDeps.deserialize(in);
            if (knownDeps == null)
                continue;

            Ballot ballot = CommandSerializers.ballot.deserialize(in);
            Deps coordinatedDeps = DepsSerializers.nullableDeps.deserialize(in);
            Deps localDeps = DepsSerializers.nullableDeps.deserialize(in);
            values[i] = new LatestDeps.LatestEntry(knownDeps, ballot, coordinatedDeps, localDeps);
        }
        starts[size] = KeySerializers.routingKey.deserialize(in);

        return LatestDeps.SerializerSupport.create(starts, values);
    }

    private static long serializedSizeWithoutSize(LatestDeps t)
    {
        long size = 0;
        if (t.size() == 0)
            return size;

        for (int i = 0 ; i < t.size() ; ++i)
        {
            RoutingKey start = t.startAt(i);
            size += KeySerializers.routingKey.serializedSize(start);
            LatestDeps.LatestEntry e = t.valueAt(i);
            if (e == null)
            {
                size += CommandSerializers.knownDeps.serializedSize(null);
            }
            else
            {
                size += CommandSerializers.knownDeps.serializedSize(e.known);
                size += CommandSerializers.ballot.serializedSize(e.ballot);
                size += DepsSerializers.nullableDeps.serializedSize(e.coordinatedDeps);
                size += DepsSerializers.nullableDeps.serializedSize(e.localDeps);
            }
        }
        size += KeySerializers.routingKey.serializedSize(t.startAt(t.size()));
        return size;
    }}
