/*
 * Copyright 2017 Thomas Andres
 *
 * This file is part of Kandroid.
 *
 * Kandroid is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Kandroid is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */

package in.andres.kandroid.ui;

import android.Manifest;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.NestedScrollView;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.method.LinkMovementMethod;
import android.util.Base64;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.OvershootInterpolator;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;

import org.commonmark.parser.Parser;
import org.commonmark.renderer.NodeRenderer;
import org.commonmark.renderer.html.HtmlNodeRendererContext;
import org.commonmark.renderer.html.HtmlNodeRendererFactory;
import org.commonmark.renderer.html.HtmlRenderer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;

import in.andres.kandroid.BuildConfig;
import in.andres.kandroid.CompactHtmlRenderer;
import in.andres.kandroid.Constants;
import in.andres.kandroid.DownloadIntentService;
import in.andres.kandroid.R;
import in.andres.kandroid.Utils;
import in.andres.kandroid.kanboard.KanboardAPI;
import in.andres.kandroid.kanboard.KanboardCategory;
import in.andres.kandroid.kanboard.KanboardColumn;
import in.andres.kandroid.kanboard.KanboardComment;
import in.andres.kandroid.kanboard.KanboardRequest;
import in.andres.kandroid.kanboard.KanboardSubtask;
import in.andres.kandroid.kanboard.KanboardSwimlane;
import in.andres.kandroid.kanboard.KanboardTask;
import in.andres.kandroid.kanboard.KanboardTaskFile;
import in.andres.kandroid.kanboard.KanboardUserInfo;
import in.andres.kandroid.kanboard.events.OnCloseTaskListener;
import in.andres.kandroid.kanboard.events.OnCreateCommentListener;
import in.andres.kandroid.kanboard.events.OnCreateSubtaskListener;
import in.andres.kandroid.kanboard.events.OnDownloadTaskFileListener;
import in.andres.kandroid.kanboard.events.OnGetActiveSwimlanesListener;
import in.andres.kandroid.kanboard.events.OnGetAllCommentsListener;
import in.andres.kandroid.kanboard.events.OnGetAllSubtasksListener;
import in.andres.kandroid.kanboard.events.OnGetAllTaskFilesListener;
import in.andres.kandroid.kanboard.events.OnGetCategoryListener;
import in.andres.kandroid.kanboard.events.OnGetColumnsListener;
import in.andres.kandroid.kanboard.events.OnGetProjectUsersListener;
import in.andres.kandroid.kanboard.events.OnGetSwimlaneListener;
import in.andres.kandroid.kanboard.events.OnGetTaskListener;
import in.andres.kandroid.kanboard.events.OnMoveTaskPositionListener;
import in.andres.kandroid.kanboard.events.OnOpenTaskListener;
import in.andres.kandroid.kanboard.events.OnRemoveCommentListener;
import in.andres.kandroid.kanboard.events.OnRemoveSubtaskListener;
import in.andres.kandroid.kanboard.events.OnRemoveTaskFileListener;
import in.andres.kandroid.kanboard.events.OnRemoveTaskListener;
import in.andres.kandroid.kanboard.events.OnSubtaskTimetrackingListener;
import in.andres.kandroid.kanboard.events.OnUpdateCommentListener;
import in.andres.kandroid.kanboard.events.OnUpdateSubtaskListener;

public class TaskDetailActivity extends AppCompatActivity {
    private KanboardTask task;
    private KanboardCategory category;
    private KanboardSwimlane swimlane;
    private KanboardColumn column;
    private KanboardUserInfo me;
    private List<KanboardComment> comments = new ArrayList<>();
    private List<KanboardSubtask> subtasks = new ArrayList<>();
    private List<KanboardTaskFile> files = new ArrayList<>();
    private List<KanboardColumn> projectColumns;
    private List<KanboardSwimlane> projectSwimlanes;
    private Dictionary<Integer, String> users;
    private Hashtable<Integer, Double> hasTimer = new Hashtable<>();
    private int downloadFileId = -1;
    private String downloadFileName = null;
//    private HashSet<Integer> hasTimer = new HashSet<>();
    private MenuItem refreshAction;
    private MenuItem opencloseAction;
    private int activeRequests = 0;
    private boolean progressVisible = false;
    private Context self;
    private Parser mParser = Parser.builder().build();
    private HtmlRenderer mRenderer = HtmlRenderer.builder().nodeRendererFactory(new HtmlNodeRendererFactory() {
        @Override
        public NodeRenderer create(HtmlNodeRendererContext context) {
            return new CompactHtmlRenderer(context);
        }
    }).build();

    private KanboardAPI kanboardAPI;

    //region Event Listeners
    private OnGetAllCommentsListener commentsListener = new OnGetAllCommentsListener() {
        @Override
        public void onGetAllComments(boolean success, List<KanboardComment> result) {
            hideProgress();
            hideCommentProgress();
            if (success && result.size() >= 0) {
                comments = result;
                commentListview.setAdapter(new CommentAdapter (getBaseContext(), comments, true));
//                findViewById(R.id.card_comments).setVisibility(View.VISIBLE);
            } else {
//                findViewById(R.id.card_comments).setVisibility(View.GONE);
            }
        }
    };
    private OnGetTaskListener taskListener = new OnGetTaskListener() {
        @Override
        public void onGetTask(boolean success, KanboardTask result) {
            if (success) {
                task = result;
                if (task.getCategoryId() > 0) {
                    showProgress();
                    kanboardAPI.getCategory(task.getCategoryId());
                }
                if (task.getSwimlaneId() == 0) {
                    showProgress();
                    kanboardAPI.getDefaultSwimlane(task.getProjectId());
                } else {
                    showProgress();
                    kanboardAPI.getSwimlane(task.getSwimlaneId());
                }
                hideProgress();
                Log.d("Hide Progress", "OnGetTaskListener");
                setTaskDetails();
            }
        }
    };
    private OnRemoveTaskListener removeTaskListener = new OnRemoveTaskListener() {
        @Override
        public void onRemoveTask(boolean success) {
            if (success) {
                setResult(Constants.ResultChanged);
                finish();
            } else
                Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_remove_task), Snackbar.LENGTH_LONG).show();
        }
    };
    private OnGetProjectUsersListener usersListener = new OnGetProjectUsersListener() {
        @Override
        public void onGetProjectUsers(boolean success, Dictionary<Integer, String> result) {
            hideProgress();
            if (success) {
                users = result;
                setTaskDetails();
//                textOwner.setText(Utils.fromHtml(getString(R.string.taskview_owner, result.get(task.getOwnerId()))));
//                textCreator.setText(Utils.fromHtml(getString(R.string.taskview_creator, result.get(task.getCreatorId()))));

                //Send change notification to update usernames in comments
                if (commentListview.getAdapter() != null)
                    ((CommentAdapter) commentListview.getAdapter()).notifyDataSetChanged();
                if (filesListview.getAdapter() != null)
                    ((TaskFilesAdapter) filesListview.getAdapter()).notifyDataSetChanged();
            }
        }
    };
    private OnGetCategoryListener categoryListener = new OnGetCategoryListener() {
        @Override
        public void onGetCategory(boolean success, KanboardCategory result) {
            hideProgress();
            if (success) {
                category = result;
                setCategoryDetails();
            }
        }
    };
    private OnGetSwimlaneListener swimlaneListener = new OnGetSwimlaneListener() {
        @Override
        public void onGetSwimlane(boolean success, KanboardSwimlane result) {
            hideProgress();
            if (success) {
                swimlane = result;
                setSwimlaneDetails(swimlane.getName());
            }
        }
    };
    private OnGetAllSubtasksListener allSubtasksListener = new OnGetAllSubtasksListener() {
        @Override
        public void onGetAllSubtasks(boolean success, List<KanboardSubtask> result) {
            hideProgress();
            hideSubtaskProgress();
            if (success && result.size() > 0) {
                subtasks = result;
                subtaskListview.setAdapter(new SubtaskAdapter(getBaseContext(), subtasks, true));
//                findViewById(R.id.card_subtasks).setVisibility(View.VISIBLE);
                for (final KanboardSubtask sub: subtasks) {
                    kanboardAPI.hasSubtaskTimer(sub.getId(), me.getId(), new OnSubtaskTimetrackingListener() {
                        @Override
                        public void onSubtaskTimetracking(boolean result, double time) {
                            if (result && !hasTimer.containsKey(sub.getId())) {
                                hasTimer.put(sub.getId(), 0d);
                                ((SubtaskAdapter) subtaskListview.getAdapter()).notifyDataSetChanged();
                                kanboardAPI.getSubtaskTimeSpent(sub.getId(), me.getId(), new OnSubtaskTimetrackingListener() {
                                    @Override
                                    public void onSubtaskTimetracking(boolean result, double time) {
                                        hasTimer.put(sub.getId(), time);
                                        ((SubtaskAdapter) subtaskListview.getAdapter()).notifyDataSetChanged();
                                    }
                                });
                            } else if (!result && hasTimer.containsKey(sub.getId())){
                                hasTimer.remove(sub.getId());
                                ((SubtaskAdapter) subtaskListview.getAdapter()).notifyDataSetChanged();
                            }
                        }
                    });
                }
            } else {
//                findViewById(R.id.card_subtasks).setVisibility(View.GONE);
            }
        }
    };
    private OnCreateCommentListener createCommentListener = new OnCreateCommentListener() {
        @Override
        public void onCreateComment(boolean success, Integer commentid) {
            if (success) {
                showProgress();
                kanboardAPI.getAllComments(task.getId());
            } else
                Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_create_comment), Snackbar.LENGTH_LONG).show();
        }
    };
    private OnUpdateCommentListener updateCommentListener = new OnUpdateCommentListener() {
        @Override
        public void onUpdateComment(boolean success) {
            if (success) {
                showProgress();
                kanboardAPI.getAllComments(task.getId());
            } else
                Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_update_comment), Snackbar.LENGTH_LONG).show();
        }
    };
    private OnRemoveCommentListener removeCommentListener = new OnRemoveCommentListener() {
        @Override
        public void onRemoveComment(boolean success) {
            if (success) {
                showProgress();
                kanboardAPI.getAllComments(task.getId());
            } else
                Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_remove_comment), Snackbar.LENGTH_LONG).show();
        }
    };
    private OnCreateSubtaskListener createSubtaskListener = new OnCreateSubtaskListener() {
        @Override
        public void onCreateSubtask(boolean success, Integer result) {
            if (success) {
                showProgress();
                kanboardAPI.getAllSubtasks(task.getId());
            } else
                Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_create_subtask), Snackbar.LENGTH_LONG).show();
        }
    };
    private OnUpdateSubtaskListener updateSubtaskListener = new OnUpdateSubtaskListener() {
        @Override
        public void onUpdateSubtask(boolean success) {
            if (success) {
                showProgress();
                kanboardAPI.getAllSubtasks(task.getId());
            } else
                Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_update_subtask), Snackbar.LENGTH_LONG).show();
        }
    };
    private OnRemoveSubtaskListener removeSubtaskListener = new OnRemoveSubtaskListener() {
        @Override
        public void onRemoveSubtask(boolean success) {
            if (success) {
                showProgress();
                kanboardAPI.getAllSubtasks(task.getId());
            } else
                Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_remove_subtask), Snackbar.LENGTH_LONG).show();
        }
    };
    private OnOpenTaskListener openTaskListener = new OnOpenTaskListener() {
        @Override
        public void onOpenTask(boolean success) {
            if (success) {
                textStatus.setText(Utils.fromHtml(getString(R.string.taskview_status, getString(R.string.taskview_status_open))));
                fabMenuButtonOpenCloseTask.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.task_close, null));
                fabMenuLabelOpenCloseTask.setText(getString(R.string.taskview_fab_close_task));
                if (opencloseAction != null) {
                    opencloseAction.setTitle(R.string.taskview_fab_close_task);
                }
                showProgress();
                kanboardAPI.getTask(task.getId());
            } else
                Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_open_task), Snackbar.LENGTH_LONG).show();
        }
    };
    private OnCloseTaskListener closeTaskListener = new OnCloseTaskListener() {
        @Override
        public void onCloseTask(boolean success) {
            if (success) {
                textStatus.setText(Utils.fromHtml(getString(R.string.taskview_status, getString(R.string.taskview_status_closed))));
                fabMenuButtonOpenCloseTask.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.task_open, null));
                fabMenuLabelOpenCloseTask.setText(getString(R.string.taskview_fab_open_task));
                if (opencloseAction != null) {
                    opencloseAction.setTitle(R.string.taskview_fab_open_task);
                }
                showProgress();
                kanboardAPI.getTask(task.getId());
            } else
                Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_close_task), Snackbar.LENGTH_LONG).show();
        }
    };
    private OnGetAllTaskFilesListener getAllTaskFilesListener = new OnGetAllTaskFilesListener() {
        @Override
        public void onGetAllTaskFiles(boolean success, List<KanboardTaskFile> result) {
            hideProgress();
            hideFilesProgress();
            if (success && result.size() > 0) {
//                findViewById(R.id.card_files).setVisibility(View.VISIBLE);
                files = result;
                filesListview.setAdapter(new TaskFilesAdapter(getBaseContext(), files));
            } else {
//                findViewById(R.id.card_files).setVisibility(View.GONE);
            }
        }
    };
    private OnRemoveTaskFileListener removeTaskFileListener = new OnRemoveTaskFileListener() {
        @Override
        public void onRemoveTaskFile(boolean success) {
            if (success) {
                showProgress();
                kanboardAPI.getAllTaskFiles(task.getId());
            } else {
                Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_remove_subtask), Snackbar.LENGTH_LONG).show();
            }
        }
    };
    private OnDownloadTaskFileListener downloadTaskFileListener = new OnDownloadTaskFileListener() {
        @Override
        public void onDownloadTaskFile(boolean success, int id, String data) {
            if (success) {
                byte[] inData = Base64.decode(data, Base64.DEFAULT);
                for (KanboardTaskFile f: files) {
                    if (f.getId() == id) {
                        try {
                            File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), f.getName());
                            FileOutputStream outData = new FileOutputStream(file);
                            outData.write(inData);
                            outData.close();
                            String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()));
                            if (mime == null) {
                                mime = "application/octet-stream";
                            }
                            if (BuildConfig.DEBUG) {
                                Log.d(Constants.TAG, Uri.fromFile(file).toString());
                                Log.d(Constants.TAG, mime);
                            }
                            DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
                            dm.addCompletedDownload(file.getName(), "Kandroid download", false, mime, file.getPath(), file.length(), true);
//                            Snackbar.make(findViewById(R.id.root_layout), String.format(Locale.getDefault(), "Saved file to: %s", file.getPath()), Snackbar.LENGTH_LONG).show();
                        } catch (IOException e) {
                            Log.w(Constants.TAG, "IOError writing file");
                            e.printStackTrace();
                        }
                        break;
                    }
                }
            } else {
                Snackbar.make(findViewById(R.id.root_layout), "Unable to download file", Snackbar.LENGTH_LONG).show();
            }
        }
    };
    private OnGetColumnsListener getColumnsListener = new OnGetColumnsListener() {
        @Override
        public void onGetColumns(boolean success, List<KanboardColumn> result) {
            hideProgress();
            if (success) {
                projectColumns = result;
            } else {
                Snackbar.make(findViewById(R.id.root_layout), "Error while receiving project columns", Snackbar.LENGTH_LONG).show();
            }
        }
    };
    private OnMoveTaskPositionListener moveTaskPositionListener = new OnMoveTaskPositionListener() {
        @Override
        public void onMoveTaskPosition(boolean success) {
            if (success)
                refresh();
            else
                Snackbar.make(findViewById(R.id.root_layout), "Error while moving task", Snackbar.LENGTH_LONG).show();
        }
    };
    private OnGetActiveSwimlanesListener getActiveSwimlanesListener = new OnGetActiveSwimlanesListener() {
        @Override
        public void onGetActiveSwimlanes(boolean success, List<KanboardSwimlane> result) {
            hideProgress();
            if (success) {
                projectSwimlanes = result;
            }
        }
    };
    //endregion

    private TextView textCategory;
    private TextView textStatus;
    private TextView textPosition;
    private TextView textPriority;
    private TextView textSwimlane;
    private TextView textOwner;
    private TextView textCreator;
    private TextView textDateCreated;
    private TextView textDateModified;
    private TextView textDateMoved;

    private TextView textHoursEstimated;
    private TextView textHoursUsed;
    private TextView textDateStart;
    private TextView textDateDue;

    private TextView textDescription;

    private ListView commentListview;

    private ListView subtaskListview;

    private ListView filesListview;

    private FloatingActionButton fabMenu;
    private FloatingActionButton fabMenuButtonRemoveTask;
    private FloatingActionButton fabMenuButtonOpenCloseTask;
    private FloatingActionButton fabMenuButtonNewComment;
    private FloatingActionButton fabMenuButtonNewSubtask;
    private FloatingActionButton fabMenuButtonEditTask;
    private TextView fabMenuLabelOpenCloseTask;

    private Animation fabCloseAnimation;
    private Animation fabOpenAnimation;

    private boolean isFABMenuOpen = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_task_detail);

        self = this;

        findViewById(R.id.card_description).setVisibility(View.GONE);
//        findViewById(R.id.card_subtasks).setVisibility(View.GONE);
//        findViewById(R.id.card_comments).setVisibility(View.GONE);
//        findViewById(R.id.card_files).setVisibility(View.GONE);

        textCategory = (TextView) findViewById(R.id.text_category);
        textStatus = (TextView) findViewById(R.id.text_status);
        textPosition = (TextView) findViewById(R.id.text_position);
        textPriority = (TextView) findViewById(R.id.text_priority);
        textSwimlane = (TextView) findViewById(R.id.text_swimlane);
        textOwner = (TextView) findViewById(R.id.text_owner);
        textCreator = (TextView) findViewById(R.id.text_creator);
        textDateCreated = (TextView) findViewById(R.id.text_DateCreated);
        textDateModified = (TextView) findViewById(R.id.text_DateModified);
        textDateMoved = (TextView) findViewById(R.id.text_DateMoved);

        textHoursEstimated = (TextView) findViewById(R.id.text_HoursEstimated);
        textHoursUsed = (TextView) findViewById(R.id.text_HoursUsed);
        textDateStart = (TextView) findViewById(R.id.text_DateStart);
        textDateDue = (TextView) findViewById(R.id.text_DateDue);

        textDescription = (TextView) findViewById(R.id.text_Description);
        textDescription.setMovementMethod(LinkMovementMethod.getInstance());

        commentListview = (ListView) findViewById(R.id.comment_listview);
        commentListview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (position >= comments.size() && !progressVisible) {
                    showCommentDialog(null);
                }
            }
        });
        registerForContextMenu(commentListview);

        subtaskListview = (ListView) findViewById(R.id.subtask_listview);
        subtaskListview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (position >= subtasks.size() && !progressVisible) {
                    showSubtaskDialog(null);
                }
            }
        });
        registerForContextMenu(subtaskListview);

        filesListview = (ListView) findViewById(R.id.files_listview);
        registerForContextMenu(filesListview);

        showCommentProgress();
        showSubtaskProgress();
        showFilesProgress();

        fabMenu = (FloatingActionButton) findViewById(R.id.fab);
        fabMenuButtonRemoveTask = (FloatingActionButton) findViewById(R.id.fab_menu_button_remove_task);
        fabMenuButtonOpenCloseTask = (FloatingActionButton) findViewById(R.id.fab_menu_button_open_close_task);
        fabMenuButtonNewComment = (FloatingActionButton) findViewById(R.id.fab_menu_button_new_comment);
        fabMenuButtonNewSubtask = (FloatingActionButton) findViewById(R.id.fab_menu_button_new_subtask);
        fabMenuButtonEditTask = (FloatingActionButton) findViewById(R.id.fab_menu_button_edit_task_task);
        fabMenuLabelOpenCloseTask = (TextView) findViewById(R.id.fab_menu_label_open_close_task);

        fabCloseAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_close);
        fabOpenAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_open);

        fabMenu.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (findViewById(R.id.fab).getVisibility() == View.VISIBLE) {
                    if (isFABMenuOpen)
                        collapseFABMenu();
                    else
                        expandFABMenu();
                }
            }
        });

        ((NestedScrollView) findViewById(R.id.activity_task_detail)).setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
            @Override
            public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                if (oldScrollY - scrollY > 0)
                    ((FloatingActionButton) findViewById(R.id.fab)).show();
                else if (oldScrollY - scrollY < 0) {
                    if (isFABMenuOpen)
                        collapseFABMenu();
                    ((FloatingActionButton) findViewById(R.id.fab)).hide();
                    ViewCompat.setRotation(fabMenu, 0.0F);
                }
            }
        });

        fabMenuButtonNewComment.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                collapseFABMenu();
                showCommentDialog(null);
            }
        });

        fabMenuButtonNewSubtask.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                collapseFABMenu();
                showSubtaskDialog(null);
            }
        });

        fabMenuButtonOpenCloseTask.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                collapseFABMenu();
                setResult(Constants.ResultChanged);
                if (task.getIsActive()) {
                    kanboardAPI.closeTask(task.getId());
                } else {
                    kanboardAPI.openTask(task.getId());
                }
            }
        });

        fabMenuButtonEditTask.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                collapseFABMenu();
                Intent intent = new Intent(getBaseContext(), TaskEditActivity.class);
                intent.putExtra("task", task);
                intent.putExtra("projectusers", (Hashtable<Integer, String>)users);
                startActivityForResult(intent, Constants.RequestEditTask);
            }
        });

        fabMenuButtonRemoveTask.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                collapseFABMenu();
                showDeleteTaskDialog(task);
            }
        });

        commentListview.setAdapter(new CommentAdapter (getBaseContext(), comments, true));
        subtaskListview.setAdapter(new SubtaskAdapter(getBaseContext(), subtasks, true));
        filesListview.setAdapter((new TaskFilesAdapter(getBaseContext(), files)));

        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getBaseContext());
        try {
            kanboardAPI = new KanboardAPI(preferences.getString("serverurl", ""), preferences.getString("username", ""), preferences.getString("password", ""));
            kanboardAPI.addOnGetAllCommentsListener(commentsListener);
            kanboardAPI.addOnGetTaskListener(taskListener);
            kanboardAPI.addOnRemoveTaskListener(removeTaskListener);
            kanboardAPI.addOnGetProjectUsersListener(usersListener);
            kanboardAPI.addOnGetCategoryListener(categoryListener);
            kanboardAPI.addOnGetSwimlaneListener(swimlaneListener);
            kanboardAPI.addOnGetDefaultSwimlaneListener(swimlaneListener);
            kanboardAPI.addOnGetAllSubtasksListener(allSubtasksListener);
            kanboardAPI.addOnCreateCommentListener(createCommentListener);
            kanboardAPI.addOnUpdateCommentListener(updateCommentListener);
            kanboardAPI.addOnRemoveCommentListener(removeCommentListener);
            kanboardAPI.addOnCreateSubtaskListener(createSubtaskListener);
            kanboardAPI.addOnUpdateSubtaskListener(updateSubtaskListener);
            kanboardAPI.addOnRemoveSubtaskListener(removeSubtaskListener);
            kanboardAPI.addOnOpenTaskListener(openTaskListener);
            kanboardAPI.addOnCloseTaskListener(closeTaskListener);
            kanboardAPI.addOnGetAllTaskFilesListListeners(getAllTaskFilesListener);
            kanboardAPI.addOnRemoveTaskFileListeners(removeTaskFileListener);
            kanboardAPI.addOnDownloadTaskFileListeners(downloadTaskFileListener);
            kanboardAPI.addOnGetColumnsListener(getColumnsListener);
            kanboardAPI.addOnMoveTaskPositionListener(moveTaskPositionListener);
            kanboardAPI.addOnGetActiveSwimlanesListener(getActiveSwimlanesListener);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (savedInstanceState != null) {
            restoreSavedState(savedInstanceState);
        } else {
            me = (KanboardUserInfo) getIntent().getSerializableExtra("me");
            task = (KanboardTask) getIntent().getSerializableExtra("task");
            if (getIntent().hasExtra("column")) {
                column = (KanboardColumn) getIntent().getSerializableExtra("column");
            }

            if (getIntent().hasExtra("swimlane")) {
                swimlane = (KanboardSwimlane) getIntent().getSerializableExtra("swimlane");
                setSwimlaneDetails(swimlane.getName());
            } else {
                textSwimlane.setVisibility(View.INVISIBLE);
            }

            if (getIntent().hasExtra("category")) {
                category = (KanboardCategory) getIntent().getSerializableExtra("category");
                setCategoryDetails();
            } else {
                textCategory.setText(Utils.fromHtml(getString(R.string.taskview_category, getString(R.string.task_not_assigned))));
            }

            setTaskDetails();
            refresh();
        }
        setupActionBar();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == Constants.RequestEditTask && resultCode == Constants.ResultChanged) {
            setResult(Constants.ResultChanged);
            refresh();
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable("task", task);
        outState.putSerializable("category", category);
        outState.putSerializable("swimlane", swimlane);
        outState.putSerializable("me", me);
        outState.putSerializable("users", (Hashtable<Integer, String>) users);
        outState.putSerializable("comments", (ArrayList<KanboardComment>) comments);
        outState.putSerializable("subtasks", (ArrayList<KanboardSubtask>) subtasks);
        outState.putSerializable("files", (ArrayList<KanboardTaskFile>) files);
        outState.putSerializable("projectColumns", (ArrayList<KanboardColumn>) projectColumns);
        outState.putSerializable("projectSwimlanes", (ArrayList<KanboardSwimlane>) projectSwimlanes);

        if (BuildConfig.DEBUG) Log.d(Constants.TAG, "TaskDetailActivity: saved savedInstanceState");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        restoreSavedState(savedInstanceState);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        if (v.getId() == R.id.comment_listview) {
            if (((AdapterView.AdapterContextMenuInfo)menuInfo).position < comments.size()) {
                menu.setHeaderTitle(getString(R.string.menu_caption_comment,
                        ((KanboardComment) commentListview.getAdapter().getItem(((AdapterView.AdapterContextMenuInfo) menuInfo).position)).getId()
                ));
                MenuInflater inflater = getMenuInflater();
                inflater.inflate(R.menu.activity_taskdetail_context_comment, menu);
            }
        }
        if (v.getId() == R.id.subtask_listview) {
            menu.setHeaderTitle(getString(R.string.menu_caption_subtask,
                    ((KanboardSubtask)subtaskListview.getAdapter().getItem(((AdapterView.AdapterContextMenuInfo)menuInfo).position)).getId()
            ));
            MenuInflater inflater = getMenuInflater();
            inflater.inflate(R.menu.activity_taskdetail_context_subtask, menu);
        }
        if (v.getId() == R.id.files_listview) {
            menu.setHeaderTitle(getString(R.string.menu_caption_file,
                    ((KanboardTaskFile)filesListview.getAdapter().getItem(((AdapterView.AdapterContextMenuInfo)menuInfo).position)).getId()
            ));
            MenuInflater inflater = getMenuInflater();
            inflater.inflate(R.menu.activity_taskdetail_context_files, menu);
        }
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
        switch (item.getItemId()) {
            case R.id.action_edit_comment:
                showCommentDialog((KanboardComment)commentListview.getAdapter().getItem(info.position));
                return true;
            case R.id.action_delete_comment:
                showDeleteCommentDialog((KanboardComment)commentListview.getAdapter().getItem(info.position));
                return true;
            case R.id.action_edit_subtask:
                showSubtaskDialog((KanboardSubtask)subtaskListview.getAdapter().getItem(info.position));
                return super.onContextItemSelected(item);
            case R.id.action_delete_subtask:
                showDeleteSubtaskDialog((KanboardSubtask)subtaskListview.getAdapter().getItem(info.position));
                return true;
            case R.id.action_download_file:
                String state = Environment.getExternalStorageState();
                if (Environment.MEDIA_MOUNTED.equals(state)) {
                    downloadFileId = ((KanboardTaskFile) filesListview.getAdapter().getItem(info.position)).getId();
                    downloadFileName = ((KanboardTaskFile) filesListview.getAdapter().getItem(info.position)).getName();
                    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                        Intent downloadIntent = new Intent(this, DownloadIntentService.class);
                        downloadIntent.putExtra("request", KanboardRequest.downloadTaskFile(downloadFileId).JSON[0]);
                        downloadIntent.putExtra("filename", downloadFileName);
                        startService(downloadIntent);
                    } else {
                        ActivityCompat.requestPermissions((Activity) self,
                                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                                Constants.RequestStoragePermission);
                    }
                } else {
                    Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_no_sdcard), Snackbar.LENGTH_LONG).show();
                }
                return true;
            case R.id.action_delete_file:
                showDeleteTaskFileDialog((KanboardTaskFile) filesListview.getAdapter().getItem(info.position));
                return true;
            default:
                return super.onContextItemSelected(item);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case Constants.RequestStoragePermission:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    if (downloadFileId != -1) {
                        Intent downloadIntent = new Intent(this, DownloadIntentService.class);
                        downloadIntent.putExtra("request", KanboardRequest.downloadTaskFile(downloadFileId).JSON[0]);
                        downloadIntent.putExtra("filename", downloadFileName);
                        startService(downloadIntent);
                        downloadFileId = -1;
                        downloadFileName = null;
                    } else {
                        Snackbar.make(findViewById(R.id.root_layout), getString(R.string.permission_storage_granted), Snackbar.LENGTH_LONG).show();
                    }
                } else {
                    Snackbar.make(findViewById(R.id.root_layout), getString(R.string.permission_storage_denied), Snackbar.LENGTH_LONG).show();
                }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_taskdetail_main, menu);
        refreshAction = menu.findItem(R.id.action_refresh);
        opencloseAction = menu.findItem(R.id.action_close_task);
        if (task.getIsActive()) {
            opencloseAction.setTitle(R.string.taskview_fab_close_task);
        } else {
            opencloseAction.setTitle(R.string.taskview_fab_open_task);
        }
        if (activeRequests > 0 && refreshAction != null) {
            ProgressBar prog = new ProgressBar(self);
            prog.getIndeterminateDrawable().setColorFilter(Color.WHITE, android.graphics.PorterDuff.Mode.MULTIPLY);
            refreshAction.setActionView(prog);
            refreshAction.expandActionView();
            progressVisible = true;
        }
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        switch (id) {
            case android.R.id.home:
                onBackPressed();
                return true;
            case R.id.action_refresh:
                refresh();
                return true;
            case R.id.action_new_comment:
                if (progressVisible)
                    Toast.makeText(this, getString(R.string.toast_wait_loading), Toast.LENGTH_SHORT).show();
                else
                    showCommentDialog(null);
                return true;
            case R.id.action_new_subtask:
                if (progressVisible)
                    Toast.makeText(this, getString(R.string.toast_wait_loading), Toast.LENGTH_SHORT).show();
                else
                    showSubtaskDialog(null);
                return true;
            case R.id.action_edit_task:
                Intent intent = new Intent(getBaseContext(), TaskEditActivity.class);
                intent.putExtra("task", task);
                intent.putExtra("projectusers", (Hashtable<Integer, String>)users);
                startActivityForResult(intent, Constants.RequestEditTask);
                return true;
            case R.id.action_change_column:
                if (progressVisible)
                    Toast.makeText(this, getString(R.string.toast_wait_loading), Toast.LENGTH_SHORT).show();
                else
                    showChangeColumnDialog();
                return true;
            case R.id.action_change_swimlane:
                if (progressVisible)
                    Toast.makeText(this, getString(R.string.toast_wait_loading), Toast.LENGTH_SHORT).show();
                else
                    showChangeSwimlaneDialog();
                return true;
            case R.id.action_close_task:
                setResult(Constants.ResultChanged);
                if (task.getIsActive()) {
                    kanboardAPI.closeTask(task.getId());
                } else {
                    kanboardAPI.openTask(task.getId());
                }
                return true;
            case R.id.action_delete_task:
                showDeleteTaskDialog(task);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onBackPressed() {
        if (isFABMenuOpen)
            collapseFABMenu();
        else
            super.onBackPressed();
    }

    private void restoreSavedState(Bundle savedInstanceState) {
        if (BuildConfig.DEBUG) Log.v(Constants.TAG, "TaskDetailActivity: restore savedInstanceState.");
        task = (KanboardTask) savedInstanceState.getSerializable("task");
        category = (KanboardCategory) savedInstanceState.getSerializable("category");
        swimlane = (KanboardSwimlane) savedInstanceState.getSerializable("swimlane");
        me = (KanboardUserInfo) savedInstanceState.getSerializable("me");
        Object ou = savedInstanceState.getSerializable("users");
        if (ou instanceof HashMap)
            users = new Hashtable<>((HashMap<Integer, String>) ou);
        else
            users = (Hashtable<Integer, String>) ou;
        comments = (ArrayList<KanboardComment>) savedInstanceState.getSerializable("comments");
        subtasks = (ArrayList<KanboardSubtask>) savedInstanceState.getSerializable("subtasks");
        files = (ArrayList<KanboardTaskFile>) savedInstanceState.getSerializable("files");
        projectColumns = (ArrayList<KanboardColumn>) savedInstanceState.getSerializable("projectColumns");
        projectSwimlanes = (ArrayList<KanboardSwimlane>) savedInstanceState.getSerializable("projectSwimlanes");

        setTaskDetails();
        setCategoryDetails();
        setSwimlaneDetails(swimlane.getName());

        if (comments != null) {
            commentListview.setAdapter(new CommentAdapter (getBaseContext(), comments, true));
            hideCommentProgress();
//            findViewById(R.id.card_comments).setVisibility(View.VISIBLE);
        } else {
//            findViewById(R.id.card_comments).setVisibility(View.GONE);
        }

        if (subtasks != null) {
            subtaskListview.setAdapter(new SubtaskAdapter(getBaseContext(), subtasks, true));
            hideSubtaskProgress();
//            findViewById(R.id.card_subtasks).setVisibility(View.VISIBLE);
        } else {
//            findViewById(R.id.card_subtasks).setVisibility(View.GONE);
        }

        if (files != null) {
            filesListview.setAdapter(new TaskFilesAdapter(getBaseContext(), files));
            hideFilesProgress();
//            findViewById(R.id.card_files).setVisibility(View.VISIBLE);
            Log.d(Constants.TAG, "restore files");
        } else {
//            findViewById(R.id.card_files).setVisibility(View.GONE);
            Log.d(Constants.TAG, "no files");
        }

    }

    private void expandFABMenu() {
        Log.i(Constants.TAG, "Expand FAB.");
        ViewCompat.animate(fabMenu).rotation(90.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(5.0F)).start();
        findViewById(R.id.fab_menu_item0).startAnimation(fabOpenAnimation);
        findViewById(R.id.fab_menu_item1).startAnimation(fabOpenAnimation);
        findViewById(R.id.fab_menu_item2).startAnimation(fabOpenAnimation);
        findViewById(R.id.fab_menu_item3).startAnimation(fabOpenAnimation);
        findViewById(R.id.fab_menu_item4).startAnimation(fabOpenAnimation);
        fabMenuButtonRemoveTask.setClickable(true);
        fabMenuButtonOpenCloseTask.setClickable(true);
        fabMenuButtonNewComment.setClickable(true);
        fabMenuButtonNewSubtask.setClickable(true);
        fabMenuButtonEditTask.setClickable(true);
        isFABMenuOpen = true;
    }

    private void collapseFABMenu() {
        Log.i(Constants.TAG, "Collapse FAB.");
        ViewCompat.animate(fabMenu).rotation(0.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(5.0F)).start();
        findViewById(R.id.fab_menu_item0).startAnimation(fabCloseAnimation);
        findViewById(R.id.fab_menu_item1).startAnimation(fabCloseAnimation);
        findViewById(R.id.fab_menu_item2).startAnimation(fabCloseAnimation);
        findViewById(R.id.fab_menu_item3).startAnimation(fabCloseAnimation);
        findViewById(R.id.fab_menu_item4).startAnimation(fabCloseAnimation);
        fabMenuButtonRemoveTask.setClickable(false);
        fabMenuButtonOpenCloseTask.setClickable(false);
        fabMenuButtonNewComment.setClickable(false);
        fabMenuButtonNewSubtask.setClickable(false);
        fabMenuButtonEditTask.setClickable(false);
        isFABMenuOpen = false;
    }

    private void showProgress() {
        activeRequests++;
        if (activeRequests > 0 && refreshAction != null && !progressVisible) {
            ProgressBar prog = new ProgressBar(self);
            prog.getIndeterminateDrawable().setColorFilter(Color.WHITE, android.graphics.PorterDuff.Mode.MULTIPLY);
            refreshAction.setActionView(prog);
            refreshAction.expandActionView();
            progressVisible = true;
        }
    }

    private void hideProgress() {
        activeRequests = activeRequests > 0 ? --activeRequests : 0;
        if (activeRequests == 0 && refreshAction != null && progressVisible) {
            refreshAction.collapseActionView();
            refreshAction.setActionView(null);
            progressVisible = false;
        }
    }

    private void showCommentProgress() {
        commentListview.setVisibility(View.GONE);
        findViewById(R.id.commentProgress).setVisibility(View.VISIBLE);
    }

    private void hideCommentProgress() {
        commentListview.setVisibility(View.VISIBLE);
        findViewById(R.id.commentProgress).setVisibility(View.GONE);
    }

    private void showSubtaskProgress() {
        subtaskListview.setVisibility(View.GONE);
        findViewById(R.id.subtaskProgress).setVisibility(View.VISIBLE);
    }

    private void hideSubtaskProgress() {
        subtaskListview.setVisibility(View.VISIBLE);
        findViewById(R.id.subtaskProgress).setVisibility(View.GONE);
    }

    private void showFilesProgress() {
        filesListview.setVisibility(View.GONE);
        findViewById(R.id.filesProgress).setVisibility(View.VISIBLE);
    }

    private void hideFilesProgress() {
        filesListview.setVisibility(View.VISIBLE);
        findViewById(R.id.filesProgress).setVisibility(View.GONE);
    }

    private void refresh() {
        Log.i(Constants.TAG, "Loading task data.");
        showProgress();
        kanboardAPI.getTask(task.getId());
        showProgress();
        kanboardAPI.getProjectUsers(task.getProjectId());
        showProgress();
        kanboardAPI.getAllComments(task.getId());
        showCommentProgress();
        showProgress();
        kanboardAPI.getAllSubtasks(task.getId());
        showSubtaskProgress();
        showProgress();
        kanboardAPI.getAllTaskFiles(task.getId());
        showFilesProgress();
        showProgress();
        kanboardAPI.getColumns(task.getProjectId());
        showProgress();
        kanboardAPI.getActiveSwimlanes(task.getProjectId());
    }

    private void showCommentDialog(@Nullable final KanboardComment comment) {
        AlertDialog.Builder builder = new AlertDialog.Builder(self);
        builder.setTitle(getString(comment == null ? R.string.taskview_fab_new_comment : R.string.taskview_dlg_update_comment));
        final EditText input = new EditText(this);
        input.setText(comment == null ? "" : comment.getContent());
        input.setSingleLine(false);
        input.setMinLines(5);
        input.setMaxLines(10);
        input.setVerticalScrollBarEnabled(true);
        builder.setView(input);
        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (!input.getText().toString().contentEquals("")) {
                    if (comment == null) {
                        Log.i(Constants.TAG, "Creating new comment.");
                        kanboardAPI.createComment(task.getId(), me.getId(), input.getText().toString());
                    } else {
                        Log.i(Constants.TAG, "Updating comment.");
                        kanboardAPI.updateComment(comment.getId(), input.getText().toString());
                    }
                    dialog.dismiss();
                }
            }
        });
        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
            }
        });
        builder.show();
    }

    private void showSubtaskDialog(@Nullable final KanboardSubtask subtask) {
        View dlgView = getLayoutInflater().inflate(R.layout.dialog_new_subtask, null);
        final EditText editTitle = (EditText) dlgView.findViewById(R.id.subtask_title);
        editTitle.setText(subtask == null ? "" : subtask.getTitle());
        final Spinner userSpinner = (Spinner) dlgView.findViewById(R.id.user_spinner);
        ArrayList<String> possibleOwners = Collections.list(users.elements());
        possibleOwners.add(0, "");
        ArrayAdapter<String> adapter = new ArrayAdapter<>(self, android.R.layout.simple_spinner_item, possibleOwners);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        userSpinner.setAdapter(adapter);
        if (subtask != null)
            userSpinner.setSelection(possibleOwners.indexOf(users.get(subtask.getUserId())));

        AlertDialog.Builder builder = new AlertDialog.Builder(self);
        builder.setTitle(getString(subtask == null ? R.string.taskview_fab_new_subtask : R.string.taskview_dlg_update_subtask));
        builder.setView(dlgView);
        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Integer userid = null;
                if (userSpinner.getSelectedItem() != null) {
                    for (Enumeration<Integer> iter = users.keys(); iter.hasMoreElements();) {
                        Integer key = iter.nextElement();
                        if (users.get(key).contentEquals((String) userSpinner.getSelectedItem())) {
                            userid = key;
                            break;
                        }
                    }
                }
                if (!editTitle.getText().toString().equalsIgnoreCase("")) {
                    if (subtask == null) {
                        Log.i(Constants.TAG, "Creating new subtask.");
                        kanboardAPI.createSubtask(task.getId(), editTitle.getText().toString(), userid, null, null, null);
                    } else {
                        Log.i(Constants.TAG, "Updating subtask.");
                        kanboardAPI.updateSubtask(subtask.getId(), subtask.getTaskId(), editTitle.getText().toString(), userid, null, null, null);
                    }
                    dialog.dismiss();
                }
            }
        });
        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
            }
        });
        builder.show();
    }

    private void showChangeColumnDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(self);
        builder.setTitle(getText(R.string.taskview_fab_change_column));
        final Spinner input = new Spinner(this);
        ArrayAdapter<KanboardColumn> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, projectColumns);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        input.setAdapter(adapter);
        for (int i = 0; i < projectColumns.size(); i++) {
            if (projectColumns.get(i).getId() == task.getColumnId()) {
                input.setSelection(i);
                break;
            }
        }
        builder.setView(input);
        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                setResult(Constants.ResultChanged);
                kanboardAPI.moveTaskPosition(task.getProjectId(), task.getId(), ((KanboardColumn)input.getSelectedItem()).getId(), 1, task.getSwimlaneId());
                dialog.dismiss();
            }
        });
        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
            }
        });
        builder.show();
    }

    private void showChangeSwimlaneDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(self);
        builder.setTitle(getText(R.string.taskview_fab_change_swimlane));
        final Spinner input = new Spinner(this);
        ArrayAdapter<KanboardSwimlane> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, projectSwimlanes);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        input.setAdapter(adapter);
        for (int i = 0; i < projectSwimlanes.size(); i++) {
            if (projectSwimlanes.get(i).getId() == task.getSwimlaneId()) {
                input.setSelection(i);
                break;
            }
        }
        builder.setView(input);
        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                setResult(Constants.ResultChanged);
                kanboardAPI.moveTaskPosition(task.getProjectId(), task.getId(), task.getColumnId(), 1, ((KanboardSwimlane)input.getSelectedItem()).getId());
                dialog.dismiss();
            }
        });
        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
            }
        });
        builder.show();
    }

    private void showDeleteCommentDialog(final KanboardComment comment) {
        AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(TaskDetailActivity.this);
        dlgBuilder.setTitle(getString(R.string.delete_dlg_comment));
        dlgBuilder.setMessage(getString(R.string.delete_dlg_message));
        dlgBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                kanboardAPI.removeComment(comment.getId());
            }
        });
        dlgBuilder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {}
        });
        dlgBuilder.show();
    }

    private void showDeleteSubtaskDialog(final KanboardSubtask subtask) {
        AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(TaskDetailActivity.this);
        dlgBuilder.setTitle(getString(R.string.delete_dlg_subtask));
        dlgBuilder.setMessage(getString(R.string.delete_dlg_message));
        dlgBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                kanboardAPI.removeSubtask(subtask.getId());
            }
        });
        dlgBuilder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {}
        });
        dlgBuilder.show();
    }

    private void showDeleteTaskFileDialog(final KanboardTaskFile taskfile) {
        AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(TaskDetailActivity.this);
        dlgBuilder.setTitle(getString(R.string.delete_dlg_file));
        dlgBuilder.setMessage(getString(R.string.delete_dlg_message));
        dlgBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                kanboardAPI.removeTaskFile(taskfile.getId());
            }
        });
        dlgBuilder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {}
        });
        dlgBuilder.show();
    }

    private void showDeleteTaskDialog(final KanboardTask task) {
        AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(TaskDetailActivity.this);
        dlgBuilder.setTitle(getString(R.string.delete_dlg_task));
        dlgBuilder.setMessage(getString(R.string.delete_dlg_message));
        dlgBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                kanboardAPI.removeTask(task.getId());
            }
        });
        dlgBuilder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

            }
        });
        dlgBuilder.show();
    }

    private void setupActionBar() {
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            // Show the Up button in the action bar.
            actionBar.setDisplayHomeAsUpEnabled(true);
        }
    }

    private void setTaskDetails() {
        if (getSupportActionBar() != null)
            getSupportActionBar().setTitle(task.getTitle());

        if (task.getIsActive())
            textStatus.setText(Utils.fromHtml(getString(R.string.taskview_status, getString(R.string.taskview_status_open))));
        else
            textStatus.setText(Utils.fromHtml(getString(R.string.taskview_status, getString(R.string.taskview_status_closed))));

        textPosition.setText(Utils.fromHtml(getString(R.string.taskview_position, task.getPosition())));
        textPriority.setText(Utils.fromHtml(getString(R.string.taskview_priority, task.getPriority())));
        if (task.getOwnerId() > 0)
            textOwner.setText(Utils.fromHtml(getString(R.string.taskview_owner, users == null ? Integer.toString(task.getOwnerId()) : users.get(task.getOwnerId()))));
        else
            textOwner.setText(Utils.fromHtml(getString(R.string.taskview_owner, getString(R.string.task_not_assigned))));
        textCreator.setText(Utils.fromHtml(getString(R.string.taskview_creator, users == null ? Integer.toString(task.getCreatorId()) : users.get(task.getCreatorId()))));
        textDateCreated.setText(Utils.fromHtml(getString(R.string.taskview_date_created, task.getDateCreation())));
        textDateModified.setText(Utils.fromHtml(getString(R.string.taskview_date_modified, task.getDateModification())));
        textDateMoved.setText(Utils.fromHtml(getString(R.string.taskview_date_moved, task.getDateMoved())));

        textHoursEstimated.setText(Utils.fromHtml(getString(R.string.taskview_hours_estimated, task.getTimeEstimated())));
        textHoursUsed.setText(Utils.fromHtml(getString(R.string.taskview_hours_spent, task.getTimeSpent())));

        if (task.getDateStarted() != null) {
            textDateStart.setText(Utils.fromHtml(getString(R.string.taskview_date_start, task.getDateStarted())));
            textDateStart.setVisibility(View.VISIBLE);
        } else {
            textDateStart.setVisibility(View.INVISIBLE);
        }

        if (task.getDateDue() != null) {
            textDateDue.setText(Utils.fromHtml(getString(R.string.taskview_date_due, task.getDateDue())));
            textDateDue.setVisibility(View.VISIBLE);
        } else {
            textDateDue.setVisibility(View.INVISIBLE);
        }

        if (task.getDateStarted() == null && task.getDateDue() == null) {
            textDateStart.setVisibility(View.GONE);
            textDateDue.setVisibility(View.GONE);
        }

        if (task.getDescription() != null && !task.getDescription().contentEquals("")) {
            textDescription.setText(Utils.fromHtml(mRenderer.render(mParser.parse(task.getDescription()))));
            findViewById(R.id.card_description).setVisibility(View.VISIBLE);
        }

        if (task.getIsActive()) {
            fabMenuButtonOpenCloseTask.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.task_close, null));
            fabMenuLabelOpenCloseTask.setText(getString(R.string.taskview_fab_close_task));
        } else {
            fabMenuButtonOpenCloseTask.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.task_open, null));
            fabMenuLabelOpenCloseTask.setText(getString(R.string.taskview_fab_open_task));
        }
        if (opencloseAction != null) {
            if (task.getIsActive()) {
                opencloseAction.setTitle(getString(R.string.taskview_fab_close_task));
            } else {
                opencloseAction.setTitle(getString(R.string.taskview_fab_open_task));
            }
        }
    }

    private void setSwimlaneDetails(String swimlanename) {
        textSwimlane.setText(Utils.fromHtml(getString(R.string.taskview_swimlane, swimlanename)));
        textSwimlane.setVisibility(View.VISIBLE);
    }

    private void setCategoryDetails() {
        if (category != null) {
            textCategory.setText(Utils.fromHtml(getString(R.string.taskview_category, category.getName())));
        } else {
            textCategory.setText(Utils.fromHtml(getString(R.string.taskview_category, getString(R.string.task_not_assigned))));
        }
    }

    //region internal classes
    private class SubtaskAdapter extends ArrayAdapter<KanboardSubtask> {
        private Context mContext;
        private LayoutInflater mInflater;
        private List<KanboardSubtask> mObjects;
        private boolean mShowAdd = false;

        public SubtaskAdapter(Context context, List<KanboardSubtask> objects, boolean showAdd) {
            super(context, R.layout.listitem_subtask, objects);
            mContext = context;
            mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mObjects = objects;
            mShowAdd = showAdd;
        }

        @Override
        public int getCount() {
            return mObjects.size() + (mShowAdd ? 1 : 0);
        }

        @NonNull
        @Override
        public View getView(int position, View convertView, @NonNull ViewGroup parent) {
            if (position < getCount() - (mShowAdd ? 1 : 0)) {
                convertView = mInflater.inflate(R.layout.listitem_subtask, parent, false);
                convertView.setLongClickable(true);

                TextView text = (TextView) convertView.findViewById(android.R.id.text1);
                CheckBox check = (CheckBox) convertView.findViewById(android.R.id.checkbox);
                Switch toggle = (Switch) convertView.findViewById(R.id.buttonToggle);

                // Use tag to save list position
                toggle.setTag(position);
                check.setTag(position);

                final OnSubtaskTimetrackingListener startTimer = new OnSubtaskTimetrackingListener() {
                    @Override
                    public void onSubtaskTimetracking(boolean result, double time) {
                    }
                };
                final OnSubtaskTimetrackingListener stopTimer = new OnSubtaskTimetrackingListener() {
                    @Override
                    public void onSubtaskTimetracking(boolean result, double time) {
                    }
                };
                View.OnClickListener toggleClick = new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (((Switch) v).isChecked()) {
                            kanboardAPI.setSubtaskStartTime(mObjects.get((int) v.getTag()).getId(), me.getId(), startTimer);
                        } else {
                            kanboardAPI.setSubtaskEndTime(mObjects.get((int) v.getTag()).getId(), me.getId(), stopTimer);
                        }
                    }
                };

                toggle.setOnClickListener(toggleClick);
                check.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (((CheckBox) v).isChecked())
                            kanboardAPI.updateSubtask(mObjects.get((int) v.getTag()).getId(), mObjects.get((int) v.getTag()).getTaskId(), null, null, null, null, 2);
                        else
                            kanboardAPI.updateSubtask(mObjects.get((int) v.getTag()).getId(), mObjects.get((int) v.getTag()).getTaskId(), null, null, null, null, 0);
                    }
                });

                if (mObjects.get(position).getUserId() == me.getId())
                    toggle.setEnabled(true);
                else
                    toggle.setEnabled(false);

                double timer = 0;
                if (hasTimer.containsKey(mObjects.get(position).getId())) {
                    toggle.setChecked(true);
                    timer = hasTimer.get(mObjects.get(position).getId());
                } else
                    toggle.setChecked(false);

                if (mObjects.get(position).getStatus() == 2) {
                    text.setText(Utils.fromHtml(String.format(Locale.getDefault(), "<del>%s</del>", mObjects.get(position).getTitle())));
                    check.setChecked(true);
                } else if (mObjects.get(position).getStatus() == 1) {
                    text.setText(Utils.fromHtml(String.format(Locale.getDefault(), "<b>%s</b>", mObjects.get(position).getTitle())));
                    check.setChecked(false);
                } else {
                    text.setText(mObjects.get(position).getTitle());
                    check.setChecked(false);
                }

                text.setSelected(true);

                ((TextView) convertView.findViewById(android.R.id.text2)).setText(String.format(Locale.getDefault(), "%.2fh", mObjects.get(position).getTimeSpent() + timer));
            } else {
                convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false);
                ((TextView) convertView.findViewById(android.R.id.text1)).setText(getString(R.string.taskview_fab_new_subtask));
                ((TextView) convertView.findViewById(android.R.id.text1)).setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
            }

            return convertView;
        }
    }

    private class CommentAdapter extends ArrayAdapter<KanboardComment> {
        private Context mContext;
        private LayoutInflater mInflater;
        private List<KanboardComment> mObjects;
        private boolean mShowAdd = false;

        public CommentAdapter(Context context, List<KanboardComment> objects, boolean showAdd) {
            super(context, R.layout.listitem_comment, objects);
            mContext = context;
            mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mObjects = objects;
            mShowAdd = showAdd;
        }

        @Override
        public int getCount() {
            return mObjects.size() + (mShowAdd ? 1 : 0);
        }

        @NonNull
        @Override
        public View getView(int position, View convertView, @NonNull ViewGroup parent) {
            if (position < getCount() - (mShowAdd ? 1 : 0)) {
                convertView = mInflater.inflate(R.layout.listitem_comment, parent, false);
                convertView.setLongClickable(true);
                ((TextView) convertView.findViewById(R.id.username)).setText(Utils.fromHtml(String.format("<small>%s</small>", users == null ? mObjects.get(position).getUsername() : users.get(mObjects.get(position).getUserId()))));
                ((TextView) convertView.findViewById(R.id.date)).setText(Utils.fromHtml(String.format("<small>%tF</small>", mObjects.get(position).getDateModification())));
                ((TextView) convertView.findViewById(R.id.comment)).setText(Utils.fromHtml(mRenderer.render(mParser.parse(mObjects.get(position).getContent()))));
            } else {
                convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false);
                ((TextView) convertView.findViewById(android.R.id.text1)).setText(getString(R.string.taskview_fab_new_comment));
                ((TextView) convertView.findViewById(android.R.id.text1)).setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
            }

            return convertView;
        }
    }

    private class TaskFilesAdapter extends ArrayAdapter<KanboardTaskFile> {
        private Context mContext;
        private LayoutInflater mInflater;
        List<KanboardTaskFile> mObjects;

        public TaskFilesAdapter(Context context, List<KanboardTaskFile> objects) {
            super(context, R.layout.listitem_comment, objects);
            mContext = context;
            mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mObjects = objects;

        }

        @NonNull
        @Override
        public View getView(int position, View convertView, @NonNull ViewGroup parent) {
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.listitem_taskfiles, parent, false);
                convertView.setLongClickable(true);
            }

            ((TextView) convertView.findViewById(R.id.username)).setText(Utils.fromHtml(String.format("<small>%s</small>", users == null ? mObjects.get(position).getUsername() : users.get(mObjects.get(position).getUserId()))));
            ((TextView) convertView.findViewById(R.id.date)).setText(Utils.fromHtml(String.format("<small>%tF</small>", mObjects.get(position).getFileDate())));
            ((TextView) convertView.findViewById(R.id.filename)).setText(String.format("%s", mObjects.get(position).getName()));
            double size = mObjects.get(position).getSize();
            int rounds = 0;
            while (size > 1024 && rounds < 4) {
                size /= 1024;
                rounds++;
            }
            ((TextView) convertView.findViewById(R.id.filesize)).setText(String.format(Locale.getDefault(), "%.2f %s", size, mContext.getResources().getStringArray(R.array.file_sizes)[rounds]));
            return convertView;
        }
    }
    //endregion
}