// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.mojo.bindings;

import org.chromium.mojo.system.Handle;
import org.chromium.mojo.system.Watcher;

/**
 * Helper functions.
 */
public class BindingsHelper {
    /**
     * Alignment in bytes for mojo serialization.
     */
    public static final int ALIGNMENT = 8;

    /**
     * The size, in bytes, of a serialized handle. A handle is serialized as an int representing the
     * offset of the handle in the list of handles.
     */
    public static final int SERIALIZED_HANDLE_SIZE = 4;

    /**
     * The size, in bytes, of a serialized interface, which consists of a serialized handle (4
     * bytes) and a version number (4 bytes).
     */
    public static final int SERIALIZED_INTERFACE_SIZE = 8;

    /**
     * The size, in bytes, of a serialized pointer. A pointer is serializaed as an unsigned long
     * representing the offset from its position to the pointed elemnt.
     */
    public static final int POINTER_SIZE = 8;

    /**
     * The size, in bytes, of a serialized union.
     */
    public static final int UNION_SIZE = 16;

    /**
     * The header for a serialized map element.
     */
    public static final DataHeader MAP_STRUCT_HEADER = new DataHeader(24, 0);

    /**
     * The value used for the expected length of a non-fixed size array.
     */
    public static final int UNSPECIFIED_ARRAY_LENGTH = -1;

    /**
     * Passed as |arrayNullability| when neither the array nor its elements are nullable.
     */
    public static final int NOTHING_NULLABLE = 0;

    /**
     * "Array bit" of |arrayNullability| is set iff the array itself is nullable.
     */
    public static final int ARRAY_NULLABLE = (1 << 0);

    /**
     * "Element bit" of |arrayNullability| is set iff the array elements are nullable.
     */
    public static final int ELEMENT_NULLABLE = (1 << 1);

    public static boolean isArrayNullable(int arrayNullability) {
        return (arrayNullability & ARRAY_NULLABLE) > 0;
    }

    public static boolean isElementNullable(int arrayNullability) {
        return (arrayNullability & ELEMENT_NULLABLE) > 0;
    }

    /**
     * Align |size| on {@link BindingsHelper#ALIGNMENT}.
     */
    public static int align(int size) {
        return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1);
    }

    /**
     * Align |size| on {@link BindingsHelper#ALIGNMENT}.
     */
    public static long align(long size) {
        return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1);
    }

    /**
     * Compute the size in bytes of the given string encoded as utf8.
     */
    public static int utf8StringSizeInBytes(String s) {
        int res = 0;
        for (int i = 0; i < s.length(); ++i) {
            char c = s.charAt(i);
            int codepoint = c;
            if (isSurrogate(c)) {
                i++;
                char c2 = s.charAt(i);
                codepoint = Character.toCodePoint(c, c2);
            }
            res += 1;
            if (codepoint > 0x7f) {
                res += 1;
                if (codepoint > 0x7ff) {
                    res += 1;
                    if (codepoint > 0xffff) {
                        res += 1;
                        if (codepoint > 0x1fffff) {
                            res += 1;
                            if (codepoint > 0x3ffffff) {
                                res += 1;
                            }
                        }
                    }
                }
            }
        }
        return res;
    }

    /**
     * Returns |true| if and only if the two objects are equals, handling |null|.
     */
    public static boolean equals(Object o1, Object o2) {
        if (o1 == o2) {
            return true;
        }
        if (o1 == null) {
            return false;
        }
        return o1.equals(o2);
    }

    /**
     * Returns the hash code of the object, handling |null|.
     */
    public static int hashCode(Object o) {
        if (o == null) {
            return 0;
        }
        return o.hashCode();
    }

    /**
     * Returns the hash code of the value.
     */
    public static int hashCode(boolean o) {
        return o ? 1231 : 1237;
    }

    /**
     * Returns the hash code of the value.
     */
    public static int hashCode(long o) {
        return (int) (o ^ (o >>> 32));
    }

    /**
     * Returns the hash code of the value.
     */
    public static int hashCode(float o) {
        return Float.floatToIntBits(o);
    }

    /**
     * Returns the hash code of the value.
     */
    public static int hashCode(double o) {
        return hashCode(Double.doubleToLongBits(o));
    }

    /**
     * Returns the hash code of the value.
     */
    public static int hashCode(int o) {
        return o;
    }

    /**
     * Determines if the given {@code char} value is a Unicode <i>surrogate code unit</i>. See
     * {@link Character#isSurrogate}. Extracting here because the method only exists at API level
     * 19.
     */
    private static boolean isSurrogate(char c) {
        return c >= Character.MIN_SURROGATE && c < (Character.MAX_SURROGATE + 1);
    }

    /**
     * Returns an {@link AsyncWaiter} to use with the given handle, or |null| if none if available.
     */
    static Watcher getWatcherForHandle(Handle handle) {
        if (handle.getCore() != null) {
            return handle.getCore().getWatcher();
        } else {
            return null;
        }
    }
}
