package com.colintheshots.andevcon_rxpatterns;

import android.app.ActionBar;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.common.collect.Iterables;
import com.google.gson.annotations.Expose;

import java.util.List;
import java.util.Map;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Path;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.schedulers.Schedulers;

/**
 * Example of using concat() to choose between cache and network
 * based on expiration.
 *
 * I'm basing this code on Example 1 and adding the concat() operator to demonstrate
 * one way to use this pattern. One may either concat an observable that emits 0 or
 * more events later than the cache expiration OR ELSE concat two observables using
 * the first() operator with a function to test the cache expiration on each.
 *
 * This has become a popular paradigm for combining observables from your
 * reactive database and Retrofit.
 *
 * Here's a great blog post from my friend Dan Lew back in Minneapolis:
 * http://blog.danlew.net/2015/06/22/loading-data-from-multiple-sources-with-rxjava/
 *
 * Created by colin on 7/26/15.
 */
public class Example11 extends Activity {


    /** The GitHub REST API Endpoint */
    public final static String GITHUB_BASE_URL = "https://api.github.com";

    public static final int CACHE_EXP_IN_SECS = 60;

    /** Set this variable to your GitHub personal access token */
    /* public final static String GITHUB_PERSONAL_ACCESS_TOKEN = "XXX"; */

    private GitHubClient mGitHubClient;
    private ListView mListView;
    private String mGistVisible = "none";
    private List<Gist> mGistList;

    private AdapterView.OnItemClickListener gistClickListener = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
            TextView tv = (TextView) view.findViewById(R.id.hiddenIdTextView);
            if (tv!=null) {
                String gistName = tv.getText().toString();
                mGitHubClient.gist(gistName)
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Action1<GistDetail>() {
                            @Override
                            public void call(GistDetail gistDetail) {
                                displayFileList(gistDetail);
                            }
                        }, new Action1<Throwable>() {
                            @Override
                            public void call(Throwable throwable) {
                                throwable.printStackTrace();
                            }
                        });

                mGistVisible = gistName;
                displayHomeAsUp(true); // just hacking everything together with a single activity for simplicity
            }
        }
    };

    /**
     * Retrofit interface to GitHub API methods
     */
    public interface GitHubClient {
        @GET("/gists")
        Observable<List<Gist>> gists();

        @GET("/gists/{id}")
        Observable<GistDetail> gist(@Path("id") String id);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_example1);
        mListView = (ListView) findViewById(R.id.listView);
        createGithubClient();
    }

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

        if (Secrets.GITHUB_PERSONAL_ACCESS_TOKEN.equals("XXX")) {
            Toast.makeText(getApplicationContext(), "GitHub Personal Access Token is Unset!", Toast.LENGTH_LONG).show();
        }

        Observable.create(new Observable.OnSubscribe<List<Gist>>() {
            @Override
            public void call(Subscriber<? super List<Gist>> subscriber) {
                long cacheExpiration = System.currentTimeMillis();
                cacheExpiration -= CACHE_EXP_IN_SECS * 1000;

                // pretend mGistList is coming from the database here
                if (mGistList!=null &&
                        mGistList.size()>0 &&
                        mGistList.get(0).cachedTime > cacheExpiration) {
                    subscriber.onNext(mGistList);
                }
                subscriber.onCompleted();
            }
        })
            // remember concat and concatWith enforce the order
            // unlike merge and mergeWith
            // if you want cached events always followed by network events, use merge
            .concatWith(mGitHubClient.gists())
            .first()
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Action1<List<Gist>>() {
                @Override
                public void call(List<Gist> gists) {
                    gists.get(0).cachedTime = System.currentTimeMillis();
                    mGistList = gists;
                    displayGistList(gists);
                    mListView.setOnItemClickListener(gistClickListener);
                }
            }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        throwable.printStackTrace();
                    }
                });
    }

    @Override
    public void onBackPressed() {
        if (mGistVisible.equals("none")) {
            super.onBackPressed();
        } else {
            if (mGistList!=null) {
                displayGistList(mGistList);
            }
            mGistVisible = "none";

            displayHomeAsUp(false);
        }
    }

    private void createGithubClient() {
        if (mGitHubClient == null) {
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(loggingInterceptor)
                    .addInterceptor(chain ->
                            chain.proceed(chain.request().newBuilder().addHeader("Authorization",
                                    "token " + Secrets.GITHUB_PERSONAL_ACCESS_TOKEN).build())).build();

            mGitHubClient = new Retrofit.Builder()
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(client)
                    .baseUrl(GITHUB_BASE_URL)
                    .build()
                    .create(GitHubClient.class);
        }
    }

    public void displayGistList(final List<Gist> gists) {
        if (gists.size()>0 && mListView!=null) {
            mListView.setAdapter(new GistAdapter(Example11.this, gists));
        }
    }

    public void displayFileList(final GistDetail gistDetail) {
        if (gistDetail.getFiles().size()>0 && mListView!=null) {
            mListView.setAdapter(new GistAdapter(Example11.this, gistDetail));
        }
    }

    void displayHomeAsUp(Boolean value) {
        ActionBar actionBar = getActionBar();
        if (actionBar!=null) {
            actionBar.setDisplayHomeAsUpEnabled(value);
        }
    }

    private class GistAdapter extends BaseAdapter {

        private Context mContext;
        private List<Gist> mGists;
        private Map<String, GistFile> mGistFileMap;

        public GistAdapter(Context context, List<Gist> gists) {
            mContext = context;
            mGists = gists;
        }

        public GistAdapter(Context context, GistDetail gistDetail) {
            mContext = context;
            mGistFileMap = gistDetail.getFiles();
        }

        @Override
        public int getCount() {
            if (mGistFileMap==null) {
                return mGists.size();
            } else {
                return mGistFileMap.size();
            }
        }

        @Override
        public Object getItem(int i) {
            if (mGistFileMap==null) {
                return mGists.get(i);
            } else {
                String key = Iterables.get(mGistFileMap.keySet(), i);
                return mGistFileMap.get(key);
            }
        }

        @Override
        public long getItemId(int i) {
            return i;
        }

        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            GistHolder holder;
            if (view != null) {
                holder = (GistHolder) view.getTag();
            } else {
                view = LayoutInflater.from(mContext).inflate(R.layout.row_gist, viewGroup, false);
                holder = new GistHolder(view);
                view.setTag(holder);
            }

            if (mGistFileMap == null) {
                Gist g = mGists.get(i);

                holder.title.setText(g.getHtml_url());
                holder.id.setText(g.getId());
            } else {
                GistFile gistFile = (GistFile) getItem(i);

                holder.title.setText(gistFile.getContent());
                holder.id.setText(gistFile.getContent());
            }

            return view;
        }

        private class GistHolder {

            TextView title;
            TextView id;

            GistHolder(View view) {
                title = (TextView) view.findViewById(R.id.gistTextView);
                id = (TextView) view.findViewById(R.id.hiddenIdTextView);
            }
        }
    }

    private class Gist {
        @Expose
        private String id;

        @Expose
        private String description;

        @Expose
        private String html_url;

        public long cachedTime;

        public String getId() {
            return id;
        }

        public String getDescription() {
            return description;
        }

        public String getHtml_url() {
            return html_url;
        }
    }

    private class GistDetail {
        @Expose
        private Map<String, GistFile> files;

        public Map<String, GistFile> getFiles() {
            return files;
        }
    }

    private class GistFile {
        @Expose
        private String content;

        public String getContent() {
            return content;
        }
    }


}
