package com.avg.zaap.crashlibrary.provider.stacktrace;

import android.support.annotation.NonNull;

import com.avg.zaap.crashlibrary.core.logger.CrashLogger;
import com.avg.zaap.crashlibrary.core.writer.DataWriterException;
import com.avg.zaap.crashlibrary.core.writer.IDataWriter;

import java.io.File;
import java.util.Map;

/**
 * Provides stack trace of crashing thread and stack traces of all threads.
 * <p>
 * Created by marek.osvald on 22. 4. 2016.
 */
public class StackTraceWriter implements IStackTraceWriter {

    private static final String TAG = StackTraceWriter.class.getSimpleName();
    private static final String FILE_NAME_CRASHING = "StackTraceCrashingThread.txt";
    private static final String FILE_NAME_ALL = "StackTracesAllThreads.txt";

    private final IDataWriter mDataWriter;

    /**
     * Parametric constructor. Sets dependency factory to be used.
     *
     * @param factory dependency factory to be used
     */
    public StackTraceWriter(IStackTraceWriterFactory factory) {
        mDataWriter = factory.getDataWriter();
    }

    /**
     * Default constructor. Uses default dependency factory.
     */
    public StackTraceWriter() {
        this(new StackTraceWriterFactory());
    }

    @Override
    public String writeCrashingThreadStackTrace(@NonNull String directory, @NonNull Throwable throwable) throws StackTraceWriterException {
        final String path = directory + File.separatorChar + FILE_NAME_CRASHING;

        String stackTrace = getStackTrace(throwable);

        try {
            mDataWriter.write(path, stackTrace);
            return path;
        } catch (DataWriterException ex) {
            CrashLogger.e(TAG, "Unable to write crashing stack trace to file: %s", ex.getMessage());
            throw new StackTraceWriterException("Unable to write stack trace to file.", ex);
        }
    }

    @Override
    public String writeAllStackTraces(@NonNull String directory) throws StackTraceWriterException {
        final String path = directory + File.separatorChar + FILE_NAME_ALL;

        String stackTraces = getStackTraces();

        try {
            mDataWriter.write(path, stackTraces);
            return path;
        } catch (DataWriterException ex) {
            CrashLogger.e(TAG, "Unable to write stack traces to file: %s", ex.getMessage());
            throw new StackTraceWriterException("Unable to write stack traces to file.", ex);
        }
    }

    /**
     * Get stack trace of throwable
     *
     * @param throwable throwable to get the stack trace for
     * @return stack trace of the given throwable
     */
    private String getStackTrace(@NonNull Throwable throwable) {
        StringBuilder stackTrace = new StringBuilder();

        StackTraceElement[] stackTraceElements = throwable.getStackTrace();

        if (stackTraceElements != null) {
            for (StackTraceElement element : stackTraceElements) {
                stackTrace.append(element.toString());
                stackTrace.append('\n');
            }
        }

        return stackTrace.toString();
    }

    /**
     * Get stack traces of all threads.
     *
     * @return stack traces
     */
    private String getStackTraces() {
        Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();

        StringBuilder stackTracesOutput = new StringBuilder();
        for (Map.Entry<Thread, StackTraceElement[]> entry : stackTraces.entrySet()) {
            stackTracesOutput.append(entry.getKey());
            stackTracesOutput.append('\n');
            for (StackTraceElement element : entry.getValue()) {
                stackTracesOutput.append("\t\t");
                stackTracesOutput.append(element.getFileName());
                stackTracesOutput.append('(');
                stackTracesOutput.append(element.getLineNumber());
                stackTracesOutput.append("): ");
                stackTracesOutput.append(element.getClassName());
                stackTracesOutput.append(": ");
                stackTracesOutput.append(element.getMethodName());
                stackTracesOutput.append('\n');
            }
        }

        return stackTracesOutput.toString();
    }

}
