/*
 * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.platform.internal.display;

import static cyanogenmod.hardware.LiveDisplayManager.MODE_AUTO;
import static cyanogenmod.hardware.LiveDisplayManager.MODE_DAY;
import static cyanogenmod.hardware.LiveDisplayManager.MODE_OUTDOOR;

import android.content.Context;
import android.net.Uri;
import android.os.Handler;

import java.io.PrintWriter;
import java.util.BitSet;

import cyanogenmod.hardware.CMHardwareManager;
import cyanogenmod.hardware.LiveDisplayManager;
import cyanogenmod.providers.CMSettings;

public class OutdoorModeController extends LiveDisplayFeature {

    private final CMHardwareManager mHardware;
    private AmbientLuxObserver mLuxObserver;

    // hardware capabilities
    private final boolean mUseOutdoorMode;

    // default values
    private final int mDefaultOutdoorLux;
    private final int mOutdoorLuxHysteresis;
    private final boolean mDefaultAutoOutdoorMode;
    private final boolean mSelfManaged;

    // internal state
    private boolean mIsOutdoor;
    private boolean mIsSensorEnabled;

    // sliding window for sensor event smoothing
    private static final int SENSOR_WINDOW_MS = 3000;

    public OutdoorModeController(Context context, Handler handler) {
        super(context, handler);

        mHardware = CMHardwareManager.getInstance(mContext);
        mUseOutdoorMode = mHardware.isSupported(CMHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT);
        mSelfManaged = mUseOutdoorMode && mHardware.isSunlightEnhancementSelfManaged();

        mDefaultOutdoorLux = mContext.getResources().getInteger(
                org.cyanogenmod.platform.internal.R.integer.config_outdoorAmbientLux);
        mOutdoorLuxHysteresis = mContext.getResources().getInteger(
                org.cyanogenmod.platform.internal.R.integer.config_outdoorAmbientLuxHysteresis);
        mDefaultAutoOutdoorMode = mContext.getResources().getBoolean(
                org.cyanogenmod.platform.internal.R.bool.config_defaultAutoOutdoorMode);
    }

    @Override
    public void onStart() {
        if (!mUseOutdoorMode) {
            return;
        }

        if (!mSelfManaged) {
            mLuxObserver = new AmbientLuxObserver(mContext, mHandler.getLooper(),
                    mDefaultOutdoorLux, mOutdoorLuxHysteresis, SENSOR_WINDOW_MS);
        }

        registerSettings(
                CMSettings.System.getUriFor(CMSettings.System.DISPLAY_AUTO_OUTDOOR_MODE));
    }

    @Override
    public boolean getCapabilities(final BitSet caps) {
        if (mUseOutdoorMode) {
            caps.set(LiveDisplayManager.MODE_OUTDOOR);
            if (mSelfManaged) {
                caps.set(LiveDisplayManager.FEATURE_MANAGED_OUTDOOR_MODE);
            }
        }
        return mUseOutdoorMode;
    }

    @Override
    protected void onUpdate() {
        updateOutdoorMode();
    }

    @Override
    protected void onTwilightUpdated() {
        updateOutdoorMode();
    }

    @Override
    protected synchronized void onScreenStateChanged() {
        if (!mUseOutdoorMode) {
            return;
        }

        // toggle the sensor when screen on/off
        updateSensorState();

        // Disable outdoor mode on screen off so that we don't melt the users
        // face if they turn it back on in normal conditions
        if (!isScreenOn() && !mSelfManaged && getMode() != MODE_OUTDOOR) {
            mIsOutdoor = false;
            mHardware.set(CMHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT, false);
        }
    }

    @Override
    public synchronized void onSettingsChanged(Uri uri) {
        updateOutdoorMode();
    }

    @Override
    public void dump(PrintWriter pw) {
        pw.println();
        pw.println("OutdoorModeController Configuration:");
        pw.println("  mSelfManaged=" + mSelfManaged);
        if (!mSelfManaged) {
            pw.println("  mDefaultOutdoorLux=" + mDefaultOutdoorLux);
            pw.println("  mOutdoorLuxHysteresis=" + mOutdoorLuxHysteresis);
            pw.println();
            pw.println("  OutdoorModeController State:");
            pw.println("    mAutoOutdoorMode=" + isAutomaticOutdoorModeEnabled());
            pw.println("    mIsOutdoor=" + mIsOutdoor);
            pw.println("    mIsNight=" + isNight());
            pw.println("    hardware state=" +
                    mHardware.get(CMHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT));
        }
        mLuxObserver.dump(pw);
    }

    private synchronized void updateSensorState() {
        if (!mUseOutdoorMode || mLuxObserver == null || mSelfManaged) {
            return;
        }

        /*
         * Light sensor:
         */
        boolean sensorEnabled = false;
        // no sensor if low power mode or when the screen is off
        if (isScreenOn() && !isLowPowerMode()) {
            if (isAutomaticOutdoorModeEnabled()) {
                int mode = getMode();
                if (mode == MODE_DAY) {
                    // always turn it on if day mode is selected
                    sensorEnabled = true;
                } else if (mode == MODE_AUTO && !isNight()) {
                    // in auto mode we turn it on during actual daytime
                    sensorEnabled = true;
                }
            }
        }
        if (mIsSensorEnabled != sensorEnabled) {
            mIsSensorEnabled = sensorEnabled;
            mLuxObserver.setTransitionListener(sensorEnabled ? mListener : null);
        }
    }

    /**
     * Outdoor mode is optionally enabled when ambient lux > 10000 and it's daytime
     * Melt faces!
     *
     * TODO: Use the camera or RGB sensor to determine if it's really sunlight
     */
    private synchronized void updateOutdoorMode() {
        if (!mUseOutdoorMode) {
            return;
        }

        updateSensorState();

        /*
         * Should we turn on outdoor mode or not?
         *
         * Do nothing if the screen is off.
         */
        if (isScreenOn()) {
            boolean enabled = false;
            // turn it off in low power mode
            if (!isLowPowerMode()) {
                int mode = getMode();
                // turn it on if the user manually selected the mode
                if (mode == MODE_OUTDOOR) {
                    enabled = true;
                } else if (isAutomaticOutdoorModeEnabled()) {
                    // self-managed mode means we just flip a switch and an external
                    // implementation does all the sensing. this allows the user
                    // to turn on/off the feature.
                    if (mSelfManaged) {
                        enabled = true;
                    } else if (mIsOutdoor) {
                        // if we're here, the sensor detects extremely bright light.
                        if (mode == MODE_DAY) {
                            // if the user manually selected day mode, go ahead and
                            // melt their face
                            enabled = true;
                        } else if (mode == MODE_AUTO && !isNight()) {
                            // if we're in auto mode, we should also check if it's
                            // night time, since we don't get much sun at night
                            // on this planet :)
                            enabled = true;
                        }
                    }
                }
            }
            mHardware.set(CMHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT, enabled);
        }
    }

    private final AmbientLuxObserver.TransitionListener mListener =
            new AmbientLuxObserver.TransitionListener() {
        @Override
        public void onTransition(final int state, float ambientLux) {
            final boolean outdoor = state == 1;
            synchronized (OutdoorModeController.this) {
                if (mIsOutdoor == outdoor) {
                    return;
                }

                mIsOutdoor = outdoor;
                updateOutdoorMode();
            }
        }
    };

    boolean setAutomaticOutdoorModeEnabled(boolean enabled) {
        if (!mUseOutdoorMode) {
            return false;
        }
        putBoolean(CMSettings.System.DISPLAY_AUTO_OUTDOOR_MODE, enabled);
        return true;
    }

    boolean isAutomaticOutdoorModeEnabled() {
        return mUseOutdoorMode &&
                getBoolean(CMSettings.System.DISPLAY_AUTO_OUTDOOR_MODE,
                           getDefaultAutoOutdoorMode());
    }

    boolean getDefaultAutoOutdoorMode() {
        return mDefaultAutoOutdoorMode;
    }
}
