package com.pluscubed.logcat;

import android.app.IntentService;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;

import androidx.core.app.NotificationCompat;

import com.pluscubed.logcat.data.LogLine;
import com.pluscubed.logcat.data.SearchCriteria;
import com.pluscubed.logcat.helper.PreferenceHelper;
import com.pluscubed.logcat.helper.SaveLogHelper;
import com.pluscubed.logcat.helper.ServiceHelper;
import com.pluscubed.logcat.helper.WidgetHelper;
import com.pluscubed.logcat.reader.LogcatReader;
import com.pluscubed.logcat.reader.LogcatReaderLoader;
import com.pluscubed.logcat.ui.LogcatActivity;
import com.pluscubed.logcat.util.ArrayUtil;
import com.pluscubed.logcat.util.LogLineAdapterUtil;
import com.pluscubed.logcat.util.UtilLogger;

import java.io.IOException;
import java.util.Random;

/**
 * Reads logs.
 *
 * @author nolan
 */
public class LogcatRecordingService extends IntentService {

    public static final String URI_SCHEME = "catlog_recording_service";
    public static final String EXTRA_FILENAME = "filename";
    public static final String EXTRA_LOADER = "loader";
    public static final String EXTRA_QUERY_FILTER = "filter";
    public static final String EXTRA_LEVEL = "level";
    private static final String ACTION_STOP_RECORDING = "com.pluscubed.catlog.action.STOP_RECORDING";
    private static UtilLogger log = new UtilLogger(LogcatRecordingService.class);
    private final Object lock = new Object();
    private LogcatReader mReader;
    private boolean mKilled;
    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            log.d("onReceive()");

            // received broadcast to kill service
            killProcess();
            ServiceHelper.stopBackgroundServiceIfRunning(context);
        }
    };

    private Handler handler;


    public LogcatRecordingService() {
        super("AppTrackerService");
    }


    @Override
    public void onCreate() {
        super.onCreate();
        log.d("onCreate()");

        IntentFilter intentFilter = new IntentFilter(ACTION_STOP_RECORDING);
        intentFilter.addDataScheme(URI_SCHEME);

        registerReceiver(receiver, intentFilter);

        handler = new Handler(Looper.getMainLooper());
    }


    private void initializeReader(Intent intent) {
        try {
            // use the "time" log so we can see what time the logs were logged at
            LogcatReaderLoader loader = intent.getParcelableExtra(EXTRA_LOADER);
            mReader = loader.loadReader();

            while (mReader != null && !mReader.readyToRecord() && !mKilled) {
                mReader.readLine();
                // keep skipping lines until we find one that is past the last log line, i.e.
                // it's ready to record
            }
            if (!mKilled) {
                makeToast(R.string.log_recording_started, Toast.LENGTH_SHORT);
            }
        } catch (IOException e) {
            log.d(e, "");
        }

    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        log.d("onDestroy()");
        killProcess();

        unregisterReceiver(receiver);

        stopForeground(true);

        WidgetHelper.updateWidgets(getApplicationContext(), false);
    }

    // This is the old onStart method that will be called on the pre-2.0
    // platform.
    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        log.d("onStart()");
        handleCommand();
    }

    private void handleCommand() {

        // notify the widgets that we're running
        WidgetHelper.updateWidgets(getApplicationContext());

        CharSequence tickerText = getText(R.string.notification_ticker);

        Intent stopRecordingIntent = new Intent();
        stopRecordingIntent.setAction(ACTION_STOP_RECORDING);
        // have to make this unique for God knows what reason
        stopRecordingIntent.setData(Uri.withAppendedPath(Uri.parse(URI_SCHEME + "://stop/"),
                Long.toHexString(new Random().nextLong())));

        PendingIntent pendingIntent = PendingIntent.getBroadcast(this,
                0 /* no requestCode */, stopRecordingIntent, PendingIntent.FLAG_ONE_SHOT);

        final String CHANNEL_ID = "matlog_logging_channel";
        // Set the icon, scrolling text and timestamp
        NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID);
        notification.setSmallIcon(R.drawable.notif_icon);
        notification.setTicker(tickerText);
        notification.setWhen(System.currentTimeMillis());
        notification.setContentTitle(getString(R.string.notification_title));
        notification.setContentText(getString(R.string.notification_subtext));
        notification.setContentIntent(pendingIntent);

        NotificationManager manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
        // Fix Oreo notifications showing
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            final CharSequence name = getString(R.string.app_name);
            final int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
            manager.createNotificationChannel(channel);
        }

        startForeground(R.string.notification_title, notification.build());
    }

    protected void onHandleIntent(Intent intent) {
        log.d("onHandleIntent()");
        handleIntent(intent);
    }

    private void handleIntent(Intent intent) {

        log.d("Starting up %s now with intent: %s", LogcatRecordingService.class.getSimpleName(), intent);

        String filename = intent.getStringExtra(EXTRA_FILENAME);
        String queryText = intent.getStringExtra(EXTRA_QUERY_FILTER);
        String logLevel = intent.getStringExtra(EXTRA_LEVEL);

        SearchCriteria searchCriteria = new SearchCriteria(queryText);

        CharSequence[] logLevels = getResources().getStringArray(R.array.log_levels_values);
        int logLevelLimit = ArrayUtil.indexOf(logLevels, logLevel);

        boolean searchCriteriaWillAlwaysMatch = searchCriteria.isEmpty();
        boolean logLevelAcceptsEverything = logLevelLimit == 0;

        SaveLogHelper.deleteLogIfExists(filename);

        initializeReader(intent);

        StringBuilder stringBuilder = new StringBuilder();

        try {

            String line;
            int lineCount = 0;
            int logLinePeriod = PreferenceHelper.getLogLinePeriodPreference(this);
            String filterPattern = PreferenceHelper.getFilterPatternPreference(this);
            while (mReader != null && (line = mReader.readLine()) != null && !mKilled) {

                // filter
                if (!searchCriteriaWillAlwaysMatch || !logLevelAcceptsEverything) {
                    if (!checkLogLine(line, searchCriteria, logLevelLimit, filterPattern)) {
                        continue;
                    }
                }

                stringBuilder.append(line).append("\n");

                if (++lineCount % logLinePeriod == 0) {
                    // avoid OutOfMemoryErrors; flush now
                    SaveLogHelper.saveLog(stringBuilder, filename);
                    stringBuilder.delete(0, stringBuilder.length()); // clear
                }
            }
        } catch (IOException e) {
            log.e(e, "unexpected exception");
        } finally {
            killProcess();
            log.d("CatlogService ended");

            boolean logSaved = SaveLogHelper.saveLog(stringBuilder, filename);

            if (logSaved) {
                makeToast(R.string.log_saved, Toast.LENGTH_SHORT);
                startLogcatActivityToViewSavedFile(filename);
            } else {
                makeToast(R.string.unable_to_save_log, Toast.LENGTH_LONG);
            }
        }
    }

    private boolean checkLogLine(String line, SearchCriteria searchCriteria, int logLevelLimit, String filterPattern) {
        LogLine logLine = LogLine.newLogLine(line, false, filterPattern);
        return searchCriteria.matches(logLine)
                && LogLineAdapterUtil.logLevelIsAcceptableGivenLogLevelLimit(logLine.getLogLevel(), logLevelLimit);
    }


    private void startLogcatActivityToViewSavedFile(String filename) {

        // start up the logcat activity if necessary and show the saved file

        Intent targetIntent = new Intent(getApplicationContext(), LogcatActivity.class);
        targetIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        targetIntent.setAction(Intent.ACTION_MAIN);
        targetIntent.putExtra("filename", filename);

        startActivity(targetIntent);

    }


    private void makeToast(final int stringResId, final int toastLength) {
        handler.post(Toast.makeText(LogcatRecordingService.this, stringResId, toastLength)::show);

    }

    private void killProcess() {
        if (!mKilled) {
            synchronized (lock) {
                if (!mKilled && mReader != null) {
                    // kill the logcat process
                    mReader.killQuietly();
                    mKilled = true;
                }
            }
        }
    }

}