/*
 * Copyright 2017 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.server.location;

import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import android.Manifest;
import android.content.Context;
import android.hardware.contexthub.V1_0.ContextHub;
import android.hardware.contexthub.V1_0.ContextHubMsg;
import android.hardware.contexthub.V1_0.HostEndPoint;
import android.hardware.contexthub.V1_0.HubAppInfo;
import android.hardware.contexthub.V1_0.Result;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.NanoAppBinary;
import android.hardware.location.NanoAppMessage;
import android.hardware.location.NanoAppState;
import android.util.Log;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

/**
 * A class encapsulating helper functions used by the ContextHubService class
 */
/* package */ class ContextHubServiceUtil {
    private static final String TAG = "ContextHubServiceUtil";
    private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
    private static final String CONTEXT_HUB_PERMISSION = Manifest.permission.ACCESS_CONTEXT_HUB;

    /**
     * Creates a ConcurrentHashMap of the Context Hub ID to the ContextHubInfo object given an
     * ArrayList of HIDL ContextHub objects.
     *
     * @param hubList the ContextHub ArrayList
     * @return the HashMap object
     */
    /* package */
    static HashMap<Integer, ContextHubInfo> createContextHubInfoMap(List<ContextHub> hubList) {
        HashMap<Integer, ContextHubInfo> contextHubIdToInfoMap = new HashMap<>();
        for (ContextHub contextHub : hubList) {
            contextHubIdToInfoMap.put(contextHub.hubId, new ContextHubInfo(contextHub));
        }

        return contextHubIdToInfoMap;
    }

    /**
     * Copies a primitive byte array to a ArrayList<Byte>.
     *
     * @param inputArray  the primitive byte array
     * @param outputArray the ArrayList<Byte> array to append
     */
    /* package */
    static void copyToByteArrayList(byte[] inputArray, ArrayList<Byte> outputArray) {
        outputArray.clear();
        outputArray.ensureCapacity(inputArray.length);
        for (byte element : inputArray) {
            outputArray.add(element);
        }
    }

    /**
     * Creates a byte array given a ArrayList<Byte> and copies its contents.
     *
     * @param array the ArrayList<Byte> object
     * @return the byte array
     */
    /* package */
    static byte[] createPrimitiveByteArray(ArrayList<Byte> array) {
        byte[] primitiveArray = new byte[array.size()];
        for (int i = 0; i < array.size(); i++) {
            primitiveArray[i] = array.get(i);
        }

        return primitiveArray;
    }

    /**
     * Creates a primitive integer array given a Collection<Integer>.
     * @param collection the collection to iterate
     * @return the primitive integer array
     */
    static int[] createPrimitiveIntArray(Collection<Integer> collection) {
        int[] primitiveArray = new int[collection.size()];

        int i = 0;
        for (int contextHubId : collection) {
            primitiveArray[i++] = contextHubId;
        }

        return primitiveArray;
    }

    /**
     * Generates the Context Hub HAL's NanoAppBinary object from the client-facing
     * android.hardware.location.NanoAppBinary object.
     *
     * @param nanoAppBinary the client-facing NanoAppBinary object
     * @return the Context Hub HAL's NanoAppBinary object
     */
    /* package */
    static android.hardware.contexthub.V1_0.NanoAppBinary createHidlNanoAppBinary(
            NanoAppBinary nanoAppBinary) {
        android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
                new android.hardware.contexthub.V1_0.NanoAppBinary();

        hidlNanoAppBinary.appId = nanoAppBinary.getNanoAppId();
        hidlNanoAppBinary.appVersion = nanoAppBinary.getNanoAppVersion();
        hidlNanoAppBinary.flags = nanoAppBinary.getFlags();
        hidlNanoAppBinary.targetChreApiMajorVersion = nanoAppBinary.getTargetChreApiMajorVersion();
        hidlNanoAppBinary.targetChreApiMinorVersion = nanoAppBinary.getTargetChreApiMinorVersion();

        // Log exceptions while processing the binary, but continue to pass down the binary
        // since the error checking is deferred to the Context Hub.
        try {
            copyToByteArrayList(nanoAppBinary.getBinaryNoHeader(), hidlNanoAppBinary.customBinary);
        } catch (IndexOutOfBoundsException e) {
            Log.w(TAG, e.getMessage());
        } catch (NullPointerException e) {
            Log.w(TAG, "NanoApp binary was null");
        }

        return hidlNanoAppBinary;
    }

    /**
     * Generates a client-facing NanoAppState array from a HAL HubAppInfo array.
     *
     * @param nanoAppInfoList the array of HubAppInfo objects
     * @return the corresponding array of NanoAppState objects
     */
    /* package */
    static List<NanoAppState> createNanoAppStateList(
            List<HubAppInfo> nanoAppInfoList) {
        ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
        for (HubAppInfo appInfo : nanoAppInfoList) {
            nanoAppStateList.add(
                    new NanoAppState(appInfo.appId, appInfo.version, appInfo.enabled));
        }

        return nanoAppStateList;
    }

    /**
     * Creates a HIDL ContextHubMsg object to send to a nanoapp.
     *
     * @param hostEndPoint the ID of the client sending the message
     * @param message      the client-facing NanoAppMessage object describing the message
     * @return the HIDL ContextHubMsg object
     */
    /* package */
    static ContextHubMsg createHidlContextHubMessage(short hostEndPoint, NanoAppMessage message) {
        ContextHubMsg hidlMessage = new ContextHubMsg();

        hidlMessage.appName = message.getNanoAppId();
        hidlMessage.hostEndPoint = hostEndPoint;
        hidlMessage.msgType = message.getMessageType();
        copyToByteArrayList(message.getMessageBody(), hidlMessage.msg);

        return hidlMessage;
    }

    /**
     * Creates a client-facing NanoAppMessage object to send to a client.
     *
     * @param message the HIDL ContextHubMsg object from a nanoapp
     * @return the NanoAppMessage object
     */
    /* package */
    static NanoAppMessage createNanoAppMessage(ContextHubMsg message) {
        byte[] messageArray = createPrimitiveByteArray(message.msg);

        return NanoAppMessage.createMessageFromNanoApp(
                message.appName, message.msgType, messageArray,
                message.hostEndPoint == HostEndPoint.BROADCAST);
    }

    /**
     * Checks for location hardware permissions.
     *
     * @param context the context of the service
     */
    /* package */
    static void checkPermissions(Context context) {
        if (context.checkCallingPermission(HARDWARE_PERMISSION) != PERMISSION_GRANTED
                && context.checkCallingPermission(CONTEXT_HUB_PERMISSION) != PERMISSION_GRANTED) {
            throw new SecurityException(
                "LOCATION_HARDWARE or ACCESS_CONTEXT_HUB permission required to use Context Hub");
        }
    }

    /**
     * Helper function to convert from the HAL Result enum error code to the
     * ContextHubTransaction.Result type.
     *
     * @param halResult the Result enum error code
     * @return the ContextHubTransaction.Result equivalent
     */
    @ContextHubTransaction.Result
    /* package */
    static int toTransactionResult(int halResult) {
        switch (halResult) {
            case Result.OK:
                return ContextHubTransaction.RESULT_SUCCESS;
            case Result.BAD_PARAMS:
                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
            case Result.NOT_INIT:
                return ContextHubTransaction.RESULT_FAILED_UNINITIALIZED;
            case Result.TRANSACTION_PENDING:
                return ContextHubTransaction.RESULT_FAILED_BUSY;
            case Result.TRANSACTION_FAILED:
            case Result.UNKNOWN_FAILURE:
            default: /* fall through */
                return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
        }
    }
}
