/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

package org.webrtc.videoengine;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import dalvik.system.DexClassLoader;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.util.Log;

import com.voiceblue.phone.utils.Compatibility;

import org.webrtc.videoengine.camera.CameraUtilsWrapper;

@TargetApi(5)
@SuppressWarnings("deprecation")
public class VideoCaptureDeviceInfoAndroid {

    //Context
    Context context;

    // Set VERBOSE as the default logging level because camera device info
    // is very useful information and doesn't degrade performance normally
    private final static String TAG = "WEBRTC";

    // Private class with info about all available cameras and the capabilities
    public class AndroidVideoCaptureDevice {
        public AndroidVideoCaptureDevice() {
            frontCameraType = FrontFacingCameraType.None;
            index = 0;
        }

        public String deviceUniqueName;
        public CaptureCapabilityAndroid captureCapabilies[];
        public FrontFacingCameraType frontCameraType;

        // Orientation of camera as described in
        // android.hardware.Camera.CameraInfo.Orientation
        public int orientation;
        // Camera index used in Camera.Open on Android 2.3 and onwards
        public int index;
        public CaptureCapabilityAndroid bestCapability;
    }

    public enum FrontFacingCameraType {
        None, // This is not a front facing camera
                GalaxyS, // Galaxy S front facing camera.
                HTCEvo, // HTC Evo front facing camera
                Android23, // Android 2.3 front facing camera.
                }

    String currentDeviceUniqueId;
    int id;
    List<AndroidVideoCaptureDevice> deviceList;

    private CameraUtilsWrapper cameraUtils;

    public static VideoCaptureDeviceInfoAndroid
    CreateVideoCaptureDeviceInfoAndroid(int in_id, Context in_context) {
        Log.d(TAG,
                String.format(Locale.US, "VideoCaptureDeviceInfoAndroid"));

        VideoCaptureDeviceInfoAndroid self =
                new VideoCaptureDeviceInfoAndroid(in_id, in_context);
        if(self != null && self.Init() == 0) {
            return self;
        }
        else {
            Log.d(TAG, "Failed to create VideoCaptureDeviceInfoAndroid.");
        }
        return null;
    }

    private VideoCaptureDeviceInfoAndroid(int in_id,
            Context in_context) {
        id = in_id;
        context = in_context;
        deviceList = new ArrayList<AndroidVideoCaptureDevice>();
        cameraUtils = CameraUtilsWrapper.getInstance();
    }

    private int Init() {
        // Populate the deviceList with available cameras and their capabilities.
        try{
            cameraUtils.Init(this, deviceList);
        } 
        catch (Exception ex) {
            Log.e(TAG, "Failed to init VideoCaptureDeviceInfo ex" +
                    ex.getLocalizedMessage());
            return -1;
        }
        VerifyCapabilities();
        return 0;
    }

    // Adds the capture capabilities of the currently opened device
    public void AddDeviceInfo(AndroidVideoCaptureDevice newDevice,
            Camera.Parameters parameters) {

        List<Size> sizes = parameters.getSupportedPreviewSizes();
        List<Integer> frameRates = parameters.getSupportedPreviewFrameRates();
        int maxFPS = 0;
        if(sizes == null) {
            newDevice.captureCapabilies = new CaptureCapabilityAndroid[0];
            return;
        }
        if(frameRates != null) {
        for(Integer frameRate:frameRates) {
            if(frameRate > maxFPS) {
                maxFPS = frameRate;
            }
        }
        }else {
            maxFPS = 15;
        }

        newDevice.captureCapabilies = new CaptureCapabilityAndroid[sizes.size()];
        newDevice.bestCapability = new CaptureCapabilityAndroid();
        int bestBandwidth = 0;
        
        for(int i = 0; i < sizes.size(); ++i) {
            Size s = sizes.get(i);
            newDevice.captureCapabilies[i] = new CaptureCapabilityAndroid();
            newDevice.captureCapabilies[i].height = s.height;
            newDevice.captureCapabilies[i].width = s.width;
            newDevice.captureCapabilies[i].maxFPS = maxFPS;
            Log.v(TAG,
                    "VideoCaptureDeviceInfo " + "maxFPS:" + maxFPS +
                    " width:" + s.width + " height:" + s.height);
            
            // We use h264 primer formula here to estimate bandwidth need
            int currentBandwidth = (int) (s.width * s.height * maxFPS * 0.07);
            int maxBestBandwidth = 1000000;
            // We'd like to find a bandwidth < 1 Mbits
            if (bestBandwidth == 0 ||
                    (currentBandwidth < bestBandwidth && currentBandwidth >= maxBestBandwidth)) {
                newDevice.bestCapability.width = s.width;
                newDevice.bestCapability.height = s.height;
                newDevice.bestCapability.maxFPS = maxFPS;
                bestBandwidth = currentBandwidth;
            } else if (currentBandwidth < maxBestBandwidth) {
                if (s.width > newDevice.bestCapability.width ||
                        s.height > newDevice.bestCapability.height ||
                        bestBandwidth > maxBestBandwidth) {
                    if(s.height != s.width) {
                        newDevice.bestCapability.width = s.width;
                        newDevice.bestCapability.height = s.height;
                        newDevice.bestCapability.maxFPS = maxFPS;
                        bestBandwidth = currentBandwidth;
                    }
                }

            }
        }
        
        Log.d(TAG, "Best capability found " + newDevice.bestCapability.width + " x "
                + newDevice.bestCapability.height);
    }

    // Function that make sure device specific capabilities are
    // in the capability list.
    // Ie Galaxy S supports CIF but does not list CIF as a supported capability.
    // Motorola Droid Camera does not work with frame rate above 15fps.
    // http://code.google.com/p/android/issues/detail?id=5514#c0
    private void VerifyCapabilities() {
        // Nexus S or Galaxy S
        if(android.os.Build.DEVICE.equals("GT-I9000") ||
                android.os.Build.DEVICE.equals("crespo")) {
            CaptureCapabilityAndroid specificCapability =
                    new CaptureCapabilityAndroid();
            specificCapability.width = 352;
            specificCapability.height = 288;
            specificCapability.maxFPS = 15;
            AddDeviceSpecificCapability(specificCapability);

            specificCapability = new CaptureCapabilityAndroid();
            specificCapability.width = 176;
            specificCapability.height = 144;
            specificCapability.maxFPS = 15;
            AddDeviceSpecificCapability(specificCapability);

            specificCapability = new CaptureCapabilityAndroid();
            specificCapability.width = 320;
            specificCapability.height = 240;
            specificCapability.maxFPS = 15;
            AddDeviceSpecificCapability(specificCapability);
        }
        // Motorola Milestone Camera server does not work at 30fps
        // even though it reports that it can
        if(android.os.Build.MANUFACTURER.equals("motorola") &&
                android.os.Build.DEVICE.equals("umts_sholes")) {
            for(AndroidVideoCaptureDevice device:deviceList) {
                for(CaptureCapabilityAndroid capability:device.captureCapabilies) {
                    capability.maxFPS=15;
                }
            }
        }
    }

    private void AddDeviceSpecificCapability(
        CaptureCapabilityAndroid specificCapability) {
        for(AndroidVideoCaptureDevice device:deviceList) {
            boolean foundCapability = false;
            for(CaptureCapabilityAndroid capability:device.captureCapabilies) {
                if(capability.width == specificCapability.width &&
                        capability.height == specificCapability.height) {
                    foundCapability = true;
                    break;
                }
            }
            
            // 3R : galaxy S CIF only on rear
            if (android.os.Build.DEVICE.equals("GT-I9000") &&
                    (device.frontCameraType == FrontFacingCameraType.GalaxyS ||
                    device.frontCameraType == FrontFacingCameraType.Android23)
                    && specificCapability.width == 352 && specificCapability.height == 288) {
                // Simulate found
                foundCapability = true;
            }
            
            if(foundCapability==false) {
                CaptureCapabilityAndroid newCaptureCapabilies[]=
                        new CaptureCapabilityAndroid[device.captureCapabilies.length+1];
                for(int i = 0; i < device.captureCapabilies.length; ++i) {
                    newCaptureCapabilies[i+1] = device.captureCapabilies[i];
                }
                newCaptureCapabilies[0] = specificCapability;
                device.captureCapabilies = newCaptureCapabilies;
            }
        }
    }

    // Returns the number of Capture devices that is supported
    public int NumberOfDevices() {
        return deviceList.size();
    }

    public String GetDeviceUniqueName(int deviceNumber) {
        if(deviceNumber < 0 || deviceNumber >= deviceList.size()) {
            return null;
        }
        return deviceList.get(deviceNumber).deviceUniqueName;
    }

    public CaptureCapabilityAndroid[] GetCapabilityArray (String deviceUniqueId)
    {
        for (AndroidVideoCaptureDevice device: deviceList) {
            if(device.deviceUniqueName.equals(deviceUniqueId)) {
                return (CaptureCapabilityAndroid[]) device.captureCapabilies;
            }
        }
        return null;
    }
    
    public CaptureCapabilityAndroid GetBestCapability (String deviceUniqueId)
    {
        for (AndroidVideoCaptureDevice device: deviceList) {
            if(device.deviceUniqueName.equals(deviceUniqueId)) {
                return (CaptureCapabilityAndroid) device.bestCapability;
            }
        }
        return null;
        
    }

    // Returns the camera orientation as described by
    // android.hardware.Camera.CameraInfo.orientation
    public int GetOrientation(String deviceUniqueId) {
        for (AndroidVideoCaptureDevice device: deviceList) {
            if(device.deviceUniqueName.equals(deviceUniqueId)) {
                return device.orientation;
            }
        }
        return -1;
    }

    // Returns an instance of VideoCaptureAndroid.
    public VideoCaptureAndroid AllocateCamera(int id, long context,
            String deviceUniqueId) {
        try {
            Log.d(TAG, "AllocateCamera " + deviceUniqueId);

            Camera camera = null;
            AndroidVideoCaptureDevice deviceToUse = null;
            for (AndroidVideoCaptureDevice device: deviceList) {
                if(device.deviceUniqueName.equals(deviceUniqueId)) {
                    // Found the wanted camera
                    deviceToUse = device;
                    switch(device.frontCameraType) {
                        case GalaxyS:
                            camera = AllocateGalaxySFrontCamera();
                            break;
                        case HTCEvo:
                            camera = AllocateEVOFrontFacingCamera();
                            break;
                        default:
                            camera = cameraUtils.openCamera(device.index);
                    }
                }
            }

            if(camera == null) {
                return null;
            }
            Log.v(TAG, "AllocateCamera - creating VideoCaptureAndroid");

            return new VideoCaptureAndroid(id, context, camera, deviceToUse);

        }catch (Exception ex) {
            Log.e(TAG, "AllocateCamera Failed to open camera- ex " +
                    ex.getLocalizedMessage());
        }
        return null;
    }

    // Searches for a front facing camera device. This is device specific code.
    public Camera.Parameters
    SearchOldFrontFacingCameras(AndroidVideoCaptureDevice newDevice)
            throws SecurityException, IllegalArgumentException,
            NoSuchMethodException, ClassNotFoundException,
            IllegalAccessException, InvocationTargetException {
        // Check the id of the opened camera device
        // Returns null on X10 and 1 on Samsung Galaxy S.
        Camera camera = Camera.open();
        Camera.Parameters parameters = camera.getParameters();
        String cameraId = parameters.get("camera-id");
        if(cameraId != null && cameraId.equals("1")) {
            // This might be a Samsung Galaxy S with a front facing camera.
            try {
                parameters.set("camera-id", 2);
                camera.setParameters(parameters);
                parameters = camera.getParameters();
                newDevice.frontCameraType = FrontFacingCameraType.GalaxyS;
                newDevice.orientation = 0;
                camera.release();
                return parameters;
            }
            catch (Exception ex) {
                // Nope - it did not work.
                Log.e(TAG, "Init Failed to open front camera camera - ex " +
                        ex.getLocalizedMessage());
            }
        }
        camera.release();

        // Check for Evo front facing camera
        File file =
                new File("/system/framework/com.htc.hardware.twinCamDevice.jar");
        boolean exists = file.exists();
        if (!exists) {
            file =
                    new File("/system/framework/com.sprint.hardware.twinCamDevice.jar");
            exists = file.exists();
        }
        if(exists) {
            newDevice.frontCameraType = FrontFacingCameraType.HTCEvo;
            newDevice.orientation = 0;
            Camera evCamera = AllocateEVOFrontFacingCamera();
            parameters = evCamera.getParameters();
            evCamera.release();
            return parameters;
        }
        return null;
    }

    // Returns a handle to HTC front facing camera.
    // The caller is responsible to release it on completion.
    private Camera AllocateEVOFrontFacingCamera()
            throws SecurityException, NoSuchMethodException,
            ClassNotFoundException, IllegalArgumentException,
            IllegalAccessException, InvocationTargetException {
        String classPath = null;
        File file =
                new File("/system/framework/com.htc.hardware.twinCamDevice.jar");
        classPath = "com.htc.hardware.twinCamDevice.FrontFacingCamera";
        boolean exists = file.exists();
        if (!exists){
            file =
                    new File("/system/framework/com.sprint.hardware.twinCamDevice.jar");
            classPath = "com.sprint.hardware.twinCamDevice.FrontFacingCamera";
            exists = file.exists();
        }
        if(!exists) {
            return null;
        }

        String dexOutputDir = "";
        if(context != null) {
            dexOutputDir = context.getFilesDir().getAbsolutePath();
            File mFilesDir = new File(dexOutputDir, "dexfiles");
            if(!mFilesDir.exists()){
                // Log.e("*WEBRTCN*", "Directory doesn't exists");
                if(!mFilesDir.mkdirs()) {
                    // Log.e("*WEBRTCN*", "Unable to create files directory");
                }
            }
        }

        dexOutputDir += "/dexfiles";

        DexClassLoader loader =
                new DexClassLoader(file.getAbsolutePath(), dexOutputDir,
                        null, ClassLoader.getSystemClassLoader());

        Method method = ((ClassLoader)loader).loadClass(classPath).getDeclaredMethod(
            "getFrontFacingCamera", (Class[]) null);
        Camera camera = (Camera) method.invoke((Object[])null,(Object[]) null);
        return camera;
    }

    // Returns a handle to Galaxy S front camera.
    // The caller is responsible to release it on completion.
    private Camera AllocateGalaxySFrontCamera() {
        Camera camera = Camera.open();
        Camera.Parameters parameters = camera.getParameters();
        parameters.set("camera-id",2);
        camera.setParameters(parameters);
        return camera;
    }

}
