/*
 * Copyright (C) 2013 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 android.hardware.camera2.utils;

import static android.hardware.camera2.CameraAccessException.CAMERA_DISABLED;
import static android.hardware.camera2.CameraAccessException.CAMERA_DISCONNECTED;
import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
import static android.hardware.camera2.CameraAccessException.CAMERA_ERROR;
import static android.hardware.camera2.CameraAccessException.MAX_CAMERAS_IN_USE;
import static android.hardware.camera2.CameraAccessException.CAMERA_DEPRECATED_HAL;

import android.os.DeadObjectException;
import android.os.RemoteException;

import java.lang.reflect.Method;

/**
 * Translate camera device status_t return values into exceptions.
 *
 * @see android.hardware.camera2.utils.CameraBinderDecorator#newInstance
 * @hide
 */
public class CameraBinderDecorator {

    public static final int NO_ERROR = 0;
    public static final int PERMISSION_DENIED = -1;
    public static final int ALREADY_EXISTS = -17;
    public static final int BAD_VALUE = -22;
    public static final int DEAD_OBJECT = -32;
    public static final int INVALID_OPERATION = -38;
    public static final int TIMED_OUT = -110;

    /**
     * TODO: add as error codes in Errors.h
     * - POLICY_PROHIBITS
     * - RESOURCE_BUSY
     * - NO_SUCH_DEVICE
     * - NOT_SUPPORTED
     * - TOO_MANY_USERS
     */
    public static final int EACCES = -13;
    public static final int EBUSY = -16;
    public static final int ENODEV = -19;
    public static final int EOPNOTSUPP = -95;
    public static final int EUSERS = -87;


    static class CameraBinderDecoratorListener implements Decorator.DecoratorListener {

        @Override
        public void onBeforeInvocation(Method m, Object[] args) {
        }

        @Override
        public void onAfterInvocation(Method m, Object[] args, Object result) {
            // int return type => status_t => convert to exception
            if (m.getReturnType() == Integer.TYPE) {
                int returnValue = (Integer) result;
                throwOnError(returnValue);
            }
        }

        @Override
        public boolean onCatchException(Method m, Object[] args, Throwable t) {

            if (t instanceof DeadObjectException) {
                throw new CameraRuntimeException(CAMERA_DISCONNECTED,
                        "Process hosting the camera service has died unexpectedly",
                        t);
            } else if (t instanceof RemoteException) {
                throw new UnsupportedOperationException("An unknown RemoteException was thrown" +
                        " which should never happen.", t);
            }

            return false;
        }

        @Override
        public void onFinally(Method m, Object[] args) {
        }

    }

    /**
     * Throw error codes returned by the camera service as exceptions.
     *
     * @param errorFlag error to throw as an exception.
     */
    public static void throwOnError(int errorFlag) {
        switch (errorFlag) {
            case NO_ERROR:
                return;
            case PERMISSION_DENIED:
                throw new SecurityException("Lacking privileges to access camera service");
            case ALREADY_EXISTS:
                // This should be handled at the call site. Typically this isn't bad,
                // just means we tried to do an operation that already completed.
                return;
            case BAD_VALUE:
                throw new IllegalArgumentException("Bad argument passed to camera service");
            case DEAD_OBJECT:
                throw new CameraRuntimeException(CAMERA_DISCONNECTED);
            case TIMED_OUT:
                throw new CameraRuntimeException(CAMERA_ERROR,
                        "Operation timed out in camera service");
            case EACCES:
                throw new CameraRuntimeException(CAMERA_DISABLED);
            case EBUSY:
                throw new CameraRuntimeException(CAMERA_IN_USE);
            case EUSERS:
                throw new CameraRuntimeException(MAX_CAMERAS_IN_USE);
            case ENODEV:
                throw new CameraRuntimeException(CAMERA_DISCONNECTED);
            case EOPNOTSUPP:
                throw new CameraRuntimeException(CAMERA_DEPRECATED_HAL);
            case INVALID_OPERATION:
                throw new CameraRuntimeException(CAMERA_ERROR,
                        "Illegal state encountered in camera service.");
        }

        /**
         * Trap the rest of the negative return values. If we have known
         * error codes i.e. ALREADY_EXISTS that aren't really runtime
         * errors, then add them to the top switch statement
         */
        if (errorFlag < 0) {
            throw new CameraRuntimeException(CAMERA_ERROR,
                    String.format("Unknown camera device error %d", errorFlag));
        }
    }

    /**
     * <p>
     * Wraps the type T with a proxy that will check 'status_t' return codes
     * from the native side of the camera service, and throw Java exceptions
     * automatically based on the code.
     * </p>
     * <p>
     * In addition it also rewrites binder's RemoteException into either a
     * CameraAccessException or an UnsupportedOperationException.
     * </p>
     * <p>
     * As a result of calling any method on the proxy, RemoteException is
     * guaranteed never to be thrown.
     * </p>
     *
     * @param obj object that will serve as the target for all method calls
     * @param <T> the type of the element you want to wrap. This must be an interface.
     * @return a proxy that will intercept all invocations to obj
     */
    public static <T> T newInstance(T obj) {
        return Decorator.<T> newInstance(obj, new CameraBinderDecoratorListener());
    }
}
