/* Android IMSI-Catcher Detector | (c) AIMSICD Privacy Project
 * -----------------------------------------------------------
 * LICENSE:  http://git.io/vki47 | TERMS:  http://git.io/vki4o
 * -----------------------------------------------------------
 */
package zz.aimsicd.lite.ui.activities;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import io.freefair.android.injection.annotation.InjectView;
import io.freefair.android.injection.annotation.XmlLayout;
import io.freefair.android.injection.annotation.XmlMenu;
import zz.aimsicd.lite.R;
import zz.aimsicd.lite.utils.Helpers;


/**
 *  Description:    This class is providing for the Debug log feature in the swipe menu.
 *                  It reads the last 500 lines from the Logcat ring buffers: main and radio.
 *
 *  Dependencies:
 *                  menu/activity_debug_logs.xml **
 *                  layout/activity_debug_logs.xml
 *                  values/strings.xml
 *  Issues:
 *
 *          [ ]     Are we clearing logcat when starting it? If we are, we miss all previous errors
 *                  if any has occurred. We need to clear the logcat when app starts. Also there
 *                  is no reason to clear it if we only catch the last 500 lines anyway.
 *
 *          [ ]     Add the output of "getprop |sort". But Java CLI processes doesn't handle pipes.
 *                  Try with:  Collections.sort(list, String.CASE_INSENSITIVE_ORDER)
 *
 *          [ ]     Apparently the button for radio log has been added to the strings.xml,
 *                  but never implemented here. We need to add the buffer selector button
 *                  to the top bar, next to email icon button. **
 *
 *  TODO:   [ ]     We should add an XPrivacy button (or automatic) to add XPrivacy filters when used.
 */
@XmlLayout(R.layout.activity_debug_logs)
@XmlMenu(R.menu.activity_debug_logs)
public class DebugLogs extends BaseActivity {

    public static final String TAG = "AICDL";
    public static final String mTAG = "XXX";

    private LogUpdaterThread logUpdater = null;
    private boolean updateLogs = true;
    private boolean isRadioLogs = true; // Including this, should be a toggle.

    @InjectView(R.id.debug_log_view)
    private TextView logView;

    @InjectView(R.id.btnClear)
    private Button btnClear;

    @InjectView(R.id.btnCopy)
    private Button btnCopy;

    @InjectView(zz.aimsicd.lite.R.id.btnStopLogs)
    private Button btnStop;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Show the Up button in the action bar.
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                logView.setFocusable(false);
            }
        });

        btnClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    clearLogs();
                } catch (IOException e) {
                   Log.e(TAG, mTAG + "Error clearing logs", e);
                }
            }
        });

        btnCopy.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
                ClipData cd = ClipData.newPlainText("log", logView.getText());
                clipboard.setPrimaryClip(cd);
                Helpers.msgShort(DebugLogs.this, getString(R.string.msg_copied_to_clipboard));
            }
        });

        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (updateLogs) {
                    updateLogs = false;
                    btnStop.setText(getString(R.string.btn_start_logs));
                } else {
                    startLogging();
                }
            }
        });

        /*
        // logcat radio buffer toggle on/off
        btnRadio.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (isRadioLogs) {
                    isRadioLogs = false;
                    btnRadio.setText(getString(R.string.btn_radio_logs));
                } else {
                    isRadioLogs = true;
                    btnRadio.setText(getString(R.string.btn_main_logs));
                }
            }
        });
        */

    }

    @Override
    protected void onPause() {
        updateLogs = false; // exit the log updater
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        startLogging();
    }

    private void startLogging() {
        updateLogs = true;

        logUpdater = new LogUpdaterThread();
        logUpdater.start();

        btnStop.setText(getString(R.string.btn_stop_logs));
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case zz.aimsicd.lite.R.id.action_send_logs:
                sendEmail();
                return true;

            case android.R.id.home:
                // This ID represents the Home or Up button. In the case of this
                // activity, the Up button is shown. Use NavUtils to allow users
                // to navigate up one level in the application structure. For
                // more details, see the Navigation pattern on Android Design:
                //
                // http://developer.android.com/design/patterns/navigation.html#up-vs-back
                //
                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    public void sendEmail() {
        new Thread() {
            @Override
            public void run() {
                // Send Error Log
                try {
                    String helpUs = getString(R.string.describe_the_problem_you_had);
                    String log = helpUs + "\n\n" + "GETPROP:" + "\n\n" + getProp() +
                                          "\n\n" + "LOGCAT:" + "\n\n" + getLogs() + "\n\n" + helpUs;

                    // show a share intent
                    Intent intent = new Intent(Intent.ACTION_SEND);
                    intent.setType("text/html");
                    // E-Mail address will ONLY be handed out when a DEVELOPER asked for the logs!
                    intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"See GitHub Issues first!"});
                    intent.putExtra(Intent.EXTRA_SUBJECT, "AIMSICD Error Log");
                    intent.putExtra(Intent.EXTRA_TEXT, log);
                    startActivity(Intent.createChooser(intent, "Send Error Log"));
                } catch (IOException e) {
                   Log.w(TAG, mTAG + "Error reading logs", e);
                }
            }
        }.start();
    }

    /**
     * Read getprop and return the sorted result as a string
     *
     * TODO: Need a way to sort properties for easy reading
     *
     * @return
     * @throws IOException
     */
    public String getProp() throws IOException {
        return runProcess("/system/bin/getprop");
    }

    /**
     *  Description:    Read logcat and return as a string
     *
     *  Notes:
     *
     *  1) " *:V" makes log very spammy due to verbose OemRilRequestRaw debug output (AIMSICD_Helpers).
     *          ==> Now disabled!
     *  2) Need to silent some spammy Samsung Galaxy's:     " AbsListView:S PackageInfo:S"
     *  3) Need to silent some Qualcomm QMI:                " RILQ:S"
     *  4) Need to silent some Qualcomm GPS:                " LocSvc_eng:S LocSvc_adapter:S LocSvc_afw:S"
     *  5) "-d" is not necessary when using "-t".
     *  6) Need to silent some spammy HTC's:                "QC-QMI:S AudioPolicyManager:S"
     *  7) Need to silent some spammy XPrivacy items:       "XPrivacy/XRuntime:S Xposed:S"
     *  8) Need to silent even more XPrivacy items:         "XPrivacy/XTelephonyManager:S XPrivacy/XLocationManager:S XPrivacy/XPackageManager:S"
     *
     */
    private String getLogs() throws IOException {
        return runProcess(
            "logcat -t 500 -v brief -b main" +
                    (isRadioLogs ? " -b radio RILQ:S" : "") +
                    " AbsListView:S PackageInfo:S" +
                    " LocSvc_eng:S LocSvc_adapter:S LocSvc_afw:S" +
                    " QC-QMI:S AudioPolicyManager:S" +
                    " XPrivacy/XRuntime:S Xposed:S" +
                    " XPrivacy/XTelephonyManager:S XPrivacy/XLocationManager:S XPrivacy/XPackageManager:S" + " *:D"
        );
    }

    /**
     * Run a shell command and return the results
     */
    private String runProcess(String command) throws IOException {
        return runProcess(new String[]{command});
    }

    /**
     * Run a shell command and return the results
     *
     * @param command
     * @return
     * @throws IOException
     */
    private String runProcess(String[] command) throws IOException {
        Process process = null;
        if (command.length == 1) {
            process = Runtime.getRuntime().exec(command[0]);
        } else {
            Runtime.getRuntime().exec(command);
        }

        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(process.getInputStream()));

        StringBuilder log = new StringBuilder();
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            log.append(line);
            log.append("\n");
        }
        bufferedReader.close();
        return log.toString();
    }

    /**
     * Clear logcat
     * @return
     * @throws IOException
     */
    private void clearLogs() throws IOException {
        new Thread() {
            @Override
            public void run() {
                try {
                    Runtime.getRuntime().exec("logcat -c -b main -b system -b radio -b events");
                } catch (IOException e) {
                   Log.e(TAG, mTAG + "Error clearing logs", e);
                }

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        logView.setText("");
                    }
                });
            }
        }.start();
    }

    class LogUpdaterThread extends Thread {
        @Override
        public void run() {
            while (updateLogs) {
                try {
                    final String logs = getLogs();
                    if (!logs.equals(logView.getText().toString())) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                // update log display
                                logView.setText(logs);

                                // scroll to the bottom of the log display
                                final ScrollView scroll = ((ScrollView) logView.getParent());
                                scroll.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        scroll.fullScroll(View.FOCUS_DOWN);
                                    }
                                });
                            }
                        });
                    }
                } catch (IOException e) {
                   Log.w(TAG, mTAG + "Error updating logs", e);
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                   Log.w(TAG, mTAG + "Thread was interrupted", e);
                }
            }
        }
    }
}