/*
 * Copyright 2012-2018 Chronicle Map Contributors
 *
 * Licensed 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 net.openhft.chronicle.map;

import net.openhft.chronicle.hash.serialization.DataAccess;
import net.openhft.chronicle.hash.serialization.impl.ExternalizableDataAccess;
import net.openhft.chronicle.hash.serialization.impl.ExternalizableReader;
import org.junit.Assert;
import org.junit.Test;

import java.io.*;
import java.nio.ByteBuffer;
import java.util.Arrays;

import static org.junit.Assert.assertEquals;

public class ConstantSizeBySampleTest {

    @Test
    public void testConstantKeys() {
        try (ChronicleMap<byte[], Long> map = ChronicleMapBuilder.of(byte[].class, Long.class)
                .constantKeySizeBySample(new byte[8])
                .entries(100)
                .create()) {

            byte[] zero = ByteBuffer.allocate(8).putLong(0L).array();
            map.put(zero, 0L);
            assertEquals(0L, (long) map.get(zero));

            byte[] one = ByteBuffer.allocate(8).putLong(1L).array();
            map.put(one, 1L);
            assertEquals(1L, (long) map.get(one));

            map.put(one, 0L);
            assertEquals(0L, (long) map.get(one));
        }
    }

    @Test
    public void testUnexpectedlyLongConstantByteArrayValues() throws IOException {
        try (ChronicleMap<Long, byte[]> map = ChronicleMapBuilder.of(Long.class, byte[].class)
                .constantValueSizeBySample(new byte[512 * 1024])
                .entries(100)
                .actualSegments(1)
                .create()) {
            byte[] value = new byte[512 * 1024];

            value[42] = 1;
            map.put(1L, value);
            Assert.assertTrue(Arrays.equals(map.get(1L), value));
        }
    }

    @Test
    public void testUnexpectedlyLongConstantExternalizableValues() throws IOException {
        try (ChronicleMap<Long, ExternalizableData> map =
                     ChronicleMapBuilder.of(Long.class, ExternalizableData.class)
                             .valueReaderAndDataAccess(new ExternalizableDataReader(),
                                     new ExternalizableDataDataAccess())
                             .constantValueSizeBySample(new ExternalizableData())
                             .entries(100)
                             .actualSegments(1)
                             .create()) {
            ExternalizableData value = new ExternalizableData();
            value.data[42] = 1;
            map.put(1L, value);
            Assert.assertEquals(map.get(1L), value);
        }
    }

    @Test
    public void testUnexpectedlyLongConstantSerializableValues() throws IOException {
        try (ChronicleMap<Long, SerializableData> map =
                     ChronicleMapBuilder.of(Long.class, SerializableData.class)
                             .constantValueSizeBySample(new SerializableData())
                             .entries(100)
                             .actualSegments(1)
                             .create()) {
            SerializableData value = new SerializableData();
            value.data[42] = 1;
            map.put(1L, value);
            Assert.assertEquals(map.get(1L), value);
        }
    }

    static final class ExternalizableData implements Externalizable {
        byte[] data = new byte[512 * 1024];

        public ExternalizableData() {
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof ExternalizableData))
                return false;
            return Arrays.equals(((ExternalizableData) obj).data, data);
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.write(data);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            in.read(data = new byte[512 * 1024]);
        }
    }

    static final class SerializableData implements Serializable {
        byte[] data = new byte[512 * 1024];

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof SerializableData))
                return false;
            return Arrays.equals(((SerializableData) obj).data, data);
        }
    }

    private static final class ExternalizableDataDataAccess
            extends ExternalizableDataAccess<ExternalizableData> implements Serializable {
        public ExternalizableDataDataAccess() {
            super(ExternalizableData.class);
        }

        @Override
        protected ExternalizableData createInstance() {
            return new ExternalizableData();
        }

        @Override
        public DataAccess<ExternalizableData> copy() {
            return new ExternalizableDataDataAccess();
        }
    }

    private static final class ExternalizableDataReader extends ExternalizableReader<ExternalizableData> {
        public ExternalizableDataReader() {
            super(ExternalizableData.class);
        }

        @Override
        protected ExternalizableData createInstance() {
            return new ExternalizableData();
        }
    }
}