/*
 * 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 accord.local.cfk;

import java.util.Arrays;
import java.util.function.Supplier;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import accord.local.Node;
import accord.primitives.Routable.Domain;
import accord.primitives.Txn.Kind;
import accord.primitives.TxnId;
import accord.primitives.TxnId.Cardinality;
import accord.primitives.TxnId.FastPath;
import accord.primitives.TxnId.MediumPath;
import accord.utils.RandomSource;
import accord.utils.RandomTestRunner;

import static accord.local.cfk.Serialize.EMPTY_FLAG_HISTORY;
import static accord.local.cfk.Serialize.encodedFlagBits;
import static accord.local.cfk.Serialize.selectFlagHistory;
import static accord.local.cfk.Serialize.updateFlagHistory;
import static accord.primitives.Routable.Domain.Range;
import static accord.primitives.TxnId.Cardinality.Any;
import static accord.utils.Utils.shuffle;

// this class is mostly tested in C* integration due to having concrete serializers to work with
public class SerializeTest
{
    @Test
    public void testFlagHistory()
    {
        for (int c = 0 ; c < 1000 ; ++c)
            testOneFlagHistory();
    }

    private void testOneFlagHistory()
    {
        RandomTestRunner.test().check(rnd -> {
            int[] inputs = new int[rnd.nextInt(100, 1000)];

            Supplier<Kind> kinds = rnd.randomWeightedPicker(randomSubset(Kind.values(), rnd));
            Supplier<Domain> domains = rnd.randomWeightedPicker(randomSubset(Domain.values(), rnd));
            Supplier<FastPath> fastPaths = rnd.randomWeightedPicker(randomSubset(FastPath.values(), rnd));
            Supplier<MediumPath> mediumPaths = rnd.randomWeightedPicker(randomSubset(MediumPath.values(), rnd));
            Supplier<Cardinality> cardinalities = rnd.randomWeightedPicker(randomSubset(Cardinality.values(), rnd));
            float randomBitsChance = rnd.nextFloat() * 0.2f;

            for (int i = 0 ; i < inputs.length ; ++i)
            {
                if (rnd.decide(randomBitsChance))
                {
                    inputs[i] = rnd.nextInt(rnd.nextBoolean() ? 0x100 : 0x10000);
                }
                else
                {
                    Domain domain = domains.get();
                    Cardinality cardinality = domain == Range ? Any : cardinalities.get();
                    TxnId txnId = new TxnId(0, 0, fastPaths.get().bits | mediumPaths.get().bit(), kinds.get(), domain, cardinality, Node.Id.NONE);
                    inputs[i] = txnId.flags();
                }
            }

            int[] sortedHistoryFlags = new int[6];
            long flagHistory = Serialize.EMPTY_FLAG_HISTORY;
            int[] encoded = new int[inputs.length];
            int[] raw = new int[inputs.length];
            long[] histories = new long[inputs.length];
            for (int i = 0 ; i < inputs.length ; ++i)
            {
                int flags = inputs[i];
                encoded[i] = encodedFlagBits(flags, flagHistory);
                Assertions.assertTrue(encoded[i] < 8);
                raw[i] = encoded[i] == 0 ? (flags & 0xff) : encoded[i] == 1 ? (flags & 0xffff) : -1;
                if (encoded[i] == 0)
                {
                    flagHistory = updateFlagHistory(flags, encoded[i], flagHistory);
                    for (int j = 0 ; j < 6 ; ++j)
                        sortedHistoryFlags[j] = (int) ((flagHistory >>> (9 * j)) & 0xff);
                    Arrays.sort(sortedHistoryFlags);
                    for (int j = 1 ; j < 6 ; ++j)
                        Assertions.assertNotEquals(sortedHistoryFlags[j - 1], sortedHistoryFlags[j]);
                }
                histories[i] = flagHistory;
            }

            flagHistory = EMPTY_FLAG_HISTORY;
            for (int i = 0 ; i < inputs.length ; ++i)
            {
                int decodedFlags = encoded[i] < 2 ? raw[i] : selectFlagHistory(encoded[i], flagHistory);
                Assertions.assertEquals(inputs[i], decodedFlags);
                if (encoded[i] == 0)
                    flagHistory = updateFlagHistory(decodedFlags, encoded[i], flagHistory);
                Assertions.assertEquals(histories[i], flagHistory);
            }
        });
    }

    private static <T> T[] randomSubset(T[] input, RandomSource rnd)
    {
        shuffle(input, rnd);
        return Arrays.copyOf(input, 1 + rnd.nextInt(input.length - 1));
    }
}
