/*
 * Copyright (C) 2006 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.media;

import android.graphics.Bitmap;
import android.graphics.PointF;
import android.util.Log;

import java.lang.IllegalArgumentException;

/**
 * Identifies the faces of people in a 
 * {@link android.graphics.Bitmap} graphic object.
 */
public class FaceDetector {

    /**
     * A Face contains all the information identifying the location
     * of a face in a bitmap.
     */
    public class Face {
        /** The minimum confidence factor of good face recognition */
        public static final float CONFIDENCE_THRESHOLD = 0.4f;
        /** The x-axis Euler angle of a face. */
        public static final int EULER_X = 0;
        /** The y-axis Euler angle of a face. */
        public static final int EULER_Y = 1;
        /** The z-axis Euler angle of a face. */
        public static final int EULER_Z = 2;

        /** 
         * Returns a confidence factor between 0 and 1. This indicates how
         * certain what has been found is actually a face. A confidence
         * factor above 0.3 is usually good enough.
         */
        public float confidence() {
            return mConfidence;
        }
        /**
         * Sets the position of the mid-point between the eyes.
         * @param point the PointF coordinates (float values) of the 
         *              face's mid-point
         */
        public void getMidPoint(PointF point) {
            // don't return a PointF to avoid allocations
            point.set(mMidPointX, mMidPointY);
        }
        /**
         * Returns the distance between the eyes.
         */
        public float eyesDistance() {
            return mEyesDist;
        }
        /**
         * Returns the face's pose. That is, the rotations around either 
         * the X, Y or Z axis (the positions in 3-dimensional Euclidean space).
         * 
         * @param euler the Euler axis to retrieve an angle from 
         *              (<var>EULER_X</var>, <var>EULER_Y</var> or 
         *              <var>EULER_Z</var>)
         * @return the Euler angle of the of the face, for the given axis
         */
        public float pose(int euler) {
            // don't use an array to avoid allocations
            if (euler == EULER_X)
                return mPoseEulerX;
            else if (euler == EULER_Y)
                return mPoseEulerY;
            else if (euler == EULER_Z)
                return mPoseEulerZ;
           throw new IllegalArgumentException();
        }

        // private ctor, user not supposed to build this object
        private Face() {
        }
        private float   mConfidence;
        private float   mMidPointX;
        private float   mMidPointY;
        private float   mEyesDist;
        private float   mPoseEulerX;
        private float   mPoseEulerY;
        private float   mPoseEulerZ;
    }


    /**
     * Creates a FaceDetector, configured with the size of the images to
     * be analysed and the maximum number of faces that can be detected.
     * These parameters cannot be changed once the object is constructed.
     * Note that the width of the image must be even.
     * 
     * @param width  the width of the image
     * @param height the height of the image
     * @param maxFaces the maximum number of faces to identify
     *
     */
    public FaceDetector(int width, int height, int maxFaces)
    {
        if (!sInitialized) {
            return;
        }
        fft_initialize(width, height, maxFaces);
        mWidth = width;
        mHeight = height;
        mMaxFaces = maxFaces;
        mBWBuffer = new byte[width * height];
    }

    /**
     * Finds all the faces found in a given {@link android.graphics.Bitmap}. 
     * The supplied array is populated with {@link FaceDetector.Face}s for each
     * face found. The bitmap must be in 565 format (for now).
     * 
     * @param bitmap the {@link android.graphics.Bitmap} graphic to be analyzed
     * @param faces  an array in which to place all found 
     *               {@link FaceDetector.Face}s. The array must be sized equal
     *               to the <var>maxFaces</var> value set at initialization
     * @return the number of faces found
     * @throws IllegalArgumentException if the Bitmap dimensions don't match
     *               the dimensions defined at initialization or the given array 
     *               is not sized equal to the <var>maxFaces</var> value defined
     *               at initialization
     */
    public int findFaces(Bitmap bitmap, Face[] faces)
    {
        if (!sInitialized) {
            return 0;
        }
        if (bitmap.getWidth() != mWidth || bitmap.getHeight() != mHeight) {
            throw new IllegalArgumentException(
                    "bitmap size doesn't match initialization");
        }
        if (faces.length < mMaxFaces) {
            throw new IllegalArgumentException(
                    "faces[] smaller than maxFaces");
        }
        
        int numFaces = fft_detect(bitmap);
        if (numFaces >= mMaxFaces)
            numFaces = mMaxFaces;
        for (int i=0 ; i<numFaces ; i++) {
            if (faces[i] == null)
                faces[i] = new Face();
            fft_get_face(faces[i], i);
        }
        return numFaces;
    }


    /* no user serviceable parts here ... */
    @Override
    protected void finalize() throws Throwable {
        fft_destroy();
    }

    /*
     * We use a class initializer to allow the native code to cache some
     * field offsets.
     */
    private static boolean sInitialized;
    native private static void nativeClassInit();

    static {
        sInitialized = false;
        try {
            System.loadLibrary("FFTEm");
            nativeClassInit();
            sInitialized = true;
        } catch (UnsatisfiedLinkError e) {
            Log.d("FFTEm", "face detection library not found!");
        }
    }

    native private int  fft_initialize(int width, int height, int maxFaces);
    native private int  fft_detect(Bitmap bitmap);
    native private void fft_get_face(Face face, int i);
    native private void fft_destroy();

    private int     mFD;
    private int     mSDK;
    private int     mDCR;
    private int     mWidth;
    private int     mHeight;
    private int     mMaxFaces;    
    private byte    mBWBuffer[];
}

