/*
 * Copyright 2014 http://Bither.net
 *
 * 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 net.bither.service;

import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;

import net.bither.BitherSetting;
import net.bither.NotificationAndroidImpl;
import net.bither.R;
import net.bither.bitherj.AbstractApp;
import net.bither.bitherj.BitherjSettings;
import net.bither.bitherj.core.AddressManager;
import net.bither.bitherj.core.Block;
import net.bither.bitherj.core.PeerManager;
import net.bither.bitherj.exception.BlockStoreException;
import net.bither.bitherj.utils.BlockUtil;
import net.bither.bitherj.utils.TransactionsUtil;
import net.bither.preference.AppSharedPreference;
import net.bither.runnable.DownloadSpvRunnable;
import net.bither.util.BitherTimer;
import net.bither.util.BroadcastUtil;
import net.bither.util.LogUtil;
import net.bither.util.NetworkUtil;
import net.bither.util.NetworkUtil.NetworkType;
import net.bither.util.UpgradeUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;


public class BlockchainService extends android.app.Service {

    public static final String ACTION_BEGIN_DOWLOAD_SPV_BLOCK = R.class
            .getPackage().getName() + ".dowload_block_api_begin";
    private static final Logger log = LoggerFactory
            .getLogger(BlockchainService.class);
    private WakeLock wakeLock;
    private long serviceCreatedAt;
    private BitherTimer mBitherTimer;
    private SPVFinishedReceiver spvFinishedReceiver = null;
    private TickReceiver tickReceiver = null;
    private TxReceiver txReceiver = null;

    private boolean connectivityReceivered = false;

    private final IBinder mBinder = new LocalBinder(BlockchainService.this);

    private boolean peerCanNotRun = false;

    @Override
    public void onCreate() {
        serviceCreatedAt = System.currentTimeMillis();
        log.info(".onCreate()");
        super.onCreate();
        final String lockName = getPackageName() + " blockchain sync";
        final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
        if (AppSharedPreference.getInstance().getAppMode() != BitherjSettings.AppMode.COLD) {
            tickReceiver = new TickReceiver(BlockchainService.this);
            txReceiver = new TxReceiver(BlockchainService.this, tickReceiver);
            receiverConnectivity();
            registerReceiver(tickReceiver, new IntentFilter(
                    Intent.ACTION_TIME_TICK));
            registerReceiver(txReceiver, new IntentFilter(NotificationAndroidImpl.ACTION_ADDRESS_BALANCE));
            BroadcastUtil.sendBroadcastStartPeer();
        }
        startMarkTimerTask();
    }

    private void receiverConnectivity() {
        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
        intentFilter
                .addAction(BroadcastUtil.ACTION_START_PEER_MANAGER);
        registerReceiver(connectivityReceiver, intentFilter);
        connectivityReceivered = true;

    }

    private void scheduleStartBlockchainService(@Nonnull final Context context) {
        BitherSetting.SyncInterval syncInterval = AppSharedPreference.getInstance().getSyncInterval();
        if (syncInterval == BitherSetting.SyncInterval.OnlyOpenApp ||
                AddressManager.getInstance().getAllAddresses().size() == 0) {
            return;
        }
        long interval = AlarmManager.INTERVAL_HOUR;
        if (syncInterval == BitherSetting.SyncInterval.Normal) {
            final long lastUsedAgo = AppSharedPreference.getInstance().getLastUsedAgo();
            if (lastUsedAgo < BitherSetting.LAST_USAGE_THRESHOLD_JUST_MS) {
                interval = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
                log.info("start INTERVAL_FIFTEEN_MINUTES");
            } else if (lastUsedAgo < BitherSetting.LAST_USAGE_THRESHOLD_RECENTLY_MS) {
                interval = AlarmManager.INTERVAL_HALF_DAY;
                log.info("start INTERVAL_HALF_DAY");
            } else {
                interval = AlarmManager.INTERVAL_DAY;
                log.info("start INTERVAL_DAY");
            }
        }
        final AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context
                .ALARM_SERVICE);
        final PendingIntent alarmIntent = PendingIntent.getService(context, 0,
                new Intent(context, BlockchainService.class), 0);
        alarmManager.cancel(alarmIntent);
        final long now = System.currentTimeMillis();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
        // as of KitKat, set() is inexact
        {
            alarmManager.set(AlarmManager.RTC_WAKEUP, now + interval, alarmIntent);
        } else
        // workaround for no inexact set() before KitKat
        {
            alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, now + interval,
                    AlarmManager.INTERVAL_HOUR, alarmIntent);
        }
    }

    @Override
    public void onDestroy() {
        log.info(".onDestroy()");
        if (AppSharedPreference.getInstance().getAppMode() != BitherjSettings.AppMode.COLD) {
            scheduleStartBlockchainService(this);
            PeerManager.instance().stop();
            PeerManager.instance().onDestroy();
            if (mBitherTimer != null) {
                mBitherTimer.stopTimer();
                mBitherTimer = null;
            }
            if (wakeLock != null && wakeLock.isHeld()) {
                wakeLock.release();
                wakeLock = null;
            }
            if (connectivityReceivered) {
                unregisterReceiver(connectivityReceiver);
                connectivityReceivered = false;
            }
            if (tickReceiver != null) {
                unregisterReceiver(tickReceiver);
            }
            if (txReceiver != null) {
                unregisterReceiver(txReceiver);
            }
            BroadcastUtil.removeMarketState();
        }
        super.onDestroy();

        log.info("service was up for "
                + ((System.currentTimeMillis() - serviceCreatedAt) / 1000 / 60)
                + " minutes");
    }

    @Override
    public void onLowMemory() {
        log.warn("low memory detected, stopping service");
        stopSelf();
    }

    @Override
    public int onStartCommand(final Intent intent, final int flags,
                              final int startId) {
        if (intent == null) {
            return START_NOT_STICKY;
        }
        final String action = intent.getAction();
        if (action != null) {
            LogUtil.i("onStartCommand", "onStartCommand Service:" + action);
        }
        if (ACTION_BEGIN_DOWLOAD_SPV_BLOCK.equals(action)) {
            new Thread(new DownloadSpvRunnable()).start();
        }
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(final Intent intent) {
        log.debug(".onBind()");
        return mBinder;
    }

    @Override
    public boolean onUnbind(final Intent intent) {
        log.debug(".onUnbind()");

        return super.onUnbind(intent);
    }

    private final BroadcastReceiver connectivityReceiver = new BroadcastReceiver() {
        private boolean hasConnectivity;
        private boolean hasStorage = true;

        @Override
        public void onReceive(final Context context, final Intent intent) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        onReceive(intent);
                    } catch (BlockStoreException e) {
                        e.printStackTrace();
                    }

                }
            }).start();

        }

        private void onReceive(final Intent intent) throws BlockStoreException {
            final String action = intent.getAction();
            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
                hasConnectivity = !intent.getBooleanExtra(
                        ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
                log.info("network is " + (hasConnectivity ? "up" : "down"));
                check();
            } else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
                hasStorage = false;
                log.info("device storage low");

                check();
            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
                hasStorage = true;
                log.info("device storage ok");

                check();
            } else if (BroadcastUtil.ACTION_START_PEER_MANAGER
                    .equals(action)) {
                hasStorage = true;
                check();
            }
        }

        @SuppressLint("Wakelock")
        private void check() throws BlockStoreException {
            BitherjSettings.AppMode mode = AppSharedPreference.getInstance().getAppMode();
            if (mode == BitherjSettings.AppMode.COLD) {
                return;
            }
            final boolean hasEverything = hasConnectivity && hasStorage;
            NetworkType networkType = NetworkUtil.isConnectedType();
            boolean networkIsAvailadble = (!AppSharedPreference.getInstance().getSyncBlockOnlyWifi())
                    || (networkType == NetworkType.Wifi);

            if (networkIsAvailadble) {
                if (hasEverything) {
                    log.debug("acquiring wakelock");
                    callWekelock();
                    if (!PeerManager.instance().isRunning()) {
                        startPeer();
                    }
                } else {

                    PeerManager.instance().stop();
                }
            } else {

                PeerManager.instance().stop();
            }


        }
    };

    public void stopAndUnregister() {
        peerCanNotRun = true;
        if (connectivityReceivered) {
            unregisterReceiver(connectivityReceiver);
            connectivityReceivered = false;
        }
        PeerManager.instance().stop();
    }

    public void startAndRegister() {
        peerCanNotRun = false;
        receiverConnectivity();
        new Thread(new Runnable() {
            @Override
            public void run() {
                startPeer();
            }
        }).start();

    }

    private void callWekelock() {
        if ((wakeLock != null) && // we have a WakeLock
                !wakeLock.isHeld()) { // but we don't hold it
            try {
                // WakeLock.acquireLocked(PowerManager.java:329) sdk16 nullpoint
                wakeLock.acquire();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

    private boolean spvFinishedReceivered = false;

    private synchronized void startPeer() {
        try {
            if (peerCanNotRun) {
                return;
            }
            if (UpgradeUtil.needUpgrade()) {
                return;
            }
            if (!AppSharedPreference.getInstance().getDownloadSpvFinish()) {
                Block block = BlockUtil.dowloadSpvBlock();
                if (block == null) {
                    return;
                }
            }
            if (AppSharedPreference.getInstance().getAppMode() != BitherjSettings.AppMode.COLD) {
                if (!AppSharedPreference.getInstance().getBitherjDoneSyncFromSpv()) {
                    if (!PeerManager.instance().isConnected()) {
                        PeerManager.instance().start();
                        if (!spvFinishedReceivered) {
                            final IntentFilter intentFilter = new IntentFilter();
                            intentFilter.addAction(NotificationAndroidImpl.ACTION_SYNC_FROM_SPV_FINISHED);
                            spvFinishedReceiver = new SPVFinishedReceiver();
                            registerReceiver(spvFinishedReceiver, intentFilter);
                            spvFinishedReceivered = true;
                        }
                    }
                } else {
                    if (!AddressManager.getInstance().addressIsSyncComplete()) {
                        TransactionsUtil.getMyTxFromBither();
                    }
                    startPeerManager();

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void startPeerManager() {
        if (AddressManager.getInstance().addressIsSyncComplete()
                && AppSharedPreference.getInstance().getBitherjDoneSyncFromSpv()
                && AppSharedPreference.getInstance().getDownloadSpvFinish()) {
            NetworkType networkType = NetworkUtil.isConnectedType();
            boolean networkIsAvailadble = (!AppSharedPreference.getInstance().getSyncBlockOnlyWifi())
                    || (networkType == NetworkType.Wifi);
            if (networkIsAvailadble && !PeerManager.instance().isConnected()) {
                PeerManager.instance().start();
            }
        }

    }

    public void startMarkTimerTask() {
        if (AppSharedPreference.getInstance().getAppMode() != BitherjSettings.AppMode.COLD) {
            if (mBitherTimer == null) {
                mBitherTimer = new BitherTimer(BlockchainService.this);
                mBitherTimer.startTimer();
            }
        }
    }


    public class SPVFinishedReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            LogUtil.d("block", "sendBroadcastSyncSPVFinished onReceive");
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        if (!AddressManager.getInstance().addressIsSyncComplete()) {
                            TransactionsUtil.getMyTxFromBither();
                        }
                        startPeerManager();
                        AbstractApp.notificationService.removeBroadcastSyncSPVFinished();
                        if (spvFinishedReceiver != null && spvFinishedReceivered) {
                            unregisterReceiver(spvFinishedReceiver);
                            spvFinishedReceivered = false;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();


        }
    }


}