/*
 * Copyright (c) 2019 the Eclipse Milo Authors
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.milo.opcua.stack.core.types.builtin;

import java.io.Serializable;
import java.util.Arrays;

import com.google.common.base.MoreObjects;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UByte;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class ByteString implements Serializable {

    private static final long serialVersionUID = 451472015617419665L;

    public static final ByteString NULL_VALUE = new ByteString(null);

    private final byte[] bytes;

    public ByteString(@Nullable byte[] bytes) {
        this.bytes = bytes;
    }

    public int length() {
        return bytes != null ? bytes.length : 0;
    }

    public boolean isNull() {
        return bytes == null;
    }

    public boolean isNullOrEmpty() {
        return bytes == null || bytes.length == 0;
    }

    public boolean isNotNull() {
        return bytes != null;
    }

    @Nullable
    public byte[] bytes() {
        return bytes;
    }

    @NotNull
    public byte[] bytesOrElse(@NotNull byte[] other) {
        return bytes != null ? bytes : other;
    }

    @NotNull
    public byte[] bytesOrEmpty() {
        return bytesOrElse(new byte[0]);
    }

    @Nullable
    public UByte[] uBytes() {
        if (bytes == null) return null;

        UByte[] bs = new UByte[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
            bs[i] = Unsigned.ubyte(bytes[i]);
        }
        return bs;
    }

    @NotNull
    public UByte[] uBytesOrElse(@NotNull UByte[] other) {
        UByte[] ubs = uBytes();

        return ubs != null ? ubs : other;
    }

    @NotNull
    public UByte[] uBytesOrEmpty() {
        return uBytesOrElse(new UByte[0]);
    }

    public byte byteAt(int index) {
        if (bytes == null) throw new IndexOutOfBoundsException("index=" + index);

        return bytes[index];
    }

    public UByte uByteAt(int index) {
        return Unsigned.ubyte(byteAt(index));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ByteString that = (ByteString) o;

        return Arrays.equals(bytes, that.bytes);
    }

    @Override
    public int hashCode() {
        return bytes != null ? Arrays.hashCode(bytes) : 0;
    }

    public static ByteString of(byte[] bs) {
        return new ByteString(bs);
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
            .add("bytes", Arrays.toString(bytes))
            .toString();
    }

}
