/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.android.tools.chunkio;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.stream.Collectors;

/**
 * Various runtime utilities used by the code generated by the ChunkIO
 * annotation processor.
 */
@SuppressWarnings({ "unused", "WeakerAccess" })
public final class ChunkUtils {
    private ChunkUtils() {
    }

  /**
   * Throws a {@link ChunkException} if the specified condition is false.
   *
   * @param condition The condition to test
   * @param format Exception message in the {@link String#format(String, Object...)} format
   * @param args Arguments for the exception message
   */
    public static void checkState(boolean condition, String format, Object... args) {
        if (!condition) throw new ChunkException(String.format(format, args));
    }

  /**
   * Joins the items of the specified collection using a delimited.
   *
   * @param list List of items to join, cannot be null
   * @param delimiter Delimiter used to join the items, cannot be null
   */
    public static <T> String join(Collection<T> list, String delimiter) {
        return list.stream().map(Object::toString).collect(Collectors.joining(delimiter));
    }

    public static boolean readBoolean(RangedInputStream in, long byteCount) throws IOException {
        boolean b;
        switch ((int) byteCount) {
            case -1:
            case 1:
                b = in.readByte() != 0;
                break;
            case 2:
                b = in.readShort() != 0;
                break;
            case 4:
                b = in.readInt() != 0;
                break;
            case 8:
                b = in.readLong() != 0;
                break;
            default:
                b = in.readByte() != 0;
                skip(in, byteCount - 1);
                break;
        }
        return b;
    }

    public static byte readByte(RangedInputStream in, long byteCount) throws IOException {
        byte b;
        switch ((int) byteCount) {
            case -1:
            case 1:
                b = in.readByte();
                break;
            default:
                b = in.readByte();
                skip(in, byteCount - 1);
                break;
        }
        return b;
    }

    public static char readChar(RangedInputStream in, long byteCount) throws IOException {
        char c;
        switch ((int) byteCount) {
            case 1:
                c = (char) (in.readByte() & 0xff);
                break;
            case -1:
            case 2:
                c = in.readChar();
                break;
            default:
                c = in.readChar();
                skip(in, byteCount - 2);
                break;
        }
        return c;
    }

    public static double readDouble(RangedInputStream in, long byteCount) throws IOException {
        double d;
        switch ((int) byteCount) {
            case 4:
                d = in.readFloat();
                break;
            case -1:
            case 8:
                d = in.readDouble();
                break;
            default:
                d = in.readDouble();
                skip(in, byteCount - 8);
                break;
        }
        return d;
    }

    public static float readFloat(RangedInputStream in, long byteCount) throws IOException {
        float f;
        switch ((int) byteCount) {
            case -1:
            case 4:
                f = in.readFloat();
                break;
            default:
                f = in.readFloat();
                skip(in, byteCount - 4);
                break;
        }
        return f;
    }

    public static int readInt(RangedInputStream in, long byteCount) throws IOException {
        int i;
        switch ((int) byteCount) {
            case 1:
                i = in.readUnsignedByte();
                break;
            case 2:
                i = in.readUnsignedShort();
                break;
            case -1:
            case 4:
                i = in.readInt();
                break;
            default:
                i = in.readInt();
                skip(in, byteCount - 4);
                break;
        }
        return i;
    }

    public static long readLong(RangedInputStream in, long byteCount) throws IOException {
        long l;
        switch ((int) byteCount) {
            case 1:
                l = in.readUnsignedByte();
                break;
            case 2:
                l = in.readUnsignedShort();
                break;
            case 4:
                l = in.readInt() & 0xffffffffL;
                break;
            case -1:
            case 8:
                l = in.readLong();
                break;
            default:
                l = in.readLong();
                skip(in, byteCount - 8);
                break;
        }
        return l;
    }

    public static short readShort(RangedInputStream in, long byteCount) throws IOException {
        short s;
        switch ((int) byteCount) {
            case 1:
                s = (short) (in.readByte() & 0xff);
                break;
            case -1:
            case 2:
                s = in.readShort();
                break;
            default:
                s = in.readShort();
                skip(in, byteCount - 2);
                break;
        }
        return s;
    }

    public static void skip(RangedInputStream in, long byteCount) throws IOException {
        if (byteCount > 0) {
            long skipped = 0;
            long toSkip = byteCount;

            while (skipped < byteCount) {
                long s = in.skip(toSkip);
                if (s < 0) break;
                skipped += s;
                toSkip -= s;
            }
        }
    }

    static long copy(InputStream from, OutputStream to, int bufferSize) throws IOException {
        byte[] buf = new byte[bufferSize];

        long total = 0;
        while (true) {
            int read = from.read(buf);
            if (read < 0) break;
            to.write(buf, 0, read);
            total += read;
        }

        return total;
    }

    public static byte[] readUnboundedByteArray(RangedInputStream in, int bufferSize)
            throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        copy(in, out, bufferSize);
        return out.toByteArray();
    }

    public static byte[] readByteArray(RangedInputStream in, long byteCount) throws IOException {
        byte[] data = new byte[(int) byteCount];
        in.readFully(data);
        return data;
    }

    public static String readString(RangedInputStream in, long byteCount, Charset charset)
            throws IOException {
        return new String(readByteArray(in, byteCount), charset);
    }

    public static byte[] readByteArray(RangedInputStream in, long byteCount, int bufferSize)
            throws IOException {
        if (byteCount >= 0) {
            return readByteArray(in, byteCount);
        } else {
            return readUnboundedByteArray(in, bufferSize);
        }
    }
}
