package com.tns;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

import android.Manifest;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.design.widget.TabLayout;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;


class ErrorReport implements TabLayout.OnTabSelectedListener {
    public static final String ERROR_FILE_NAME = "hasError";
    private static AppCompatActivity activity;

    private TabLayout tabLayout;
    private ViewPager viewPager;
    private Context context;

    private static String exceptionMsg;
    private static String logcatMsg;

    private static boolean checkingForPermissions = false;

    private final static String EXTRA_NATIVESCRIPT_ERROR_REPORT = "NativeScriptErrorMessage";
    private final static String EXTRA_ERROR_REPORT_MSG = "msg";
    private final static String EXTRA_PID = "pID";
    private final static int EXTRA_ERROR_REPORT_VALUE = 1;

    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    // Will prevent error activity from killing process if permission request dialog pops up
    public static boolean isCheckingForPermissions() {
        return checkingForPermissions;
    }

    public static void resetCheckingForPermissions() {
        checkingForPermissions = false;
    }

    // The following will not compile if uncommented with compileSdk lower than 23
    public static void verifyStoragePermissions(Activity activity) {
        // Check if we have write permission
        final int version = Build.VERSION.SDK_INT;
        if (version >= 23) {
            try {
                // Necessary to work around compile errors with compileSdk 22 and lower
                Method checkSelfPermissionMethod;
                try {
                    checkSelfPermissionMethod = ActivityCompat.class.getMethod("checkSelfPermission", Context.class, String.class);
                } catch (NoSuchMethodException e) {
                    // method wasn't found, so there is no need to handle permissions explicitly
                    e.printStackTrace();
                    return;
                }

                int permission = (int) checkSelfPermissionMethod.invoke(null, activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);

                if (permission != PackageManager.PERMISSION_GRANTED) {
                    // We don't have permission so prompt the user
                    Method requestPermissionsMethod = ActivityCompat.class.getMethod("requestPermissions", Activity.class, PERMISSIONS_STORAGE.getClass(), int.class);

                    checkingForPermissions = true;

                    requestPermissionsMethod.invoke(null, activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
                }
            } catch (Exception e) {
                Toast.makeText(activity, "Couldn't resolve permissions", Toast.LENGTH_LONG).show();
                e.printStackTrace();
                return;
            }
        }
    }

    public ErrorReport(AppCompatActivity activity) {
        ErrorReport.activity = activity;
        this.context = activity.getApplicationContext();
    }

    static boolean startActivity(final Context context, String errorMessage) {
        final Intent intent = getIntent(context);
        if (intent == null) {
            return false; // (if in release mode) don't do anything
        }

        intent.putExtra(EXTRA_ERROR_REPORT_MSG, errorMessage);

        String PID = Integer.toString(android.os.Process.myPid());
        intent.putExtra(EXTRA_PID, PID);

        createErrorFile(context);

        try {
            startPendingErrorActivity(context, intent);
        } catch (CanceledException e) {
            Log.d("ErrorReport", "Couldn't send pending intent! Exception: " + e.getMessage());
        }

        killProcess(context);

        return true;
    }

    static void killProcess(Context context) {
        // finish current activity and all below it first
        if (context instanceof Activity) {
            ((Activity) context).finishAffinity();
        }

        // kill process
        android.os.Process.killProcess(android.os.Process.myPid());
    }

    static void startPendingErrorActivity(Context context, Intent intent) throws CanceledException {
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

        pendingIntent.send(context, 0, intent);
    }

    static String getErrorMessage(Throwable ex) {
        String content;
        PrintStream ps = null;

        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ps = new PrintStream(baos);
            ex.printStackTrace(ps);

            try {
                content = baos.toString("US-ASCII");
            } catch (UnsupportedEncodingException e) {
                content = e.getMessage();
            }
        } finally {
            if (ps != null)
                ps.close();
        }

        return content;
    }

    /*
    * Gets the process Id of the running app and filters all
    * output that doesn't belong to that process
    * */
    public static String getLogcat(String pId) {
        String content;

        try {
            String logcatCommand = "logcat -d";
            Process process = java.lang.Runtime.getRuntime().exec(logcatCommand);

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

            StringBuilder log = new StringBuilder();
            String line = "";
            String lineSeparator = System.getProperty("line.separator");
            while ((line = bufferedReader.readLine()) != null) {
                if (line.contains(pId)) {
                    log.append(line);
                    log.append(lineSeparator);
                }
            }

            content = log.toString();
        } catch (IOException e) {
            content = "Failed to read logcat";
            Log.e("TNS.Android", content);
        }

        return content;
    }

    static Intent getIntent(Context context) {
        Class<?> errorActivityClass;

        if (AndroidJsDebugger.isDebuggableApp(context)) {
            errorActivityClass = ErrorReportActivity.class;
        } else {
            return null;
        }

        Intent intent = new Intent(context, errorActivityClass);

        intent.putExtra(EXTRA_NATIVESCRIPT_ERROR_REPORT, EXTRA_ERROR_REPORT_VALUE);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);

        return intent;
    }

    static boolean hasIntent(Intent intent) {
        int value = intent.getIntExtra(EXTRA_NATIVESCRIPT_ERROR_REPORT, 0);

        return value == EXTRA_ERROR_REPORT_VALUE;
    }

    void buildUI() {
        Intent intent = activity.getIntent();

        exceptionMsg = intent.getStringExtra(EXTRA_ERROR_REPORT_MSG);

        String processId = intent.getStringExtra(EXTRA_PID);
        logcatMsg = getLogcat(processId);

        int errActivityId = this.context.getResources().getIdentifier("error_activity", "layout", this.context.getPackageName());

        activity.setContentView(errActivityId);

        int toolBarId = this.context.getResources().getIdentifier("toolbar", "id", this.context.getPackageName());

        Toolbar toolbar = (Toolbar) activity.findViewById(toolBarId);
        activity.setSupportActionBar(toolbar);

        final int tabLayoutId = this.context.getResources().getIdentifier("tabLayout", "id", this.context.getPackageName());

        tabLayout = (TabLayout) activity.findViewById(tabLayoutId);
        tabLayout.addTab(tabLayout.newTab().setText("Exception"));
        tabLayout.addTab(tabLayout.newTab().setText("Logcat"));
        tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
        int pagerId = this.context.getResources().getIdentifier("pager", "id", this.context.getPackageName());

        viewPager = (ViewPager) activity.findViewById(pagerId);

        Pager adapter = new Pager(activity.getSupportFragmentManager(), tabLayout.getTabCount());

        viewPager.setAdapter(adapter);
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                tabLayout.getTabAt(position).select();
                viewPager.setCurrentItem(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });

        tabLayout.setOnTabSelectedListener(this);
    }

    private static void createErrorFile(final Context context) {
        try {
            File errFile = new File(context.getFilesDir(), ERROR_FILE_NAME);
            errFile.createNewFile();
        } catch (IOException e) {
            Log.d("ErrorReport", e.getMessage());
        }
    }

    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        viewPager.setCurrentItem(tab.getPosition());
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {

    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {
        viewPager.setCurrentItem(tab.getPosition());
    }

    private class Pager extends FragmentStatePagerAdapter {

        int tabCount;

        public Pager(FragmentManager fm, int tabCount) {
            super(fm);
            this.tabCount = tabCount;
        }

        @Override
        public Fragment getItem(int position) {
            switch (position) {
                case 0:
                    return new ExceptionTab();
                case 1:
                    return new LogcatTab();
                default:
                    return null;
            }
        }

        @Override
        public int getCount() {
            return tabCount;
        }
    }

    public static class ExceptionTab extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            int exceptionTabId = container.getContext().getResources().getIdentifier("exception_tab", "layout", container.getContext().getPackageName());
            View view = inflater.inflate(exceptionTabId, container, false);

            int txtViewId = container.getContext().getResources().getIdentifier("txtErrorMsg", "id", container.getContext().getPackageName());
            TextView txtErrorMsg = (TextView) view.findViewById(txtViewId);
            txtErrorMsg.setText(exceptionMsg);
            txtErrorMsg.setMovementMethod(new ScrollingMovementMethod());

            int btnCopyExceptionId = container.getContext().getResources().getIdentifier("btnCopyException", "id", container.getContext().getPackageName());
            Button copyToClipboard = (Button) view.findViewById(btnCopyExceptionId);
            copyToClipboard.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
                    ClipData clip = ClipData.newPlainText("nsError", exceptionMsg);
                    clipboard.setPrimaryClip(clip);
                }
            });

            return view;
        }
    }

    public static class LogcatTab extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            int logcatTabId = container.getContext().getResources().getIdentifier("logcat_tab", "layout", container.getContext().getPackageName());
            View view = inflater.inflate(logcatTabId, container, false);

            int textViewId = container.getContext().getResources().getIdentifier("logcatMsg", "id", container.getContext().getPackageName());
            TextView txtlogcatMsg = (TextView) view.findViewById(textViewId);
            txtlogcatMsg.setText(logcatMsg);

            txtlogcatMsg.setMovementMethod(new ScrollingMovementMethod());

            final String logName = "Log-" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());

            int btnCopyLogcatId = container.getContext().getResources().getIdentifier("btnCopyLogcat", "id", container.getContext().getPackageName());
            Button copyToClipboard = (Button) view.findViewById(btnCopyLogcatId);
            copyToClipboard.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    verifyStoragePermissions(activity);

                    if (!isCheckingForPermissions()) {
                        try {
                            File dir = new File(Environment.getExternalStorageDirectory().getPath() + "/logcat-reports/");
                            dir.mkdirs();

                            File logcatReportFile = new File(dir, logName);
                            FileOutputStream stream = new FileOutputStream(logcatReportFile);
                            OutputStreamWriter writer = new OutputStreamWriter(stream, "UTF-8");
                            writer.write(logcatMsg);
                            writer.close();

                            String logPath = dir.getPath() + "/" + logName;

                            ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
                            ClipData clip = ClipData.newPlainText("logPath", logPath);
                            clipboard.setPrimaryClip(clip);

                            Toast.makeText(activity, "Path copied to clipboard: " + logPath, Toast.LENGTH_LONG).show();
                        } catch (Exception e) {
                            String err = "Could not write logcat report to sdcard. Make sure you have allowed access to external storage!";
                            Toast.makeText(activity, err, Toast.LENGTH_LONG).show();
                            e.printStackTrace();
                        }
                    }
                }
            });

            return view;
        }
    }
}