/*
 * Copyright (C) 2017 Peter Gregus for GravityBox Project (C3C076@xda)
 * 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.wrbug.gravitybox.nougat.quicksettings;

import de.robv.android.xposed.XSharedPreferences;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;

import java.util.concurrent.atomic.AtomicReference;

import com.wrbug.gravitybox.nougat.R;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;

public class BluetoothTetheringTile extends QsTile {
    private static final int BT_PROFILE_PAN = 5;
    public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";

    private String[] mTetherableBluetoothRegexs;
    private AtomicReference<Object> mBluetoothPan;
    private boolean mBluetoothEnableForTether;
    private boolean mIsListening;

    public BluetoothTetheringTile(Object host, String key, XSharedPreferences prefs,
            QsTileEventDistributor eventDistributor) throws Throwable {
        super(host, key, prefs, eventDistributor);

        mBluetoothPan = new AtomicReference<>();
    }

    private String[] getTetherableBluetoothRegexs() {
        if (mTetherableBluetoothRegexs == null) {
            try {
                ConnectivityManager cm = (ConnectivityManager)
                        mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
                return (String[]) XposedHelpers.callMethod(cm, "getTetherableBluetoothRegexs");
            } catch (Throwable t) { 
                return new String[0];
            }
        }
        return mTetherableBluetoothRegexs;
    }

    private String[] getTetheringErroredIfaces() {
        try {
            ConnectivityManager cm = (ConnectivityManager)
                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
            return (String[]) XposedHelpers.callMethod(cm, "getTetheringErroredIfaces");
        } catch (Throwable t) { 
            return new String[0];
        }
    }

    private BluetoothProfile.ServiceListener mProfileServiceListener =
            new BluetoothProfile.ServiceListener() {
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            if (DEBUG) log("mProfileServiceListener: onServiceConnected");
            mBluetoothPan.set(proxy);
            if (mBluetoothEnableForTether) {
                setTethering(true);
                mBluetoothEnableForTether = false;
            }
            refreshState();
        }
        @Override
        public void onServiceDisconnected(int profile) {
            if (DEBUG) log("mProfileServiceListener: onServiceDisconnected");
            unregisterServiceListener();
        }
    };

    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                        BluetoothAdapter.ERROR);
                if (DEBUG) log("Bluetooth state changed: state=" + state +
                        "; mBluetoothEnableForTether=" + mBluetoothEnableForTether);
                switch (state) {
                    case BluetoothAdapter.STATE_ON:
                        registerServiceListener();
                        break;
                    case BluetoothAdapter.STATE_OFF:
                    case BluetoothAdapter.ERROR:
                        unregisterServiceListener();
                        break;
                    default:
                        // ignore transition states
                }
            }
            refreshState();
        }
    };

    private void registerListeners() {
        if (!mIsListening) {
            registerServiceListener();
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(ACTION_TETHER_STATE_CHANGED);
            intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
            mContext.registerReceiver(mBroadcastReceiver, intentFilter);
            mIsListening = true;
            if (DEBUG) log("listeners registered");
        }
    }

    private void unregisterListeners() {
        if (mIsListening) {
            unregisterServiceListener();
            mContext.unregisterReceiver(mBroadcastReceiver);
            mIsListening = false;
            if (DEBUG) log("listeners unregistered");
        }
    }

    private void registerServiceListener() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON &&
                mBluetoothPan.get() == null) {
            adapter.getProfileProxy(mContext, mProfileServiceListener,
                    BT_PROFILE_PAN);
            if (DEBUG) log("Service listener registered");
        }
    }

    private void unregisterServiceListener() {
        mBluetoothEnableForTether = false;
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter != null && mBluetoothPan.get() != null) {
            adapter.closeProfileProxy(BT_PROFILE_PAN, (BluetoothProfile) mBluetoothPan.get());
            mBluetoothPan.set(null);
            if (DEBUG) log("Service listener unregistered");
        }
    }

    private boolean isTetheringOn() {
        try {
            Object pan = mBluetoothPan.get();
            return (pan != null &&
                    (boolean) XposedHelpers.callMethod(pan, "isTetheringOn"));
        } catch (Throwable t) {
            XposedBridge.log(t);
            return false;
        }
    }

    private boolean isInErrorState(int btState) {
        if (btState == BluetoothAdapter.ERROR)
            return true;
        for (String s : getTetheringErroredIfaces()) {
            for (String regex : getTetherableBluetoothRegexs()) {
                if (s.matches(regex)) return true;
            }
        }
        return false;
    }

    private void setTethering(boolean enabled) {
        try {
            Object pan = mBluetoothPan.get();
            if (pan != null) {
                XposedHelpers.callMethod(pan, "setBluetoothTethering", enabled);
                if (DEBUG) log("setTethering: enabled=" + enabled);
            }
        } catch (Throwable t) {
            XposedBridge.log(t);
        }
    }

    @Override
    public void setListening(boolean listening) {
        if (listening && mEnabled) {
            registerListeners();
        } else {
            unregisterListeners();
        }
    }

    @Override
    public void handleUpdateState(Object state, Object arg) {
        mState.visible = true;
        mState.booleanValue = false;

        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();

        int btState = adapter == null ? BluetoothAdapter.ERROR : adapter.getState();
        if (isInErrorState(btState)) {
            mState.label = mGbContext.getString(R.string.qs_tile_bt_tethering_error);
            mState.icon = mGbContext.getDrawable(R.drawable.ic_qs_bt_tethering_off);
        } else if (btState == BluetoothAdapter.STATE_TURNING_ON ||
                btState == BluetoothAdapter.STATE_TURNING_OFF) {
            mState.label = "---";
            mState.icon = mGbContext.getDrawable(R.drawable.ic_qs_bt_tethering_off);
        } else if (btState == BluetoothAdapter.STATE_ON && isTetheringOn()) {
            mState.label = mGbContext.getString(R.string.qs_tile_bt_tethering_on);
            mState.icon = mGbContext.getDrawable(R.drawable.ic_qs_bt_tethering_on);
            mState.booleanValue = true;
        } else {
            mState.label = mGbContext.getString(R.string.qs_tile_bt_tethering_off);
            mState.icon = mGbContext.getDrawable(R.drawable.ic_qs_bt_tethering_off);
        }

        super.handleUpdateState(state, arg);
    }

    @Override
    public void handleClick() {
        if (mBluetoothEnableForTether)
            return;

        if (isTetheringOn()) {
            setTethering(false);
        } else {
            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
            if (adapter != null) {
                if (adapter.getState() == BluetoothAdapter.STATE_OFF) {
                    mBluetoothEnableForTether = true;
                    adapter.enable();
                } else if (adapter.getState() == BluetoothAdapter.STATE_ON) {
                    setTethering(true);
                }
            }
        }
        refreshState();
    }

    @Override
    public boolean handleLongClick() {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
        startSettingsActivity(intent);
        return true;
    }

    @Override
    public void handleDestroy() {
        mTetherableBluetoothRegexs = null;
        mBluetoothPan = null;
        super.handleDestroy();
    }
}