/*
 * 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 com.android.bluetooth.hfp;

import android.content.Context;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.util.Log;
import android.bluetooth.BluetoothDevice;


// Note:
// All methods in this class are not thread safe, donot call them from
// multiple threads. Call them from the HeadsetPhoneStateMachine message
// handler only.
class HeadsetPhoneState {
    private static final String TAG = "HeadsetPhoneState";

    private HeadsetStateMachine mStateMachine;
    private TelephonyManager mTelephonyManager;
    private ServiceState mServiceState;

    // HFP 1.6 CIND service
    private int mService = HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;

    // Number of active (foreground) calls
    private int mNumActive = 0;

    // Current Call Setup State
    private int mCallState = HeadsetHalConstants.CALL_STATE_IDLE;

    // Number of held (background) calls
    private int mNumHeld = 0;

    // HFP 1.6 CIND signal
    private int mSignal = 0;

    // HFP 1.6 CIND roam
    private int mRoam = HeadsetHalConstants.SERVICE_TYPE_HOME;

    // HFP 1.6 CIND battchg
    private int mBatteryCharge = 0;

    private int mSpeakerVolume = 0;

    private int mMicVolume = 0;

    private boolean mListening = false;

    // when HFP Service Level Connection is established
    private boolean mSlcReady = false;

    private Context mContext = null;

    private PhoneStateListener mPhoneStateListener = null;

    private SubscriptionManager mSubMgr;

    private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
            new OnSubscriptionsChangedListener() {
        @Override
        public void onSubscriptionsChanged() {
            listenForPhoneState(false);
            listenForPhoneState(true);
        }
    };


    HeadsetPhoneState(Context context, HeadsetStateMachine stateMachine) {
        mStateMachine = stateMachine;
        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        mContext = context;

        // Register for SubscriptionInfo list changes which is guaranteed
        // to invoke onSubscriptionInfoChanged and which in turns calls
        // loadInBackgroud.
        mSubMgr = SubscriptionManager.from(mContext);
        mSubMgr.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
    }

    public void cleanup() {
        listenForPhoneState(false);
        mSubMgr.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);

        mTelephonyManager = null;
        mStateMachine = null;
    }

    void listenForPhoneState(boolean start) {

        mSlcReady = start;

        if (start) {
            startListenForPhoneState();
        } else {
            stopListenForPhoneState();
        }

    }

    private void startListenForPhoneState() {
        if (!mListening && mSlcReady) {

            int subId = SubscriptionManager.getDefaultSubId();

            if (SubscriptionManager.isValidSubscriptionId(subId)) {
                mPhoneStateListener = getPhoneStateListener(subId);

                mTelephonyManager.listen(mPhoneStateListener,
                                         PhoneStateListener.LISTEN_SERVICE_STATE |
                                         PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
                mListening = true;
            }
        }
    }

    private void stopListenForPhoneState() {
        if (mListening) {

            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
            mListening = false;
        }
    }

    int getService() {
        return mService;
    }

    int getNumActiveCall() {
        return mNumActive;
    }

    void setNumActiveCall(int numActive) {
        mNumActive = numActive;
    }

    int getCallState() {
        return mCallState;
    }

    void setCallState(int callState) {
        mCallState = callState;
    }

    int getNumHeldCall() {
        return mNumHeld;
    }

    void setNumHeldCall(int numHeldCall) {
        mNumHeld = numHeldCall;
    }

    int getSignal() {
        return mSignal;
    }

    int getRoam() {
        return mRoam;
    }

    void setRoam(int roam) {
        mRoam = roam;
    }

    void setBatteryCharge(int batteryLevel) {
        if (mBatteryCharge != batteryLevel) {
            mBatteryCharge = batteryLevel;
            sendDeviceStateChanged();
        }
    }

    int getBatteryCharge() {
        return mBatteryCharge;
    }

    void setSpeakerVolume(int volume) {
        mSpeakerVolume = volume;
    }

    int getSpeakerVolume() {
        return mSpeakerVolume;
    }

    void setMicVolume(int volume) {
        mMicVolume = volume;
    }

    int getMicVolume() {
        return mMicVolume;
    }

    boolean isInCall() {
        return (mNumActive >= 1);
    }

    void sendDeviceStateChanged()
    {
        // When out of service, send signal strength as 0. Some devices don't
        // use the service indicator, but only the signal indicator
        int signal = mService == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mSignal : 0;

        Log.d(TAG, "sendDeviceStateChanged. mService="+ mService +
                   " mSignal=" + signal +" mRoam="+ mRoam +
                   " mBatteryCharge=" + mBatteryCharge);
        HeadsetStateMachine sm = mStateMachine;
        if (sm != null) {
            sm.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED,
                new HeadsetDeviceState(mService, mRoam, signal, mBatteryCharge));
        }
    }

    private PhoneStateListener getPhoneStateListener(int subId) {
        PhoneStateListener mPhoneStateListener = new PhoneStateListener(subId) {
            @Override
            public void onServiceStateChanged(ServiceState serviceState) {

                mServiceState = serviceState;
                mService = (serviceState.getState() == ServiceState.STATE_IN_SERVICE) ?
                    HeadsetHalConstants.NETWORK_STATE_AVAILABLE :
                    HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
                setRoam(serviceState.getRoaming() ? HeadsetHalConstants.SERVICE_TYPE_ROAMING
                                                  : HeadsetHalConstants.SERVICE_TYPE_HOME);

                sendDeviceStateChanged();
            }

            @Override
            public void onSignalStrengthsChanged(SignalStrength signalStrength) {

                int prevSignal = mSignal;
                if (mService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
                    mSignal = 0;
                } else if (signalStrength.isGsm()) {
                    mSignal = signalStrength.getLteLevel();
                    if (mSignal == SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
                        mSignal = gsmAsuToSignal(signalStrength);
                    } else {
                        // SignalStrength#getLteLevel returns the scale from 0-4
                        // Bluetooth signal scales at 0-5
                        // Let's match up the larger side
                        mSignal++;
                    }
                } else {
                    mSignal = cdmaDbmEcioToSignal(signalStrength);
                }

                // network signal strength is scaled to BT 1-5 levels.
                // This results in a lot of duplicate messages, hence this check
                if (prevSignal != mSignal) {
                    sendDeviceStateChanged();
                }
            }

            /* convert [0,31] ASU signal strength to the [0,5] expected by
             * bluetooth devices. Scale is similar to status bar policy
             */
            private int gsmAsuToSignal(SignalStrength signalStrength) {
                int asu = signalStrength.getGsmSignalStrength();
                if      (asu >= 16) return 5;
                else if (asu >= 8)  return 4;
                else if (asu >= 4)  return 3;
                else if (asu >= 2)  return 2;
                else if (asu >= 1)  return 1;
                else                return 0;
            }

            /**
             * Convert the cdma / evdo db levels to appropriate icon level.
             * The scale is similar to the one used in status bar policy.
             *
             * @param signalStrength
             * @return the icon level
             */
            private int cdmaDbmEcioToSignal(SignalStrength signalStrength) {
                int levelDbm = 0;
                int levelEcio = 0;
                int cdmaIconLevel = 0;
                int evdoIconLevel = 0;
                int cdmaDbm = signalStrength.getCdmaDbm();
                int cdmaEcio = signalStrength.getCdmaEcio();

                if (cdmaDbm >= -75) levelDbm = 4;
                else if (cdmaDbm >= -85) levelDbm = 3;
                else if (cdmaDbm >= -95) levelDbm = 2;
                else if (cdmaDbm >= -100) levelDbm = 1;
                else levelDbm = 0;

                // Ec/Io are in dB*10
                if (cdmaEcio >= -90) levelEcio = 4;
                else if (cdmaEcio >= -110) levelEcio = 3;
                else if (cdmaEcio >= -130) levelEcio = 2;
                else if (cdmaEcio >= -150) levelEcio = 1;
                else levelEcio = 0;

                cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio;

                // STOPSHIP: Change back to getRilVoiceRadioTechnology
                if (mServiceState != null &&
                      (mServiceState.getRadioTechnology() ==
                          ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0 ||
                       mServiceState.getRadioTechnology() ==
                           ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A)) {
                      int evdoEcio = signalStrength.getEvdoEcio();
                      int evdoSnr = signalStrength.getEvdoSnr();
                      int levelEvdoEcio = 0;
                      int levelEvdoSnr = 0;

                      // Ec/Io are in dB*10
                      if (evdoEcio >= -650) levelEvdoEcio = 4;
                      else if (evdoEcio >= -750) levelEvdoEcio = 3;
                      else if (evdoEcio >= -900) levelEvdoEcio = 2;
                      else if (evdoEcio >= -1050) levelEvdoEcio = 1;
                      else levelEvdoEcio = 0;

                      if (evdoSnr > 7) levelEvdoSnr = 4;
                      else if (evdoSnr > 5) levelEvdoSnr = 3;
                      else if (evdoSnr > 3) levelEvdoSnr = 2;
                      else if (evdoSnr > 1) levelEvdoSnr = 1;
                      else levelEvdoSnr = 0;

                      evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr;
                }
                // TODO(): There is a bug open regarding what should be sent.
                return (cdmaIconLevel > evdoIconLevel) ?  cdmaIconLevel : evdoIconLevel;
            }
        };
        return mPhoneStateListener;
    }

}

class HeadsetDeviceState {
    int mService;
    int mRoam;
    int mSignal;
    int mBatteryCharge;

    HeadsetDeviceState(int service, int roam, int signal, int batteryCharge) {
        mService = service;
        mRoam = roam;
        mSignal = signal;
        mBatteryCharge = batteryCharge;
    }
}

class HeadsetCallState {
    int mNumActive;
    int mNumHeld;
    int mCallState;
    String mNumber;
    int mType;

    public HeadsetCallState(int numActive, int numHeld, int callState, String number, int type) {
        mNumActive = numActive;
        mNumHeld = numHeld;
        mCallState = callState;
        mNumber = number;
        mType = type;
    }
}

class HeadsetClccResponse {
    int mIndex;
    int mDirection;
    int mStatus;
    int mMode;
    boolean mMpty;
    String mNumber;
    int mType;

    public HeadsetClccResponse(int index, int direction, int status, int mode, boolean mpty,
                               String number, int type) {
        mIndex = index;
        mDirection = direction;
        mStatus = status;
        mMode = mode;
        mMpty = mpty;
        mNumber = number;
        mType = type;
    }
}

class HeadsetVendorSpecificResultCode {
    BluetoothDevice mDevice;
    String mCommand;
    String mArg;

    public HeadsetVendorSpecificResultCode(BluetoothDevice device, String command, String arg) {
        mDevice = device;
        mCommand = command;
        mArg = arg;
    }
}
