/*
 * MIT License
 *
 * Copyright (c) 2017 Inova IT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package si.inova.neatle.scan;

import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;

import si.inova.neatle.util.NeatleLogger;

abstract class BaseScanner implements Scanner {
    private final Handler handler;


    private Runnable pauseCallback = new PauseCallback();
    private Runnable resumeCallback = new ResumeCallback();

    private boolean scanning = false;
    private boolean doStarted = false;

    private long scanDuration;
    private long scanInterval;
    private int scanMode;

    private BroadcastReceiver broadcastReceiver = new BluetoothStateReceiver();
    private Context context;


    public BaseScanner() {
        handler = new Handler();
        //get default values
        ScanMode defaults = new ScanMode();
        scanDuration = defaults.getDuration();
        scanInterval = defaults.getInterval();
        scanMode = defaults.getMode();
    }

    @Override
    public void setMode(ScanMode mode) {
        if (mode.getDuration() == scanDuration
                && mode.getInterval() == scanInterval
                && mode.getMode() == scanMode) {
            return;
        }
        boolean wasScanning = scanning;
        Context oldContext = this.context;
        stopScanning();


        scanMode = mode.getMode();
        scanInterval = mode.getInterval();
        scanDuration = mode.getDuration();

        if (wasScanning) {
            startScanning(oldContext);
        }
    }

    @Override
    public final void startScanning(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("Context cannot be null");
        }
        if (scanning) {
            NeatleLogger.i("Already scanning, ignoring start scanning request");
            return;
        }
        this.context = context;
        registerStateReciever(context);
        scanning = true;

        conditionalStart();
    }

    private void conditionalStart() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null) {
            NeatleLogger.e("Bluetooth LE scan failed to start. No bluetooth adapter found");
            return;
        }
        int adapterState = adapter.getState();
        if (adapterState != BluetoothAdapter.STATE_ON) {
            NeatleLogger.e("Bluetooth off, will start scanning when it turns on.");
            pause();
            return;
        }

        onStart(adapter, scanMode);
        doStarted = true;
        if (scanDuration > 0) {
            handler.postDelayed(pauseCallback, scanDuration);
        }
    }

    protected void resume() {
        handler.removeCallbacks(pauseCallback);
        handler.removeCallbacks(resumeCallback);
        if (!scanning) {
            NeatleLogger.e("resume called but scanning is stopped. ");
            return;
        }
        conditionalStart();
    }

    private void pause() {
        handler.removeCallbacks(pauseCallback);
        handler.removeCallbacks(resumeCallback);

        if (!scanning) {
            NeatleLogger.i("called pause, but there is no scanning in progress");
            return;
        }

        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (doStarted) {
            doStarted = false;
            onStop(adapter);
        }


        if (scanInterval > 0 && adapter.getState() == BluetoothAdapter.STATE_ON) {
            NeatleLogger.i("scanning paused, will resume in " + scanInterval + " milliseconds");
            handler.postDelayed(resumeCallback, scanInterval);
        } else {
            NeatleLogger.i("no scan interval set or bluetooth off, stopping scanning");
        }
    }



    private void registerStateReciever(Context context) {
        IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
        context.registerReceiver(broadcastReceiver, intentFilter);
    }

    private void unregisterStateReciever(Context context) {
        context.unregisterReceiver(broadcastReceiver);
    }

    protected abstract void onStart(BluetoothAdapter adapter, int scanMode);

    @Override
    public void stopScanning() {
        handler.removeCallbacks(pauseCallback);
        handler.removeCallbacks(resumeCallback);
        if (scanning) {
            unregisterStateReciever(context);
            if (doStarted) {
                doStarted = false;
                onStop(BluetoothAdapter.getDefaultAdapter());
            }

            context = null;
            scanning = false;
        }
    }

    protected abstract void onStop(BluetoothAdapter adapter);


    private class ResumeCallback implements Runnable {

        @Override
        public void run() {
            resume();
        }
    }

    private class PauseCallback implements Runnable {
        @Override
        public void run() {
            pause();
        }
    }

    private class BluetoothStateReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (!action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                return;
            }
            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
            int state = adapter.getState();
            if (state == BluetoothAdapter.STATE_ON) {
                NeatleLogger.i("BluetoothAdapter turned on");
                resume();
            } else {
                NeatleLogger.i("BluetoothAdapter state changed to " + state  +", turning off");
                pause();
            }
        }
    }
}