/*
 * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license.
 * See LICENSE in the project root for license information.
 */
package com.microsoft.office365.msgraphsnippetapp;

import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.text.ClipboardManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.microsoft.office365.msgraphsnippetapp.snippet.AbstractSnippet;
import com.microsoft.office365.msgraphsnippetapp.snippet.SnippetContent;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import butterknife.ButterKnife;
import butterknife.BindView;
import butterknife.OnClick;
import okhttp3.Headers;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import timber.log.Timber;

import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.microsoft.office365.msgraphsnippetapp.R.color.code_1xx;
import static com.microsoft.office365.msgraphsnippetapp.R.color.code_3xx;
import static com.microsoft.office365.msgraphsnippetapp.R.color.code_4xx;
import static com.microsoft.office365.msgraphsnippetapp.R.color.transparent;
import static com.microsoft.office365.msgraphsnippetapp.R.id.btn_run;
import static com.microsoft.office365.msgraphsnippetapp.R.id.progressbar;
import static com.microsoft.office365.msgraphsnippetapp.R.id.txt_desc;
import static com.microsoft.office365.msgraphsnippetapp.R.id.txt_hyperlink;
import static com.microsoft.office365.msgraphsnippetapp.R.id.txt_request_url;
import static com.microsoft.office365.msgraphsnippetapp.R.id.txt_response_body;
import static com.microsoft.office365.msgraphsnippetapp.R.id.txt_response_headers;
import static com.microsoft.office365.msgraphsnippetapp.R.id.txt_status_code;
import static com.microsoft.office365.msgraphsnippetapp.R.id.txt_status_color;
import static com.microsoft.office365.msgraphsnippetapp.R.string.clippy;
import static com.microsoft.office365.msgraphsnippetapp.R.string.req_url;
import static com.microsoft.office365.msgraphsnippetapp.R.string.response_body;
import static com.microsoft.office365.msgraphsnippetapp.R.string.response_headers;

public class SnippetDetailFragment<T, Result>
        extends BaseFragment implements Callback<Result> {

    public static final String ARG_ITEM_ID = "item_id";

    private static final int UNSET = -1;
    private static final String STATUS_COLOR = "STATUS_COLOR";

    private AbstractSnippet<T, Result> mItem;

    //
    // UI component bindings
    //

    /**
     * Displays the status code of the service call
     */
    @BindView(txt_status_code)
    protected TextView mStatusCode;

    /**
     * Displays the status code as color 'stoplight'
     */
    @BindView(txt_status_color)
    protected View mStatusColor;

    /**
     * On-screen description of the current snippet
     */
    @BindView(txt_desc)
    protected TextView mSnippetDescription;

    /**
     * The request url of the current snippet
     */
    @BindView(txt_request_url)
    protected TextView mRequestUrl;

    /**
     * The response headers of the current snippet's request
     */
    @BindView(txt_response_headers)
    protected TextView mResponseHeaders;

    /**
     * The response body of the snippet's request
     */
    @BindView(txt_response_body)
    protected TextView mResponseBody;

    /**
     * Barber's pole progress bar (indeterminate)
     */
    @BindView(progressbar)
    protected ProgressBar mProgressbar;

    /**
     * The 'run-snippet' button
     */
    @BindView(btn_run)
    protected Button mRunButton;

    /**
     * Fragment default constructor
     */
    public SnippetDetailFragment() {
        // unimplemented
    }

    //
    // UI event bindings
    //
    @OnClick(txt_request_url)
    public void onRequestUrlClicked(TextView tv) {
        // copy to clip
        clipboard(tv);
    }

    @OnClick(txt_response_headers)
    public void onResponseHeadersClicked(TextView tv) {
        // copy to clip
        clipboard(tv);
    }

    @OnClick(txt_response_body)
    public void onResponseBodyClicked(TextView tv) {
        // copy to clip
        clipboard(tv);
    }

    @OnClick(btn_run)
    public void onRunClicked(Button btn) {
        // disable the button while the snippet is running
        mRunButton.setEnabled(false);

        // clear the old request url
        mRequestUrl.setText("");

        // clear any old headers
        mResponseHeaders.setText("");

        // clear any old response body
        mResponseBody.setText("");

        // reset the status 'stoplight'
        displayStatusCode("",
                getResources()
                        .getColor(transparent)
        );

        // show the indeterminate spinner
        mProgressbar.setVisibility(VISIBLE);

        // actually make the request
        mItem.request(mItem.mService, this);
    }

    @OnClick(txt_hyperlink)
    public void onDocsLinkClicked(TextView textView) {
        launchUrl(Uri.parse(mItem.getUrl()));
    }

    //
    // Lifecycle hooks
    //
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments().containsKey(ARG_ITEM_ID)) {
            mItem = (AbstractSnippet<T, Result>)
                    SnippetContent.ITEMS.get(getArguments().getInt(ARG_ITEM_ID));
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_snippet_detail, container, false);
        ButterKnife.bind(this, rootView);
        mSnippetDescription.setText(mItem.getDescription());
        return rootView;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (null != mStatusColor.getTag()) {
            outState.putInt(STATUS_COLOR, (Integer) mStatusColor.getTag());
        }
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (null != getActivity() && getActivity() instanceof AppCompatActivity) {
            AppCompatActivity activity = (AppCompatActivity) getActivity();
            if (null != activity.getSupportActionBar()) {
                activity.getSupportActionBar().setTitle(mItem.getName());
            }
        }
        if (null != savedInstanceState && savedInstanceState.containsKey(STATUS_COLOR)) {
            int statusColor = savedInstanceState.getInt(STATUS_COLOR, UNSET);
            if (UNSET != statusColor) {
                mStatusColor.setBackgroundColor(statusColor);
                mStatusColor.setTag(statusColor);
            }
        }
    }

    //
    // Custom event bindings
    //
    @Override
    public void onResponse(Call<Result> call, Response<Result> response) {
        if (response.isSuccessful()) {
            if (!isAdded()) {
                // the user has left...
                return;
            }
            mRunButton.setEnabled(true);
            mProgressbar.setVisibility(GONE);
            displayResponse(response);
        } else {
            Timber.e(response.errorBody().toString(), this);
            mRunButton.setEnabled(true);
            mProgressbar.setVisibility(GONE);

            if (null != response.errorBody()) {
                displayResponse(response);
            }
        }
    }

    @Override
    public void onFailure(Call<Result> call, Throwable t) {
        Timber.e(t.getMessage(), this);
        displayThrowable(t);
    }

    //
    // Private methods
    //
    private void clipboard(TextView tv) {
        // which view are we copying to the clipboard?
        int which;

        switch (tv.getId()) {
            case txt_request_url: // the url field
                which = req_url;
                break;

            case txt_response_headers: // the display headers
                which = response_headers;
                break;

            case txt_response_body: // the response body
                which = response_body;
                break;

            default:
                which = UNSET; // don't assign a prefix
        }

        // if we know which view we're copying, prefix it with useful info
        String what = which == UNSET ? "" : getString(which) + " ";

        // concat the clipboard data to this String
        what += getString(clippy);

        // inform the user that data was added to the clipboard
        Toast.makeText(
                getActivity(),
                what,
                Toast.LENGTH_SHORT
        ).show();

        // depending on our API, do it one way or another...
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            // old way
            ClipboardManager clipboardManager = (ClipboardManager)
                    getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
            clipboardManager.setText(tv.getText());
        } else {
            clipboard11(tv);
        }
    }

    @TargetApi(11)
    private void clipboard11(TextView tv) {
        android.content.ClipboardManager clipboardManager =
                (android.content.ClipboardManager) getActivity()
                        .getSystemService(Context.CLIPBOARD_SERVICE);
        ClipData clipData = ClipData.newPlainText("RESTSnippets", tv.getText());
        clipboardManager.setPrimaryClip(clipData);
    }

    private void launchUrl(Uri uri) {
        Intent viewDocs = new Intent(Intent.ACTION_VIEW, uri);
        startActivity(viewDocs);
    }

    private void displayResponse(Response response) {
        int color = getColor(response);
        displayStatusCode(Integer.toString(response.code()), getResources().getColor(color));
        displayRequestUrl(response);
        maybeDisplayResponseHeaders(response);
        maybeDisplayResponseBody(response);
    }

    private void maybeDisplayResponseBody(Response response) {
        if (null != response.body()) {
            String body = null;
            try {
                body = ((ResponseBody) response.body()).string();
                String formattedJson = new JSONObject(body).toString(2);
                mResponseBody.setText(formattedJson);
            } catch (JSONException e) {
                if (null != body) {
                    // body wasn't JSON
                    mResponseBody.setText(body);
                } else {
                    // set the stack trace as the response body
                    displayThrowable(e);
                }
            } catch (IOException ioe) {
                displayThrowable(ioe);
            }
        }
    }

    private void maybeDisplayResponseHeaders(Response response) {
        if (null != response.headers()) {
            Headers headers = response.headers();
            String headerText = "";

            for(int i = 0; i < headers.size(); i++){
                headerText += headers.name(i) + " : " + headers.value(i) + "\n";
            }

            mResponseHeaders.setText(headerText);
        }
    }

    private void displayRequestUrl(Response response) {
        String requestUrl = response.raw().request().url().toString();
        mRequestUrl.setText(requestUrl);
    }

    private void displayStatusCode(String text, int color) {
        mStatusCode.setText(text);
        mStatusColor.setBackgroundColor(color);
        mStatusColor.setTag(color);
    }

    private void displayThrowable(Throwable t) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        t.printStackTrace(pw);
        String trace = sw.toString();
        mResponseBody.setText(trace);
    }

    private int getColor(Response response) {
        int color;
        switch (response.code() / 100) {
            case 1:
            case 2:
                color = code_1xx;
                break;
            case 3:
                color = code_3xx;
                break;
            case 4:
            case 5:
                color = code_4xx;
                break;
            default:
                color = transparent;
        }
        return color;
    }
}