/*
 * Copyright (C) 2012 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;

import static android.view.Display.DEFAULT_DISPLAY;

import android.os.RemoteException;
import android.os.ServiceManager;
import android.view.IRotationWatcher;
import android.view.IWindowManager;
import android.view.Surface;

import java.util.HashMap;
import java.util.List;

/**
 * Helper class for implementing the legacy sensor manager API.
 * @hide
 */
@SuppressWarnings("deprecation")
final class LegacySensorManager {
    private static boolean sInitialized;
    private static IWindowManager sWindowManager;
    private static int sRotation = Surface.ROTATION_0;

    private final SensorManager mSensorManager;

    // List of legacy listeners.  Guarded by mLegacyListenersMap.
    private final HashMap<SensorListener, LegacyListener> mLegacyListenersMap =
            new HashMap<SensorListener, LegacyListener>();

    public LegacySensorManager(SensorManager sensorManager) {
        mSensorManager = sensorManager;

        synchronized (SensorManager.class) {
            if (!sInitialized) {
                sWindowManager = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                if (sWindowManager != null) {
                    // if it's null we're running in the system process
                    // which won't get the rotated values
                    try {
                        sRotation = sWindowManager.watchRotation(
                                new IRotationWatcher.Stub() {
                                    public void onRotationChanged(int rotation) {
                                        LegacySensorManager.onRotationChanged(rotation);
                                    }
                                }, DEFAULT_DISPLAY);
                    } catch (RemoteException e) {
                    }
                }
            }
        }
    }

    public int getSensors() {
        int result = 0;
        final List<Sensor> fullList = mSensorManager.getFullSensorList();
        for (Sensor i : fullList) {
            switch (i.getType()) {
                case Sensor.TYPE_ACCELEROMETER:
                    result |= SensorManager.SENSOR_ACCELEROMETER;
                    break;
                case Sensor.TYPE_MAGNETIC_FIELD:
                    result |= SensorManager.SENSOR_MAGNETIC_FIELD;
                    break;
                case Sensor.TYPE_ORIENTATION:
                    result |= SensorManager.SENSOR_ORIENTATION
                            | SensorManager.SENSOR_ORIENTATION_RAW;
                    break;
            }
        }
        return result;
    }

    public boolean registerListener(SensorListener listener, int sensors, int rate) {
        if (listener == null) {
            return false;
        }
        boolean result = false;
        result = registerLegacyListener(SensorManager.SENSOR_ACCELEROMETER,
                Sensor.TYPE_ACCELEROMETER, listener, sensors, rate) || result;
        result = registerLegacyListener(SensorManager.SENSOR_MAGNETIC_FIELD,
                Sensor.TYPE_MAGNETIC_FIELD, listener, sensors, rate) || result;
        result = registerLegacyListener(SensorManager.SENSOR_ORIENTATION_RAW,
                Sensor.TYPE_ORIENTATION, listener, sensors, rate) || result;
        result = registerLegacyListener(SensorManager.SENSOR_ORIENTATION,
                Sensor.TYPE_ORIENTATION, listener, sensors, rate) || result;
        result = registerLegacyListener(SensorManager.SENSOR_TEMPERATURE,
                Sensor.TYPE_TEMPERATURE, listener, sensors, rate) || result;
        return result;
    }

    private boolean registerLegacyListener(int legacyType, int type,
            SensorListener listener, int sensors, int rate) {
        boolean result = false;
        // Are we activating this legacy sensor?
        if ((sensors & legacyType) != 0) {
            // if so, find a suitable Sensor
            Sensor sensor = mSensorManager.getDefaultSensor(type);
            if (sensor != null) {
                // We do all of this work holding the legacy listener lock to ensure
                // that the invariants around listeners are maintained.  This is safe
                // because neither registerLegacyListener nor unregisterLegacyListener
                // are called reentrantly while sensors are being registered or unregistered.
                synchronized (mLegacyListenersMap) {
                    // If we don't already have one, create a LegacyListener
                    // to wrap this listener and process the events as
                    // they are expected by legacy apps.
                    LegacyListener legacyListener = mLegacyListenersMap.get(listener);
                    if (legacyListener == null) {
                        // we didn't find a LegacyListener for this client,
                        // create one, and put it in our list.
                        legacyListener = new LegacyListener(listener);
                        mLegacyListenersMap.put(listener, legacyListener);
                    }

                    // register this legacy sensor with this legacy listener
                    if (legacyListener.registerSensor(legacyType)) {
                        // and finally, register the legacy listener with the new apis
                        result = mSensorManager.registerListener(legacyListener, sensor, rate);
                    } else {
                        result = true; // sensor already enabled
                    }
                }
            }
        }
        return result;
    }

    public void unregisterListener(SensorListener listener, int sensors) {
        if (listener == null) {
            return;
        }
        unregisterLegacyListener(SensorManager.SENSOR_ACCELEROMETER, Sensor.TYPE_ACCELEROMETER,
                listener, sensors);
        unregisterLegacyListener(SensorManager.SENSOR_MAGNETIC_FIELD, Sensor.TYPE_MAGNETIC_FIELD,
                listener, sensors);
        unregisterLegacyListener(SensorManager.SENSOR_ORIENTATION_RAW, Sensor.TYPE_ORIENTATION,
                listener, sensors);
        unregisterLegacyListener(SensorManager.SENSOR_ORIENTATION, Sensor.TYPE_ORIENTATION,
                listener, sensors);
        unregisterLegacyListener(SensorManager.SENSOR_TEMPERATURE, Sensor.TYPE_TEMPERATURE,
                listener, sensors);
    }

    private void unregisterLegacyListener(int legacyType, int type,
            SensorListener listener, int sensors) {
        // Are we deactivating this legacy sensor?
        if ((sensors & legacyType) != 0) {
            // if so, find the corresponding Sensor
            Sensor sensor = mSensorManager.getDefaultSensor(type);
            if (sensor != null) {
                // We do all of this work holding the legacy listener lock to ensure
                // that the invariants around listeners are maintained.  This is safe
                // because neither registerLegacyListener nor unregisterLegacyListener
                // are called re-entrantly while sensors are being registered or unregistered.
                synchronized (mLegacyListenersMap) {
                    // do we know about this listener?
                    LegacyListener legacyListener = mLegacyListenersMap.get(listener);
                    if (legacyListener != null) {
                        // unregister this legacy sensor and if we don't
                        // need the corresponding Sensor, unregister it too
                        if (legacyListener.unregisterSensor(legacyType)) {
                            // corresponding sensor not needed, unregister
                            mSensorManager.unregisterListener(legacyListener, sensor);

                            // finally check if we still need the legacyListener
                            // in our mapping, if not, get rid of it too.
                            if (!legacyListener.hasSensors()) {
                                mLegacyListenersMap.remove(listener);
                            }
                        }
                    }
                }
            }
        }
    }

    static void onRotationChanged(int rotation) {
        synchronized (SensorManager.class) {
            sRotation  = rotation;
        }
    }

    static int getRotation() {
        synchronized (SensorManager.class) {
            return sRotation;
        }
    }

    private static final class LegacyListener implements SensorEventListener {
        private float[] mValues = new float[6];
        private SensorListener mTarget;
        private int mSensors;
        private final LmsFilter mYawfilter = new LmsFilter();

        LegacyListener(SensorListener target) {
            mTarget = target;
            mSensors = 0;
        }

        boolean registerSensor(int legacyType) {
            if ((mSensors & legacyType) != 0) {
                return false;
            }
            boolean alreadyHasOrientationSensor = hasOrientationSensor(mSensors);
            mSensors |= legacyType;
            if (alreadyHasOrientationSensor && hasOrientationSensor(legacyType)) {
                return false; // don't need to re-register the orientation sensor
            }
            return true;
        }

        boolean unregisterSensor(int legacyType) {
            if ((mSensors & legacyType) == 0) {
                return false;
            }
            mSensors &= ~legacyType;
            if (hasOrientationSensor(legacyType) && hasOrientationSensor(mSensors)) {
                return false; // can't unregister the orientation sensor just yet
            }
            return true;
        }

        boolean hasSensors() {
            return mSensors != 0;
        }

        private static boolean hasOrientationSensor(int sensors) {
            return (sensors & (SensorManager.SENSOR_ORIENTATION
                    | SensorManager.SENSOR_ORIENTATION_RAW)) != 0;
        }

        public void onAccuracyChanged(Sensor sensor, int accuracy) {
            try {
                mTarget.onAccuracyChanged(getLegacySensorType(sensor.getType()), accuracy);
            } catch (AbstractMethodError e) {
                // old app that doesn't implement this method
                // just ignore it.
            }
        }

        public void onSensorChanged(SensorEvent event) {
            final float[] v = mValues;
            v[0] = event.values[0];
            v[1] = event.values[1];
            v[2] = event.values[2];
            int type = event.sensor.getType();
            int legacyType = getLegacySensorType(type);
            mapSensorDataToWindow(legacyType, v, LegacySensorManager.getRotation());
            if (type == Sensor.TYPE_ORIENTATION) {
                if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW) != 0) {
                    mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION_RAW, v);
                }
                if ((mSensors & SensorManager.SENSOR_ORIENTATION) != 0) {
                    v[0] = mYawfilter.filter(event.timestamp, v[0]);
                    mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION, v);
                }
            } else {
                mTarget.onSensorChanged(legacyType, v);
            }
        }

        /*
         * Helper function to convert the specified sensor's data to the windows's
         * coordinate space from the device's coordinate space.
         *
         * output: 3,4,5: values in the old API format
         *         0,1,2: transformed values in the old API format
         *
         */
        private void mapSensorDataToWindow(int sensor,
                float[] values, int orientation) {
            float x = values[0];
            float y = values[1];
            float z = values[2];

            switch (sensor) {
                case SensorManager.SENSOR_ORIENTATION:
                case SensorManager.SENSOR_ORIENTATION_RAW:
                    z = -z;
                    break;
                case SensorManager.SENSOR_ACCELEROMETER:
                    x = -x;
                    y = -y;
                    z = -z;
                    break;
                case SensorManager.SENSOR_MAGNETIC_FIELD:
                    x = -x;
                    y = -y;
                    break;
            }
            values[0] = x;
            values[1] = y;
            values[2] = z;
            values[3] = x;
            values[4] = y;
            values[5] = z;

            if ((orientation & Surface.ROTATION_90) != 0) {
                // handles 90 and 270 rotation
                switch (sensor) {
                    case SensorManager.SENSOR_ACCELEROMETER:
                    case SensorManager.SENSOR_MAGNETIC_FIELD:
                        values[0] = -y;
                        values[1] = x;
                        values[2] = z;
                        break;
                    case SensorManager.SENSOR_ORIENTATION:
                    case SensorManager.SENSOR_ORIENTATION_RAW:
                        values[0] = x + ((x < 270) ? 90 : -270);
                        values[1] = z;
                        values[2] = y;
                        break;
                }
            }
            if ((orientation & Surface.ROTATION_180) != 0) {
                x = values[0];
                y = values[1];
                z = values[2];
                // handles 180 (flip) and 270 (flip + 90) rotation
                switch (sensor) {
                    case SensorManager.SENSOR_ACCELEROMETER:
                    case SensorManager.SENSOR_MAGNETIC_FIELD:
                        values[0] = -x;
                        values[1] = -y;
                        values[2] = z;
                        break;
                    case SensorManager.SENSOR_ORIENTATION:
                    case SensorManager.SENSOR_ORIENTATION_RAW:
                        values[0] = (x >= 180) ? (x - 180) : (x + 180);
                        values[1] = -y;
                        values[2] = -z;
                        break;
                }
            }
        }

        private static int getLegacySensorType(int type) {
            switch (type) {
                case Sensor.TYPE_ACCELEROMETER:
                    return SensorManager.SENSOR_ACCELEROMETER;
                case Sensor.TYPE_MAGNETIC_FIELD:
                    return SensorManager.SENSOR_MAGNETIC_FIELD;
                case Sensor.TYPE_ORIENTATION:
                    return SensorManager.SENSOR_ORIENTATION_RAW;
                case Sensor.TYPE_TEMPERATURE:
                    return SensorManager.SENSOR_TEMPERATURE;
            }
            return 0;
        }
    }

    private static final class LmsFilter {
        private static final int SENSORS_RATE_MS = 20;
        private static final int COUNT = 12;
        private static final float PREDICTION_RATIO = 1.0f / 3.0f;
        private static final float PREDICTION_TIME =
                (SENSORS_RATE_MS * COUNT / 1000.0f) * PREDICTION_RATIO;
        private float[] mV = new float[COUNT * 2];
        private long[] mT = new long[COUNT * 2];
        private int mIndex;

        public LmsFilter() {
            mIndex = COUNT;
        }

        public float filter(long time, float in) {
            float v = in;
            final float ns = 1.0f / 1000000000.0f;
            float v1 = mV[mIndex];
            if ((v - v1) > 180) {
                v -= 360;
            } else if ((v1 - v) > 180) {
                v += 360;
            }
            /* Manage the circular buffer, we write the data twice spaced
             * by COUNT values, so that we don't have to copy the array
             * when it's full
             */
            mIndex++;
            if (mIndex >= COUNT * 2) {
                mIndex = COUNT;
            }
            mV[mIndex] = v;
            mT[mIndex] = time;
            mV[mIndex - COUNT] = v;
            mT[mIndex - COUNT] = time;

            float A, B, C, D, E;
            float a, b;
            int i;

            A = B = C = D = E = 0;
            for (i = 0; i < COUNT - 1; i++) {
                final int j = mIndex - 1 - i;
                final float Z = mV[j];
                final float T = (mT[j] / 2 + mT[j + 1] / 2 - time) * ns;
                float dT = (mT[j] - mT[j + 1]) * ns;
                dT *= dT;
                A += Z * dT;
                B += T * (T * dT);
                C += (T * dT);
                D += Z * (T * dT);
                E += dT;
            }
            b = (A * B + C * D) / (E * B + C * C);
            a = (E * b - A) / C;
            float f = b + PREDICTION_TIME * a;

            // Normalize
            f *= (1.0f / 360.0f);
            if (((f >= 0) ? f : -f) >= 0.5f) {
                f = f - (float) Math.ceil(f + 0.5f) + 1.0f;
            }
            if (f < 0) {
                f += 1.0f;
            }
            f *= 360.0f;
            return f;
        }
    }
}
