/*
 * Copyright (c) 2022 MiLaboratories Inc.
 *
 * 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 com.milaboratory.primitivio;

import com.milaboratory.primitivio.annotations.Serializable;

import java.io.*;
import java.util.*;

public final class Util {
    private Util() {
    }

    static Class<?> findSerializableParent(Class<?> type, boolean direct, boolean withCustomSerializersOnly) {
        Class<?> root = null, tmp;

        Serializable a = type.getAnnotation(Serializable.class);
        if (a != null && (!withCustomSerializersOnly || a.custom().length > 0))
            if (direct)
                return type;
            else
                root = type;

        Class<?> superclass = type.getSuperclass();

        if (superclass != null) {
            tmp = findSerializableParent(superclass, direct, withCustomSerializersOnly);
            if (tmp != null) {
                if (root == null)
                    root = tmp;
                else
                    throw new RuntimeException("Custom serializers conflict: " + root + " and " + tmp + " through " + type);
            }
        }

        for (Class<?> cInterface : type.getInterfaces()) {
            tmp = findSerializableParent(cInterface, direct, withCustomSerializersOnly);
            if (tmp != null) {
                if (root == null || root == tmp)
                    root = tmp;
                else
                    throw new RuntimeException("Custom serializers conflict: " + root + " and " + tmp + " through " + type);
            }
        }

        return root;
    }

    public static void writeList(List<?> list, String fileName) throws IOException {
        try (BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(fileName))) {
            writeList(list, stream);
        }
    }

    public static void writeList(List<?> list, File file) throws IOException {
        try (BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file))) {
            writeList(list, stream);
        }
    }

    public static void writeList(List<?> list, OutputStream output) {
        writeList(list, new PrimitivO(output));
    }

    public static void writeList(List<?> list, PrimitivO output) {
        output.writeInt(list.size());
        for (Object o : list)
            output.writeObject(o);
    }

    public static <O> List<O> readList(Class<O> type, String fileName) throws IOException {
        try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream(fileName))) {
            return readList(type, stream);
        }
    }

    public static <O> List<O> readList(Class<O> type, File file) throws IOException {
        try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream(file))) {
            return readList(type, stream);
        }
    }

    public static <O> List<O> readList(Class<O> type, InputStream is) {
        return readList(type, new PrimitivI(is));
    }

    public static <O> List<O> readList(Class<O> type, PrimitivI input) {
        int size = input.readInt();
        List<O> list = new ArrayList<>(size);
        while ((--size) >= 0)
            list.add(input.readObject(type));
        return list;
    }

    public static <K, V> void writeMap(SortedMap<K, V> map, PrimitivO output) {
        output.writeVarInt(map.size());
        for (Map.Entry<K, V> entry : map.entrySet()) {
            output.writeObject(entry.getKey());
            output.writeObject(entry.getValue());
        }
    }

    public static <K extends Enum<K>, V> EnumMap<K, V> readEnumMap(PrimitivI input, Class<K> keyClass, Class<V> valueClass) {
        int size = input.readVarInt();
        EnumMap<K, V> map = new EnumMap<>(keyClass);
        for (; size > 0; --size) {
            K key = input.readObject(keyClass);
            V value = input.readObject(valueClass);
            map.put(key, value);
        }
        return map;
    }

    public static <K, V> Map<K, V> readMap(PrimitivI input, Class<K> keyClass, Class<V> valueClass) {
        int size = input.readVarInt();
        Map<K, V> map = new HashMap<>(size);
        for (; size > 0; --size) {
            K key = input.readObject(keyClass);
            V value = input.readObject(valueClass);
            map.put(key, value);
        }
        return map;
    }

    public static long zigZagEncodeLong(long value) {
        return (value << 1) ^ (value >> 63);
    }

    public static long zigZagDecodeLong(long value) {
        return (value >>> 1) ^ ((value << 63) >> 63);
    }

    public static int zigZagEncodeInt(int value) {
        return (value << 1) ^ (value >> 31);
    }

    public static int zigZagDecodeInt(int value) {
        return (value >>> 1) ^ ((value << 31) >> 31);
    }
}
