package com.github.andlyticsproject;

import android.app.Activity;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.content.Context;
import android.support.v4.content.Loader;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ExpandableListView;

import com.github.andlyticsproject.CommentsFragment.Comments;
import com.github.andlyticsproject.console.v2.DevConsoleRegistry;
import com.github.andlyticsproject.console.v2.DevConsoleV2;
import com.github.andlyticsproject.db.AndlyticsDb;
import com.github.andlyticsproject.model.AppStats;
import com.github.andlyticsproject.model.Comment;
import com.github.andlyticsproject.model.CommentGroup;
import com.github.andlyticsproject.model.StatsSummary;
import com.github.andlyticsproject.util.LoaderBase;
import com.github.andlyticsproject.util.LoaderResult;
import com.github.andlyticsproject.util.Utils;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

public class CommentsFragment extends Fragment implements StatsView<Comment>,
		LoaderManager.LoaderCallbacks<LoaderResult<Comments>> {

	private static final String TAG = CommentsFragment.class.getSimpleName();

	private static final int MAX_LOAD_COMMENTS = 20;

	private static final int DB_LOADER_ID = 0;
	private static final int REMOTE_LOADER_ID = 1;

	private CommentsListAdapter commentsListAdapter;
	private ExpandableListView list;
	private View nocomments;
	private View footer;

	private int maxAvailableComments;
	// need to preserve insertion order
	// yyymmdd -> CommentGroup
	private LinkedHashMap<String, CommentGroup> commentGroups;
	private List<Comment> comments;
	public int nextCommentIndex;
	public boolean hasMoreComments;

	private DevConsoleV2 devConsole;

	protected DetailedStatsActivity statsActivity;

	static class Comments {
		List<Comment> loaded = new ArrayList<Comment>();
		int maxAvailableComments = 0;
		boolean cacheUpdated = false;
		boolean canReply = true;
	}

	static class CommentsLoader extends LoaderBase<Comments> {

		protected Activity activity;
		protected ContentAdapter db;
		protected String packageName;
		protected String accountName;
		protected String developerId;
		protected int nextCommentIndex;

		protected List<Comment> comments;
		protected int maxAvailableComments = -1;

		public CommentsLoader(Activity context, String accountName, String developerId,
				String packageName, int nextCommentIndex) {
			super(context);
			this.activity = context;
			db = ContentAdapter.getInstance(AndlyticsApp.getInstance());
			this.accountName = accountName;
			this.developerId = developerId;
			this.packageName = packageName;
			this.nextCommentIndex = nextCommentIndex;
		}

		@Override
		protected Comments load() throws Exception {
			if (packageName == null || accountName == null || developerId == null) {
				return null;
			}

			return loadFromCache();
		}

		protected void updateCommentsCacheIfNecessary(List<Comment> newComments) {
			if (newComments == null || newComments.isEmpty()) {
				return;
			}

			// if refreshing, clear and rebuild cache
			if (nextCommentIndex == 0) {
				updateCommentsCache(newComments);
			}
		}

		protected void updateCommentsCache(List<Comment> commentsToCache) {
			db.updateCommentsCache(commentsToCache, packageName);
			comments = new ArrayList<Comment>();
		}


		protected Comments loadFromCache() {
			Comments result = new Comments();
			result.cacheUpdated = false;
			result.canReply = true;
			result.loaded = db.getCommentsFromCache(packageName);
			AppStats appInfo = db.getLatestForApp(packageName);
			if (appInfo != null) {
				result.maxAvailableComments = appInfo.getNumberOfComments();
			} else {
				result.maxAvailableComments = comments.size();
			}

			return result;
		}

		@Override
		protected void releaseResult(LoaderResult<Comments> result) {
			// just a string, nothing to do
		}

		@Override
		protected boolean isActive(LoaderResult<Comments> result) {
			return false;
		}
	}

	static class RemoteCommentsLoader extends CommentsLoader {

		public RemoteCommentsLoader(Activity context, String accountName, String developerId,
				String packageName, int nextCommentIndex) {
			super(context, accountName, developerId, packageName, nextCommentIndex);
		}

		@Override
		protected Comments load() throws Exception {
			if (packageName == null || accountName == null || developerId == null) {
				return null;
			}

			boolean canReply = true;
			if (maxAvailableComments == -1) {
				AppStats appInfo = db.getLatestForApp(packageName);
				if (appInfo != null) {
					maxAvailableComments = appInfo.getNumberOfComments();
				} else {
					maxAvailableComments = MAX_LOAD_COMMENTS;
				}
			}

			Comments result = new Comments();
			if (maxAvailableComments != 0) {
				DevConsoleV2 console = DevConsoleRegistry.getInstance().get(accountName);

				List<Comment> loaded = console.getComments(activity, packageName, developerId,
						nextCommentIndex, MAX_LOAD_COMMENTS, Utils.getDisplayLocale());
				updateCommentsCacheIfNecessary(loaded);
				// XXX
				// we can only do this after we authenticate at least once,
				// which may not happen before refreshing if we are loading
				// from cache
				canReply = console.canReplyToComments();

				AndlyticsDb.getInstance(getContext()).saveLastCommentsRemoteUpdateTime(packageName,
						System.currentTimeMillis());

				result.loaded = loaded;
				result.maxAvailableComments = maxAvailableComments;
				result.cacheUpdated = true;
				result.canReply = canReply;
			}

			return result;
		}

	}

	public CommentsFragment() {
		setHasOptionsMenu(true);
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);

		// calling initLoader() here results in onLoadFinished() being 
		// called twice. Bad things happen then...
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.comments_fragment, container, false);

		list = (ExpandableListView) view.findViewById(R.id.comments_list);
		// TODO Use ListView.setEmptyView
		nocomments = (View) view.findViewById(R.id.comments_nocomments);

		// footer
		View inflate = inflater.inflate(R.layout.comments_list_footer, null);
		footer = (View) inflate.findViewById(R.id.comments_list_footer);
		list.addFooterView(inflate, null, false);

		View header = inflater.inflate(R.layout.comments_list_header, null);
		list.addHeaderView(header, null, false);

		list.setGroupIndicator(null);

		devConsole = DevConsoleRegistry.getInstance().get(statsActivity.getAccountName());
		commentsListAdapter = new CommentsListAdapter(getActivity());
		if (devConsole.hasSessionCredentials()) {
			commentsListAdapter.setCanReplyToComments(devConsole.canReplyToComments());
		}
		list.setAdapter(commentsListAdapter);

		maxAvailableComments = -1;
		commentGroups = new LinkedHashMap<String, CommentGroup>();
		comments = new ArrayList<Comment>();

		footer.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				fetchNextComments();
			}
		});
		hideFooter();

		return view;
	}

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

		if (statsActivity.shouldRemoteUpdateStats()) {
			loadRemoteData();
		} else {
			loadCurrentData();
		}
	}

	private void loadRemoteData() {
		Bundle args = new Bundle();
		args.putString("accountName", statsActivity.getAccountName());
		args.putString("developerId", statsActivity.getDeveloperId());
		args.putString("packageName", statsActivity.getPackage());
		args.putInt("nextCommentIndex", nextCommentIndex);

		statsActivity.refreshStarted();
		disableFooter();

		getLoaderManager().restartLoader(REMOTE_LOADER_ID, args, this);
	}

	private void loadCurrentData() {
		nextCommentIndex = 0;
		Bundle args = new Bundle();
		args.putString("accountName", statsActivity.getAccountName());
		args.putString("developerId", statsActivity.getDeveloperId());
		args.putString("packageName", statsActivity.getPackage());
		args.putInt("nextCommentIndex", nextCommentIndex);

		statsActivity.refreshStarted();
		disableFooter();

		getLoaderManager().initLoader(DB_LOADER_ID, args, this);
	}


	@Override
	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
		menu.clear();
		inflater.inflate(R.menu.comments_menu, menu);

		if (statsActivity.isRefreshing()) {
			menu.findItem(R.id.itemCommentsmenuRefresh).setActionView(
					R.layout.action_bar_indeterminate_progress);
		}
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		Context ctx = getActivity();
		if (ctx == null) {
			return false;
		}

		switch (item.getItemId()) {
		case R.id.itemCommentsmenuRefresh:
			refreshComments();
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}

	private void refreshCommentsIfNecessary() {
		if (shouldRemoteUpdateComments()) {
			resetNextCommentIndex();
			fetchNextComments();
		}
	}

	void refreshComments() {
		resetNextCommentIndex();
		fetchNextComments();
	}

	private void incrementNextCommentIndex(int increment) {
		nextCommentIndex += increment;
		if (nextCommentIndex >= maxAvailableComments) {
			hasMoreComments = false;
		} else {
			hasMoreComments = true;
		}
	}

	private void resetNextCommentIndex() {
		maxAvailableComments = -1;
		nextCommentIndex = 0;
		hasMoreComments = true;
	}

	private void fetchNextComments() {
		loadRemoteData();
	}

	protected boolean shouldRemoteUpdateComments() {
		if (comments == null || comments.isEmpty()) {
			return true;
		}

		long now = System.currentTimeMillis();
		long lastUpdate = AndlyticsDb.getInstance(getActivity()).getLastCommentsRemoteUpdateTime(
				statsActivity.getPackage());
		// never updated
		if (lastUpdate == 0) {
			return true;
		}

		return (now - lastUpdate) >= Preferences.COMMENTS_REMOTE_UPDATE_INTERVAL;
	}


	private void enableFooter() {
		footer.setEnabled(true);
	}

	private void disableFooter() {
		footer.setEnabled(false);
	}

	private void hideFooter() {
		footer.setVisibility(View.GONE);
	}

	private void showFooterIfNecessary() {
		footer.setVisibility(hasMoreComments ? View.VISIBLE : View.GONE);
	}

	private void expandCommentGroups() {
		if (comments != null && comments.size() > 0) {
			nocomments.setVisibility(View.GONE);
			commentsListAdapter.setCommentGroups(toList(commentGroups));
			for (int i = 0; i < commentGroups.size(); i++) {
				list.expandGroup(i);
			}
			commentsListAdapter.notifyDataSetChanged();
		} else {
			nocomments.setVisibility(View.VISIBLE);
		}
	}

	private static List<CommentGroup> toList(LinkedHashMap<String, CommentGroup> groups) {
		List<CommentGroup> result = new ArrayList<CommentGroup>();
		for (LinkedHashMap.Entry<String, CommentGroup> e : groups.entrySet()) {
			result.add(e.getValue());
		}

		return result;
	}

	public void rebuildCommentGroups() {
		long start = System.currentTimeMillis();
		commentGroups = new LinkedHashMap<String, CommentGroup>();
		List<Comment> expanded = Comment.expandReplies(comments);
		for (Comment comment : expanded) {
			CommentGroup group = new CommentGroup(comment);
			if (commentGroups.containsKey(group.getFormattedDate())) {
				group = commentGroups.get(group.getFormattedDate());
				group.addComment(comment);
			} else {
				commentGroups.put(group.getFormattedDate(), group);
			}
		}

		if (BuildConfig.DEBUG) {
			long elapsed = System.currentTimeMillis() - start;
			Log.d(TAG, String.format("rebuildCommentGroups took: %d [ms]", elapsed));
		}
	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try {
			statsActivity = (DetailedStatsActivity) activity;
		} catch (ClassCastException e) {
			throw new ClassCastException(activity.toString()
					+ " must implement OnFragmentInteractionListener");
		}
	}

	@Override
	public void onDetach() {
		super.onDetach();
		statsActivity = null;
	}


	@Override
	public void updateView(StatsSummary<Comment> statsSummary) {
		// XXX do nothing, revise interface!
	}

	@Override
	public String getTitle() {
		// this can be called before the fragment is attached
		Context ctx = AndlyticsApp.getInstance();
		return ctx.getString(R.string.comments);
	}


	@Override
	public Loader<LoaderResult<Comments>> onCreateLoader(int id, Bundle args) {
		String accountName = null;
		String developerId = null;
		String packageName = null;
		int nextCommentIndex = 0;
		if (args != null) {
			accountName = args.getString("accountName");
			developerId = args.getString("developerId");
			packageName = args.getString("packageName");
			nextCommentIndex = args.getInt("nextCommentIndex");
		}

		if (id == DB_LOADER_ID) {
			return new CommentsLoader(getActivity(), accountName, developerId, packageName,
					nextCommentIndex);
		}

		// id = 1
		return new RemoteCommentsLoader(getActivity(), accountName, developerId, packageName,
				nextCommentIndex);
	}


	@Override
	public void onLoadFinished(Loader<LoaderResult<Comments>> loader, LoaderResult<Comments> result) {
		if (getActivity() == null) {
			return;
		}

		statsActivity.refreshFinished();
		enableFooter();

		if (result.isFailed()) {
			Log.e(TAG, "Error fetching comments: " + result.getError().getMessage(),
					result.getError());
			statsActivity.handleUserVisibleException(result.getError());
			hideFooter();

			return;
		}

		if (result.getData() == null) {
			return;
		}

		Comments newComments = result.getData();
		boolean refreshed = false;
		if (comments == null || comments.isEmpty()) {
			comments = newComments.loaded;
		} else {
			// if refreshing clear current
			if (nextCommentIndex == 0) {
				comments = newComments.loaded;
				refreshed = true;
			} else {
				// 	otherwise add to adapter and display
				comments.addAll(newComments.loaded);
			}
		}
		maxAvailableComments = newComments.maxAvailableComments;

		commentsListAdapter.setCanReplyToComments(newComments.canReply);
		incrementNextCommentIndex(newComments.loaded.size());
		// XXX optimize or move to loader!
		rebuildCommentGroups();
		showFooterIfNecessary();

		expandCommentGroups();
		if (refreshed) {
			list.setSelectedGroup(0);
		}

		// XXX check here?
		if (!result.getData().cacheUpdated) {
			refreshCommentsIfNecessary();
		}
	}


	@Override
	public void onLoaderReset(Loader<LoaderResult<Comments>> loader) {
	}

	@Override
	public void setCurrentChart(int page, int column) {
		// NOOP, we don't have charts
	}

	@Override
	public int getCurrentChart() {
		// we don't have charts
		return -1;
	}

}
