public class ArrayPagerAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> mFragments;
    private List<String> mPageTitle;
    private FragmentManager mFragmentManager;

    public ArrayPagerAdapter(FragmentManager fm) {
        super(fm);
        mFragments = new ArrayList<>();
        mPageTitle = new ArrayList<>();
        mFragmentManager = fm;
    }

    public int addFragment(Fragment fragment, String title) {
        return addFragment(fragment, title, mFragments.size());
    }

    public int addFragment(Fragment fragment,String title, int position) {
        mFragments.add(position, fragment);
        mPageTitle.add(position, title);
        return position;
    }

    public void removeAllFragments() {
        for (Fragment fragment: mFragments)
            mFragmentManager.beginTransaction().remove(fragment).commit();
        mFragments.clear();
        mPageTitle.clear();
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mPageTitle.get(position);
    }

    @Override
    public int getItemPosition(Object object) {
        int idx = mFragments.indexOf((Fragment) object);
        if (idx == -1)
            return POSITION_NONE;
        else
            return idx;
    }
} public interface Constants {
    String TAG = "Kandroid";
    int ResultChanged = 1;
    int RequestEditTask = 1;
    int FileSelectCode = 10;
    int RequestStoragePermission = 20;
    int[] minKanboardVersion = new int[] {1, 0, 38};
} @SuppressWarnings("unused")
public class KanboardAPI {

    public static HttpURLConnection openConnection(URL url, String data) throws IOException, CertificateException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
        SSLContext sslcontext = null;
        HttpURLConnection con = null;
        URL tmpURL = url;

        do {
            con = (HttpURLConnection) tmpURL.openConnection();
            if (con == null)
                return null;

            if (url.getProtocol().equalsIgnoreCase("https")) {
                KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
                keyStore.load(null);
                TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                tmf.init(keyStore);
                sslcontext = SSLContext.getInstance("TLS");
                sslcontext.init(null, tmf.getTrustManagers(), null);
                ((HttpsURLConnection) con).setSSLSocketFactory(sslcontext.getSocketFactory());
            }
            con.setConnectTimeout(120000);
            con.setReadTimeout(120000);
            con.setInstanceFollowRedirects(false);
            con.setRequestMethod("POST");
            con.setRequestProperty("Content-Type", "application/json");
            con.setRequestProperty("charset", "utf-8");
            con.setDoOutput(true);
            con.setDoInput(true);
            DataOutputStream out = new DataOutputStream(con.getOutputStream());
            out.writeBytes(data);
            out.flush();
            out.close();

            Log.d(Constants.TAG, String.format("API: Connected to %s", con.getURL().toString()));

            if (con.getResponseCode() == 301 || con.getResponseCode() == 302 || con.getResponseCode() == 307 || con.getResponseCode() == 308) {
                Log.i(Constants.TAG, "Follow URL redirect.");
                tmpURL = new URL(con.getHeaderField("Location"));
                Log.d(Constants.TAG, String.format("API: Redirect to %s", tmpURL.toString()));
                con.disconnect();
            } else {
                break;
            }
        } while (true);
        return con;
    }

    private static Pattern urlPattern = Pattern.compile("(?i)[^/]+?\\.(php|html|htm|asp){1}?$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);

    public static URL sanitizeURL(String url) throws MalformedURLException {
        Matcher regexMatcher = urlPattern.matcher(url);
        if (regexMatcher.find()) {
            //URL has filename, replace it
            return new URL(regexMatcher.replaceAll("jsonrpc.php"));
        } else {
            //URL hs no filename, add one
            if (!url.endsWith("/"))
                url += "/";
            url += "jsonrpc.php";
            return new URL(url);
        }
    }

    private class KanboardAsync extends AsyncTask<KanboardRequest, Void, KanboardResult> {
        @Override
        protected KanboardResult doInBackground(KanboardRequest... params) {
            HttpURLConnection con = null;
            SSLContext sslcontext = null;
            int httpResponseCode = 0;
            List<JSONObject> responseList = new ArrayList<>();
            for (String s: params[0].JSON) {
                try {

                    Log.i(Constants.TAG, String.format("API: Send Request \"%s\"", params[0].Command));
                    if (BuildConfig.DEBUG) Log.v(Constants.TAG, String.format("API: Data:\n%s", s));
                    con = KanboardAPI.openConnection(kanboardURL, s);
                    if (con == null)
                        return new KanboardResult(params[0], new JSONObject[]{new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":0,\"message\":\"Unable to open connection\"},\"id\":null}")}, 0);

                    Log.i(Constants.TAG, String.format("API: HTTP Return Code for \"%s\": %d", params[0].Command, con.getResponseCode()));


                    BufferedReader in;
                    if (con.getResponseCode() < 400)
                        in = new BufferedReader(new InputStreamReader(con.getInputStream()));
                    else
                        in = new BufferedReader(new InputStreamReader(con.getErrorStream()));
                    String line;
                    StringBuilder responseStr = new StringBuilder();
                    while ((line = in.readLine()) != null) {
                        responseStr.append(line);
                    }
                    in.close();
                    httpResponseCode = con.getResponseCode();

                    Log.i(Constants.TAG, String.format("API: Received Response \"%s\"", params[0].Command));
                    if (BuildConfig.DEBUG)
                        Log.v(Constants.TAG, String.format("API: Data:\n%s", responseStr.toString()));
                    JSONObject response;
                    try {
                        response = new JSONObject(responseStr.toString());
                    } catch (JSONException e) {
                        //server response is not a JSON string, print it for debugging
                        Log.d(Constants.TAG, String.format(Locale.getDefault(), "Erroneous response: %s", responseStr.toString()));
                        response = new JSONObject();
                        response.put("jsonrpc", "2.0");
                        response.put("id", null);
                        response.put("error", new JSONObject()
                                .put("code", -50)
                                .put("responseCode", con.getResponseCode())
                                .put("message", con.getResponseMessage()))
                                .put("data", responseStr.toString());
                    }
                    responseList.add(response);

                    con.disconnect();

                } catch (SSLException e) {
                    Log.e(Constants.TAG, "API: SSL Error.");
                    e.printStackTrace();
                    try {
                        responseList.add(new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-40,\"message\":\"" + e.getLocalizedMessage() + "\"},\"id\":null}"));
                    } catch (JSONException e1) {
                        e1.printStackTrace();
                    }
                } catch (UnknownHostException e) {
                    Log.e(Constants.TAG, "API: Unknown Host.");
                    e.printStackTrace();
                    try {
                        responseList.add(new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-10,\"message\":\"Unknown Host\"},\"id\":null}"));
                    } catch (JSONException e1) {
                        e1.printStackTrace();
                    }
                } catch (ProtocolException e) {
                    Log.e(Constants.TAG, "API: Protocol Exception.");
                    e.printStackTrace();
                    try {
                        responseList.add(new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-20,\"message\":\"Protocol Exception\"},\"id\":null}"));
                    } catch (JSONException e1) {
                        e1.printStackTrace();
                    }
                } catch (SocketTimeoutException e) {
                    Log.e(Constants.TAG, String.format("API: Connection timed out.\tRequest: %s", params[0].Command));
                    e.printStackTrace();
                    try {
                        responseList.add(new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-30,\"message\":\"Network Timeout\"},\"id\":null}"));
                    } catch (JSONException e1) {
                        e1.printStackTrace();
                    }
                } catch (Exception e) {
                    Log.e(Constants.TAG, String.format("API: CatchAll.\tRequest: %s", params[0].Command));
                    e.printStackTrace();
                    try {
                        responseList.add(new JSONObject("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-1,\"message\":\"" + e.getMessage() + "\"},\"id\":null}\""));
                    } catch (JSONException e1) {
                        e1.printStackTrace();
                    }
                }
            }

            return new KanboardResult(params[0], responseList.toArray(new JSONObject[]{}), httpResponseCode);
        }
        @Override
        protected void onPostExecute(KanboardResult s) {
            // Handle Errors
            if (s == null || s.Result == null || s.Result[0] == null) {
                KanboardError res = new KanboardError(null, null, 0);
                for (OnErrorListener l: onErrorListeners)
                    l.onError(res);
                return;
            }
            if (s.Result[0].has("error") || s.ReturnCode >= 400) {
                Log.e(Constants.TAG, s.Result[0].toString());
                JSONObject err = s.Result[0].optJSONObject("error");
                KanboardError res = new KanboardError(s.Request, err, s.ReturnCode);
                for (OnErrorListener l: onErrorListeners)
                    l.onError(res);
                return;
            }

            // Handle Return Messages
            boolean success = false;
            if (s.Request.Command.equalsIgnoreCase("getMe")) {
                KanboardUserInfo res = null;
                try {
                    if (s.Result[0].has("result") && (s.ReturnCode < 400)) {
                        success = true;
                        JSONObject jso = s.Result[0].getJSONObject("result");
                        res = new KanboardUserInfo(jso);
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetMeListener l: onGetMeListeners)
                    l.onGetMe(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getVersion")) {
                String res;
                int[] version = new int[3];
                String tag = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = s.Result[0].getString("result");
                        if (res.startsWith("master")) {
                            Log.d(Constants.TAG, "Development Version");
                            version[0] = -1;
                            version[1] = -1;
                            version[2] = -1;
                            tag = res;
                        } else {
                            try {
                                Pattern regex = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)(?:,(.*)){0,1}$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
                                Matcher regexMatcher = regex.matcher(res);
                                if (regexMatcher.find()) {
                                    version[0] = Integer.parseInt(regexMatcher.group(1));
                                    version[1] = Integer.parseInt(regexMatcher.group(2));
                                    version[2] = Integer.parseInt(regexMatcher.group(3));
                                    if (regexMatcher.groupCount() == 4 && regexMatcher.group(4) != null)
                                        tag = regexMatcher.group(4).trim();
                                }
                            } catch (PatternSyntaxException ex) {
                                ex.printStackTrace();
                                throw ex;
                                // Syntax error in the regular expression
                            }
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetVersionListener l: onGetVersionListeners)
                    l.onGetVersion(success, version, tag);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getDefaultTaskColor")) {
                String res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = s.Result[0].getString("result");
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetDefaultColorListener l: onGetDefaultColorListeners) {
                    l.onGetDefaultColor(success, res);
                }
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getDefaultTaskColors")) {
                Dictionary<String, KanboardColor> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new Hashtable<>();
                        JSONObject jso = s.Result[0].getJSONObject("result");
                        Iterator<String> iter = jso.keys();
                        while (iter.hasNext()) {
                            String key = iter.next();
                            res.put(key, new KanboardColor(key, jso.getJSONObject(key)));
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetDefaultColorsListener l: onGetDefaultColorsListeners) {
                    l.onGetDefaultColors(success, res);
                }
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getMyProjectsList")) {
                List<KanboardProjectInfo> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONObject jso = s.Result[0].getJSONObject("result");
                        for (int i = 0; i < jso.names().length(); i++) {
                            String key = jso.names().getString(i);
                            res.add(new KanboardProjectInfo(Integer.parseInt(key), jso.optString(key, "")));
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                    for (OnGetMyProjectsListListener l: onGetMyProjectsListListeners)
                        l.onGetMyProjectsList(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getMyProjects")) {
                List<KanboardProject> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++) {
                            res.add(new KanboardProject(jsa.getJSONObject(i)));
                        }
                    }
                } catch (JSONException | MalformedURLException e) {
                    e.printStackTrace();
                }
                    for (OnGetMyProjectsListener l: onGetMyProjectsListeners)
                        l.onGetMyProjects(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getMyDashboard")) {
                KanboardDashboard res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        Object dash = s.Result[0].get("result");
                        res = new KanboardDashboard(dash);
                    }
                } catch (JSONException | MalformedURLException e) {
                    e.printStackTrace();
                    for (OnErrorListener l: onErrorListeners) {
                        try {
                            l.onError(new KanboardError(s.Request, new JSONObject("{\"message\": \"\", \"code\": -50}"), 200));
                        } catch (JSONException e1) {
                            e1.printStackTrace();
                        }
                    }
                }
                for (OnGetMyDashboardListener l: onGetMyDashboardListeners)
                    l.onGetMyDashboard(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getMyActivityStream")) {
                List<KanboardActivity> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++) {
                            res.add(new KanboardActivity(jsa.getJSONObject(i)));
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetMyActivityStreamListener l: onGetMyActivityStreamListeners)
                    l.onGetMyActivityStream(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getMyOverdueTasks")) {
                List<KanboardTask> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++) {
                            res.add(new KanboardTask(jsa.getJSONObject(i)));
                        }
                    }
                } catch (JSONException | MalformedURLException e) {
                    e.printStackTrace();
                }
                for (OnGetMyOverdueTasksListener l: onGetMyOverdueTasksListeners)
                    l.onGetMyOverdueTasks(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getProjectById")) {
                KanboardProject res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new KanboardProject(s.Result[0].getJSONObject("result"));
                    }
                } catch (JSONException | MalformedURLException e) {
                    e.printStackTrace();
                }
                for (OnGetProjectByIdListener l: onGetProjectByIdListeners)
                    l.onGetProjectById(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getColumns")) {
                List<KanboardColumn> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++) {
                            res.add(new KanboardColumn(jsa.getJSONObject(i)));
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetColumnsListener l: onGetColumnsListeners)
                    l.onGetColumns(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getProjectUsers")) {
                Hashtable<Integer, String> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new Hashtable<>();
                        JSONObject jso = s.Result[0].getJSONObject("result");
                        Iterator<String> iter = jso.keys();
                        while (iter.hasNext()) {
                            String key = iter.next();
                            res.put(Integer.parseInt(key), jso.getString(key));
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetProjectUsersListener l: onGetProjectUsersListeners)
                    l.onGetProjectUsers(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getAllTasks")) {
                List<KanboardTask> res = null;
                int status = 0;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        status = (new JSONObject(s.Request.JSON[0])).getJSONObject("params").getInt("status_id");
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++) {
                            res.add(new KanboardTask(jsa.getJSONObject(i)));
                        }
                    }
                } catch (JSONException | MalformedURLException e) {
                    e.printStackTrace();
                }
                for (OnGetAllTasksListener l: onGetAllTasksListener)
                    l.onGetAllTasks(success, status, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getOverdueTasksByProject")) {
                List<KanboardTask> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++) {
                            res.add(new KanboardTask(jsa.getJSONObject(i)));
                        }
                    }
                } catch (JSONException | MalformedURLException e) {
                    e.printStackTrace();
                }
                for (OnGetOverdueTasksByProjectListener l: onGetOverdueTasksByProjectListeners)
                    l.onGetOverdueTasksByProject(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("createTask")) {
                Integer res = null;
                try {
                    if (!s.Result[0].getString("result").equalsIgnoreCase("false")) {
                        success = true;
                        res = s.Result[0].getInt("result");
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnCreateTaskListener l: onCreateTaskListeners)
                    l.onCreateTask(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("updateTask")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnUpdateTaskListener l: onUpdateTaskListeners)
                    l.onUpdateTask(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getTask")) {
                KanboardTask res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new KanboardTask(s.Result[0].getJSONObject("result"));
                    }
                } catch (JSONException | MalformedURLException e) {
                    e.printStackTrace();
                }
                for (OnGetTaskListener l: onGetTaskListeners)
                    l.onGetTask(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("openTask")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnOpenTaskListener l: onOpenTaskListeners)
                    l.onOpenTask(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("closeTask")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnCloseTaskListener l: onCloseTaskListeners)
                    l.onCloseTask(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("removeTask")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnRemoveTaskListener l: onRemoveTaskListeners)
                    l.onRemoveTask(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("moveTaskPosition")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnMoveTaskPositionListener l: onMoveTaskPositionListeners)
                    l.onMoveTaskPosition(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("moveTaskToProject")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnMoveTaskToProjectListener l: onMoveTaskToProjectListeners)
                    l.onMoveTaskToProject(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("duplicateTaskToProject")) {
                Integer res = null;
                if (s.Result[0].has("result")) {
                    success = true;
                    res = s.Result[0].optInt("result");
                }
                for (OnDuplicateTaskToProjectListener l: onDuplicateTaskToProjectListeners)
                    l.onDuplicateTaskToProject(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getAllTaskFiles")) {
                List<KanboardTaskFile> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++) {
                            res.add(new KanboardTaskFile(jsa.getJSONObject(i)));
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetAllTaskFilesListener l: onGetAllTaskFilesListeners)
                    l.onGetAllTaskFiles(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("removeTaskFile")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnRemoveTaskFileListener l: onRemoveTaskFileListeners)
                    l.onRemoveTaskFile(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("downloadTaskFile")) {
                int id = s.Result[0].optInt("id");
                String data = s.Result[0].optString("result", "");
                success = !data.isEmpty();
                for (OnDownloadTaskFileListener l: onDownloadTaskFileListeners)
                    l.onDownloadTaskFile(success, id, data);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getActiveSwimlanes")) {
                List<KanboardSwimlane> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++) {
                            res.add(new KanboardSwimlane(jsa.getJSONObject(i)));
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetActiveSwimlanesListener l: onGetActiveSwimlanesListeners)
                    l.onGetActiveSwimlanes(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getDefaultSwimlane")) {
                KanboardSwimlane res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        JSONObject jso = s.Result[0].getJSONObject("result");
                        res = new KanboardSwimlane(jso);
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetSwimlaneListener l: onGetDefaultSwimlaneListeners)
                    l.onGetSwimlane(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getSwimlane")) {
                KanboardSwimlane res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new KanboardSwimlane(s.Result[0].getJSONObject("result"));
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetSwimlaneListener l: onGetSwimlaneListeners)
                    l.onGetSwimlane(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getAllCategories")) {
                List<KanboardCategory> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++) {
                            res.add(new KanboardCategory(jsa.getJSONObject(i)));
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetAllCategoriesListener l: onGetAllCategoriesListeners)
                    l.onGetAllCategories(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getCategory")) {
                KanboardCategory res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new KanboardCategory(s.Result[0].getJSONObject("result"));
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetCategoryListener l: onGetCategoryListeners)
                    l.onGetCategory(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("createComment")) {
                Integer res = null;
                try {
                    if (!s.Result[0].getString("result").equalsIgnoreCase("false")) {
                        success = true;
                        res = s.Result[0].getInt("result");
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnCreateCommentListener l: onCreateCommentListeners)
                    l.onCreateComment(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getAllComments")) {
                List<KanboardComment> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++)
                            res.add(new KanboardComment(jsa.getJSONObject(i)));
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetAllCommentsListener l: onGetAllCommentsListeners)
                    l.onGetAllComments(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("updateComment")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnUpdateCommentListener l: onUpdateCommentListeners)
                    l.onUpdateComment(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("removeComment")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnRemoveCommentListener l: onRemoveCommentListeners)
                    l.onRemoveComment(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("createSubtask")) {
                Integer res = null;
                try {
                    if (!s.Result[0].getString("result").equalsIgnoreCase("false")) {
                        success = true;
                        res = s.Result[0].getInt("result");
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnCreateSubtaskListener l: onCreateSubtaskListeners)
                    l.onCreateSubtask(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getAllSubtasks")) {
                List<KanboardSubtask> res = null;
                try {
                    if (s.Result[0].has("result")) {
                        success = true;
                        res = new ArrayList<>();
                        JSONArray jsa = s.Result[0].getJSONArray("result");
                        for (int i = 0; i < jsa.length(); i++)
                            res.add(new KanboardSubtask(jsa.getJSONObject(i)));
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                for (OnGetAllSubtasksListener l: onGetAllSubtasksListeners)
                    l.onGetAllSubtasks(success, res);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("updateSubtask")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnUpdateSubtaskListener l: onUpdateSubtaskListeners)
                    l.onUpdateSubtask(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("removeSubtask")) {
                success = s.Result[0].optBoolean("result", false);
                for (OnRemoveSubtaskListener l: onRemoveSubtaskListeners)
                    l.onRemoveSubtask(success);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("hasSubtaskTimer")) {
                try {
                    hasSubtaskTimerSet.remove(new JSONObject(s.Request.JSON[0]).getJSONArray("params").getInt(0));
                    success = s.Result[0].optBoolean("result", false);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                if (s.Request.Listener != null)
                    ((OnSubtaskTimetrackingListener) s.Request.Listener).onSubtaskTimetracking(success, 0);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("setSubtaskStartTime")) {
                success = s.Result[0].optBoolean("result", false);
                if (s.Request.Listener != null)
                    ((OnSubtaskTimetrackingListener) s.Request.Listener).onSubtaskTimetracking(success, 0);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("setSubtaskEndTime")) {
                success = s.Result[0].optBoolean("result", false);
                if (s.Request.Listener != null)
                    ((OnSubtaskTimetrackingListener) s.Request.Listener).onSubtaskTimetracking(success, 0);
                return;
            }

            if (s.Request.Command.equalsIgnoreCase("getSubtaskTimeSpent")) {
                double time;
                try {
                    getSubtaskTimeSpentSet.remove(new JSONObject(s.Request.JSON[0]).getJSONArray("params").getInt(0));
                    time = s.Result[0].getDouble("result");
                    success = true;
                } catch (JSONException e) {
                    time = 0;
                    success = false;
                }
                if (s.Request.Listener != null)
                    ((OnSubtaskTimetrackingListener) s.Request.Listener).onSubtaskTimetracking(success, time);
                return;
            }

//            if (s.Request.Command.equalsIgnoreCase("KD_getDashboard")) {
//                KanboardDashboard res = null;
//                try {
//                    if (s.Result[0].has("result") && s.Result[1].has("result") && s.Result[2].has("result")) {
//                        success = true;
//                        res = new KanboardDashboard(s.Result[0].getJSONObject("result"), s.Result[1].getJSONArray("result"), s.Result[2].getJSONArray("result"));
//                    }
//                } catch (JSONException | MalformedURLException e) {
//                    e.printStackTrace();
//                }
//                return;
//            }

//            if (s.Request.Command.equalsIgnoreCase("KD_getProjectById")) {
//                KanboardProject res = null;
//                try {
//                    if (s.Result[0].has("result") && s.Result[1].has("result") && s.Result[2].has("result") && s.Result[3].has("result") && s.Result[4].has("result") && s.Result[5].has("result") && s.Result[6].has("result")) {
//                        success = true;
//                        res = new KanboardProject(s.Result[0].optJSONObject("result"),
//                                                  s.Result[1].optJSONArray("result"),
//                                                  s.Result[2].optJSONArray("result"),
//                                                  s.Result[3].optJSONArray("result"),
//                                                  s.Result[4].optJSONArray("result"),
//                                                  s.Result[5].optJSONArray("result"),
//                                                  s.Result[6].optJSONArray("result"),
//                                                  s.Result[7].optJSONObject("result"));
//                    }
//                } catch (MalformedURLException e) {
//                    e.printStackTrace();
//                }
//                return;
//            }
        }
    }

    private URL kanboardURL;
    private ThreadPoolExecutor threadPoolExecutor;
    private HashSet<OnGetMyDashboardListener> onGetMyDashboardListeners = new HashSet<>();
    private HashSet<OnGetMyActivityStreamListener> onGetMyActivityStreamListeners = new HashSet<>();
    private HashSet<OnGetMyOverdueTasksListener> onGetMyOverdueTasksListeners = new HashSet<>();
    private HashSet<OnGetProjectByIdListener> onGetProjectByIdListeners = new HashSet<>();
    private HashSet<OnGetColumnsListener> onGetColumnsListeners = new HashSet<>();
    private HashSet<OnGetAllCommentsListener> onGetAllCommentsListeners = new HashSet<>();
    private HashSet<OnGetAllTasksListener> onGetAllTasksListener = new HashSet<>();
    private HashSet<OnGetOverdueTasksByProjectListener> onGetOverdueTasksByProjectListeners = new HashSet<>();
    private HashSet<OnGetTaskListener> onGetTaskListeners = new HashSet<>();
    private HashSet<OnGetAllCategoriesListener> onGetAllCategoriesListeners = new HashSet<>();
    private HashSet<OnGetCategoryListener> onGetCategoryListeners = new HashSet<>();
    private HashSet<OnGetProjectUsersListener> onGetProjectUsersListeners = new HashSet<>();
    private HashSet<OnGetSwimlaneListener> onGetSwimlaneListeners = new HashSet<>();
    private HashSet<OnGetActiveSwimlanesListener> onGetActiveSwimlanesListeners = new HashSet<>();
    private HashSet<OnGetSwimlaneListener> onGetDefaultSwimlaneListeners = new HashSet<>();
    private HashSet<OnGetAllSubtasksListener> onGetAllSubtasksListeners = new HashSet<>();
    private HashSet<OnGetMeListener> onGetMeListeners = new HashSet<>();
    private HashSet<OnCreateCommentListener> onCreateCommentListeners = new HashSet<>();
    private HashSet<OnUpdateCommentListener> onUpdateCommentListeners = new HashSet<>();
    private HashSet<OnRemoveCommentListener> onRemoveCommentListeners = new HashSet<>();
    private HashSet<OnCreateSubtaskListener> onCreateSubtaskListeners = new HashSet<>();
    private HashSet<OnUpdateSubtaskListener> onUpdateSubtaskListeners = new HashSet<>();
    private HashSet<OnRemoveSubtaskListener> onRemoveSubtaskListeners = new HashSet<>();
    private HashSet<OnOpenTaskListener> onOpenTaskListeners = new HashSet<>();
    private HashSet<OnCloseTaskListener> onCloseTaskListeners = new HashSet<>();
    private HashSet<OnRemoveTaskListener> onRemoveTaskListeners = new HashSet<>();
    private HashSet<OnCreateTaskListener> onCreateTaskListeners = new HashSet<>();
    private HashSet<OnUpdateTaskListener> onUpdateTaskListeners = new HashSet<>();
    private HashSet<OnMoveTaskPositionListener> onMoveTaskPositionListeners = new HashSet<>();
    private HashSet<OnMoveTaskToProjectListener> onMoveTaskToProjectListeners = new HashSet<>();
    private HashSet<OnDuplicateTaskToProjectListener> onDuplicateTaskToProjectListeners = new HashSet<>();
    private HashSet<OnGetVersionListener> onGetVersionListeners = new HashSet<>();
    private HashSet<OnErrorListener> onErrorListeners = new HashSet<>();
    private HashSet<OnGetDefaultColorListener> onGetDefaultColorListeners = new HashSet<>();
    private HashSet<OnGetDefaultColorsListener> onGetDefaultColorsListeners = new HashSet<>();
    private HashSet<OnGetMyProjectsListener> onGetMyProjectsListeners = new HashSet<>();
    private HashSet<OnGetMyProjectsListListener> onGetMyProjectsListListeners = new HashSet<>();
    private HashSet<OnGetAllTaskFilesListener> onGetAllTaskFilesListeners = new HashSet<>();
    private HashSet<OnRemoveTaskFileListener> onRemoveTaskFileListeners = new HashSet<>();
    private HashSet<OnDownloadTaskFileListener> onDownloadTaskFileListeners = new HashSet<>();

    //HashSets for AsyncTask limiting
    private HashSet<Integer> hasSubtaskTimerSet = new HashSet<>();
    private HashSet<Integer> getSubtaskTimeSpentSet = new HashSet<>();

    public KanboardAPI(String serverURL, final String username, final String password) throws IOException {
        Authenticator.setDefault(new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(username, password.toCharArray());
            }

        });
        kanboardURL = KanboardAPI.sanitizeURL(serverURL.trim());
        Log.i(Constants.TAG, String.format("Host uses %s", kanboardURL.getProtocol()));
//        threadPoolExecutor = new ThreadPoolExecutor(12, 12, 20, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(256));
        threadPoolExecutor = (ThreadPoolExecutor) AsyncTask.THREAD_POOL_EXECUTOR;
        threadPoolExecutor.setCorePoolSize(12);
        threadPoolExecutor.setMaximumPoolSize(12);
    }

    // Event Listeners

    public void addOnGetMyDashboardListener(@NonNull OnGetMyDashboardListener listener) {
        onGetMyDashboardListeners.add(listener);
    }

    public void removeOnGetMyDashboardListener(@NonNull OnGetMyDashboardListener listener) {
        onGetMyDashboardListeners.remove(listener);
    }

    public void addOnGetMyActivityStreamListener(@NonNull OnGetMyActivityStreamListener listener) {
        onGetMyActivityStreamListeners.add(listener);
    }

    public void removeOnGetMyActivityStreamListener(@NonNull OnGetMyActivityStreamListener listener) {
        onGetMyActivityStreamListeners.remove(listener);
    }

    public void addOnGetMyOverdueTasksListener(@NonNull OnGetMyOverdueTasksListener listener) {
        onGetMyOverdueTasksListeners.add(listener);
    }

    public void removeOnGetMyOverdueTasksListener(@NonNull OnGetMyOverdueTasksListener listener) {
        onGetMyOverdueTasksListeners.remove(listener);
    }

    public void addOnGetProjectByIdListener(@NonNull OnGetProjectByIdListener listener) {
        onGetProjectByIdListeners.add(listener);
    }

    public void removeOnGetProjectByIdListener(@NonNull OnGetProjectByIdListener listener) {
        onGetProjectByIdListeners.remove(listener);
    }

    public void addOnGetColumnsListener(@NonNull OnGetColumnsListener listener) {
        onGetColumnsListeners.add(listener);
    }

    public void removeOnGetColumnsListener(@NonNull OnGetColumnsListener listener) {
        onGetColumnsListeners.remove(listener);
    }

    public void addOnGetAllCommentsListener(@NonNull OnGetAllCommentsListener listener) {
        onGetAllCommentsListeners.add(listener);
    }

    public void removeOnGetAllCommentsListener(@NonNull OnGetAllCommentsListener listener) {
        onGetAllCommentsListeners.remove(listener);
    }

    public void addOnGetActiveSwimlanesListener(@NonNull OnGetActiveSwimlanesListener listener) {
        onGetActiveSwimlanesListeners.add(listener);
    }

    public void removeOnGetActiveSwimlanesListener(@NonNull OnGetActiveSwimlanesListener listener) {
        onGetActiveSwimlanesListeners.remove(listener);
    }

    public void addOnGetSwimlaneListener(@NonNull OnGetSwimlaneListener listener) {
        onGetSwimlaneListeners.add(listener);
    }

    public void removeOnGetSwimlaneListener(@NonNull OnGetSwimlaneListener listener) {
        onGetSwimlaneListeners.remove(listener);
    }

    public void addOnGetDefaultSwimlaneListener(@NonNull OnGetSwimlaneListener listener) {
        onGetDefaultSwimlaneListeners.add(listener);
    }

    public void removeOnGetDefaultSwimlaneListener(@NonNull OnGetSwimlaneListener listener) {
        onGetDefaultSwimlaneListeners.remove(listener);
    }

    public void addOnGetAllTasksListener(@NonNull OnGetAllTasksListener listener) {
        onGetAllTasksListener.add(listener);
    }

    public void removeGetAllTasksListener(@NonNull OnGetAllTasksListener listener) {
        onGetAllTasksListener.remove(listener);
    }

    public void addOnGetOverdueTasksByProjectListener(@NonNull OnGetOverdueTasksByProjectListener listener) {
        onGetOverdueTasksByProjectListeners.add(listener);
    }

    public void removeGetOverdueTasksByProjectListener(@NonNull OnGetOverdueTasksByProjectListener listener) {
        onGetOverdueTasksByProjectListeners.remove(listener);
    }

    public void addOnGetTaskListener(@NonNull OnGetTaskListener listener) {
        onGetTaskListeners.add(listener);
    }

    public void removeGetTaskListener(@NonNull OnGetTaskListener listener) {
        onGetTaskListeners.remove(listener);
    }

    public void addOnOpenTaskListener(@NonNull OnOpenTaskListener listener) {
        onOpenTaskListeners.add(listener);
    }

    public void removeOnOpenTaskListener(@NonNull OnOpenTaskListener listener) {
        onOpenTaskListeners.remove(listener);
    }

    public void addOnCloseTaskListener(@NonNull OnCloseTaskListener listener) {
        onCloseTaskListeners.add(listener);
    }

    public void removeOnCloseTaskListener(@NonNull OnCloseTaskListener listener) {
        onCloseTaskListeners.remove(listener);
    }

    public void addOnRemoveTaskListener(@NonNull OnRemoveTaskListener listener) {
        onRemoveTaskListeners.add(listener);
    }

    public void removeOnRemoveTaskListener(@NonNull OnRemoveTaskListener listener) {
        onRemoveTaskListeners.remove(listener);
    }

    public void addOnMoveTaskPositionListener(@NonNull OnMoveTaskPositionListener listener) {
        onMoveTaskPositionListeners.add(listener);
    }

    public void removeOnMoveTaskPositionListener(@NonNull OnMoveTaskPositionListener listener) {
        onMoveTaskPositionListeners.remove(listener);
    }

    public void addOnMoveTaskToProjectListener(@NonNull OnMoveTaskToProjectListener listener) {
        onMoveTaskToProjectListeners.add(listener);
    }

    public void removeOnMoveTaskToProjectListener(@NonNull OnMoveTaskToProjectListener listener) {
        onMoveTaskToProjectListeners.remove(listener);
    }

    public void addOnDuplicateTaskToProjectListener(@NonNull OnDuplicateTaskToProjectListener listener) {
        onDuplicateTaskToProjectListeners.add(listener);
    }

    public void removeOnDuplicateTaskToProjectListener(@NonNull OnDuplicateTaskToProjectListener listener) {
        onDuplicateTaskToProjectListeners.remove(listener);
    }

    public void addOnGetAllCategoriesListener(@NonNull OnGetAllCategoriesListener listener) {
        onGetAllCategoriesListeners.add(listener);
    }

    public void removeOnGetAllCategoriesListener(@NonNull OnGetAllCategoriesListener listener) {
        onGetAllCategoriesListeners.remove(listener);
    }

    public void addOnGetCategoryListener(@NonNull OnGetCategoryListener listener) {
        onGetCategoryListeners.add(listener);
    }

    public void removeOnGetCategoryListener(@NonNull OnGetCategoryListener listener) {
        onGetCategoryListeners.remove(listener);
    }

    public void addOnGetProjectUsersListener(@NonNull OnGetProjectUsersListener listener) {
        onGetProjectUsersListeners.add(listener);
    }

    public void removeOnGetProjectUsersListener(@NonNull OnGetProjectUsersListener listener) {
        onGetProjectUsersListeners.remove(listener);
    }

    public void addOnGetAllSubtasksListener(@NonNull OnGetAllSubtasksListener listener) {
        onGetAllSubtasksListeners.add(listener);
    }

    public void removeOnGetAllSubtasksListener(@NonNull OnGetAllSubtasksListener listener) {
        onGetAllSubtasksListeners.remove(listener);
    }

    public void addOnGetMeListener(@NonNull OnGetMeListener listener) {
        onGetMeListeners.add(listener);
    }

    public void removeOnGetMeListener(@NonNull OnGetMeListener listener) {
        onGetMeListeners.remove(listener);
    }

    public void addOnCreateCommentListener(@NonNull OnCreateCommentListener listener) {
        onCreateCommentListeners.add(listener);
    }

    public void removeOnCreateCommentListener(@NonNull OnCreateCommentListener listener) {
        onCreateCommentListeners.remove(listener);
    }

    public void addOnUpdateCommentListener(@NonNull OnUpdateCommentListener listener) {
        onUpdateCommentListeners.add(listener);
    }

    public void removeOnUpdateCommentListener(@NonNull OnUpdateCommentListener listener) {
        onUpdateCommentListeners.remove(listener);
    }

    public void addOnRemoveCommentListener(@NonNull OnRemoveCommentListener listener) {
        onRemoveCommentListeners.add(listener);
    }

    public void removeOnRemoveCommentListener(@NonNull OnRemoveCommentListener listener) {
        onRemoveCommentListeners.remove(listener);
    }

    public void addOnCreateSubtaskListener(@NonNull OnCreateSubtaskListener listener) {
        onCreateSubtaskListeners.add(listener);
    }

    public void removeOnCreateSubtaskListener(@NonNull OnCreateSubtaskListener listener) {
        onCreateSubtaskListeners.remove(listener);
    }

    public void addOnUpdateSubtaskListener(@NonNull OnUpdateSubtaskListener listener) {
        onUpdateSubtaskListeners.add(listener);
    }

    public void removeOnUpdateSubtaskListener(@NonNull OnUpdateSubtaskListener listener) {
        onUpdateSubtaskListeners.remove(listener);
    }

    public void addOnRemoveSubtaskListener(@NonNull OnRemoveSubtaskListener listener) {
        onRemoveSubtaskListeners.add(listener);
    }

    public void removeOnRemoveSubtaskListener(@NonNull OnRemoveSubtaskListener listener) {
        onRemoveSubtaskListeners.remove(listener);
    }

    public void addOnCreateTaskListener(@NonNull OnCreateTaskListener listener) {
        onCreateTaskListeners.add(listener);
    }

    public void removeOnCreateTaskListener(@NonNull OnCreateTaskListener listener) {
        onCreateTaskListeners.remove(listener);
    }

    public void addOnUpdateTaskListener(@NonNull OnUpdateTaskListener listener) {
        onUpdateTaskListeners.add(listener);
    }

    public void removeOnUpdateTaskListener(@NonNull OnUpdateTaskListener listener) {
        onUpdateTaskListeners.remove(listener);
    }

    public void addOnGetVersionListener(@NonNull OnGetVersionListener listener) {
        onGetVersionListeners.add(listener);
    }

    public void removeOnGetVersionListener(@NonNull OnGetVersionListener listener) {
        onGetVersionListeners.remove(listener);
    }

    public void addOnGetDefaultColorListener(@NonNull OnGetDefaultColorListener listener) {
        onGetDefaultColorListeners.add(listener);
    }

    public void removeOnGetDefaultColorListener(@NonNull OnGetDefaultColorListener listener) {
        onGetDefaultColorListeners.remove(listener);
    }

    public void addOnGetDefaultColorsListener(@NonNull OnGetDefaultColorsListener listener) {
        onGetDefaultColorsListeners.add(listener);
    }

    public void removeOnGetDefaultColorsListener(@NonNull OnGetDefaultColorsListener listener) {
        onGetDefaultColorsListeners.remove(listener);
    }

    public void addOnGetMyProjectsListener(@NonNull OnGetMyProjectsListener listener) {
        onGetMyProjectsListeners.add(listener);
    }

    public void removeOnGetMyProjectsListener(@NonNull OnGetMyProjectsListener listener) {
        onGetMyProjectsListeners.remove(listener);
    }

    public void addErrorListener(@NonNull OnErrorListener listener) {
        onErrorListeners.add(listener);
    }

    public void removeErrorListener(@NonNull OnErrorListener listener) {
        onErrorListeners.remove(listener);
    }

    public void addOnGetMyProjectsListListeners(@NonNull OnGetMyProjectsListListener listener) {
        onGetMyProjectsListListeners.add(listener);
    }

    public void removeOnGetMyProjectsListListeners(@NonNull OnGetMyProjectsListListener listener) {
        onGetMyProjectsListListeners.remove(listener);
    }

    public void addOnGetAllTaskFilesListListeners(@NonNull OnGetAllTaskFilesListener listener) {
        onGetAllTaskFilesListeners.add(listener);
    }

    public void removeOnGetAllTaskFilesListeners(@NonNull OnGetAllTaskFilesListener listener) {
        onGetAllTaskFilesListeners.remove(listener);
    }

    public void addOnRemoveTaskFileListeners(@NonNull OnRemoveTaskFileListener listener) {
        onRemoveTaskFileListeners.add(listener);
    }

    public void removeRemoveTaskFileListeners(@NonNull OnRemoveTaskFileListener listener) {
        onRemoveTaskFileListeners.remove(listener);
    }

    public void addOnDownloadTaskFileListeners(@NonNull OnDownloadTaskFileListener listener) {
        onDownloadTaskFileListeners.add(listener);
    }

    public void removeDownloadTaskFileListeners(@NonNull OnDownloadTaskFileListener listener) {
        onDownloadTaskFileListeners.remove(listener);
    }

    // API Calls

    public void getMe() {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getMe());
    }

    public void getVersion() {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getVersion());
    }

    public void getDefaultTaskColors() {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getDefaultTaskColors());
    }

    public void getDefaultTaskColor() {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getDefaultTaskColor());
    }

    public void getMyProjectsList() {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getMyProjectsList());
    }

    public void getMyProjects() {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getMyProjects());
    }

    public void getMyDashboard() {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getMyDashboard());
    }

    public void getMyActivityStream() {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getMyActivityStream());
    }

    public void getMyOverdueTasks() {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getMyOverdueTasks());
    }

    public void getProjectById(int projectid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getProjectById(projectid));
    }

    public void getProjectUsers(int projectid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getProjectUsers(projectid));
    }

    public void getColumns(int projectid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getColumns(projectid));
    }

    public void getAllTasks(int projectid, int status) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getAllTasks(projectid, status));
    }

    public void getOverdueTasksByProject(int projectid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getOverdueTasksByProject(projectid));
    }

    public void createTask(@NonNull String title, int projectid, @Nullable String colorid,
                           @Nullable Integer columnid, @Nullable Integer ownerid,
                           @Nullable Integer creatorid, @Nullable Date duedate,
                           @Nullable String description, @Nullable Integer categoryid,
                           @Nullable Integer score, @Nullable Integer swimlaneid,
                           @Nullable Integer priority, @Nullable Integer recurrencestatus,
                           @Nullable Integer recurrencetrigger, @Nullable Integer recurrencefactor,
                           @Nullable Integer recurrencetimeframe, @Nullable Integer recurrencebasedate,
                           @Nullable String[] tags, @Nullable Date starteddate) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.createTask(title,
                projectid, colorid, columnid, ownerid, creatorid, duedate, description, categoryid,
                score, swimlaneid, priority, recurrencestatus, recurrencetrigger, recurrencefactor,
                recurrencetimeframe, recurrencebasedate, tags, starteddate));
    }

    public void updateTask(int taskid, @NonNull String title, @Nullable String colorid,
                           @Nullable Integer ownerid,
                           @Nullable Date duedate,
                           @Nullable String description, @Nullable Integer categoryid,
                           @Nullable Integer score,
                           @Nullable Integer priority, @Nullable Integer recurrencestatus,
                           @Nullable Integer recurrencetrigger, @Nullable Integer recurrencefactor,
                           @Nullable Integer recurrencetimeframe, @Nullable Integer recurrencebasedate,
                           @Nullable String[] tags, @Nullable Date starteddate) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.updateTask(taskid,
                title, colorid, ownerid, duedate, description, categoryid, score,
                priority, recurrencestatus, recurrencetrigger, recurrencefactor,
                recurrencetimeframe, recurrencebasedate, tags, starteddate));
    }

    public void getTask(int taskid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getTask(taskid));
    }

    public void openTask(int taskid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.openTask(taskid));
    }

    public void closeTask(int taskid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.closeTask(taskid));
    }

    public void removeTask(int taskid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.removeTask(taskid));
    }

    public void moveTaskPosition(int projectid, int taskid, int columnid, int position,
                                 int swimlaneid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor,
                KanboardRequest.moveTaskPosition(projectid, taskid, columnid, position, swimlaneid));
    }

    public void moveTaskToProject(int projectid, int taskid, @Nullable Integer columnid,
                                  @Nullable Integer position, @Nullable Integer swimlaneid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor,
                KanboardRequest.moveTaskToProject(projectid, taskid, columnid, position, swimlaneid));
    }

    public void duplicateTaskToProject(int projectid, int taskid, @Nullable Integer columnid,
                                  @Nullable Integer position, @Nullable Integer swimlaneid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor,
                KanboardRequest.duplicateTaskToProject(projectid, taskid, columnid, position, swimlaneid));
    }

    public void getAllComments(int taskid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getAllComments(taskid));
    }

    public void createComment(int taskid, int userid, String comment) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.createComment(taskid, userid, comment));
    }

    public void updateComment(int commentid, @NonNull String comment) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.updateComment(commentid, comment));
    }

    public void removeComment(int commentid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.removeComment(commentid));
    }

    public void getActiveSwimlanes(int projectid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getActiveSwimlanes(projectid));
    }

    public void getDefaultSwimlane(int projectid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getDefaultSwimlane(projectid));
    }

    public void getSwimlane(int swimlaneid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getSwimlane(swimlaneid));
    }

    public void getAllCategories(int projectid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getAllCategories(projectid));
    }

    public void getCategory(int categoryid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getCategrory(categoryid));
    }

    public void getAllSubtasks(int taskid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getAllSubtasks(taskid));
    }

    public void createSubtask(int taskid, @NonNull String title, @Nullable Integer userid,
                              @Nullable Integer timeestimated, @Nullable Integer timespent, @Nullable Integer status) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.createSubtask(taskid, title, userid, timeestimated, timespent, status));
    }

    public void updateSubtask(int subtaskid, int taskid, @Nullable String title, @Nullable Integer userid,
                              @Nullable Integer timeestimated, @Nullable Integer timespent, @Nullable Integer status) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.updateSubtask(subtaskid, taskid, title, userid, timeestimated, timespent, status));
    }

    public void removeSubtask(int subtaskid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.removeSubtask(subtaskid));
    }

    public void hasSubtaskTimer(int subtaskid, int userid, @NonNull OnSubtaskTimetrackingListener listener) {
        if (!hasSubtaskTimerSet.contains(subtaskid)) {
            hasSubtaskTimerSet.add(subtaskid);
            new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.hasSubtaskTimer(subtaskid, userid, listener));
        }
    }

    public void setSubtaskStartTime(int subtaskid, int userid, @NonNull OnSubtaskTimetrackingListener listener) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.setSubtaskStartTime(subtaskid, userid, listener));
    }

    public void setSubtaskEndTime(int subtaskid, int userid, @NonNull OnSubtaskTimetrackingListener listener) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.setSubtaskEndTime(subtaskid, userid, listener));
    }

    public void getSubtaskTimeSpent(int subtaskid, int userid, @NonNull OnSubtaskTimetrackingListener listener) {
        if (!getSubtaskTimeSpentSet.contains(subtaskid)) {
            getSubtaskTimeSpentSet.add(subtaskid);
            new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getSubtaskTimeSpent(subtaskid, userid, listener));
        }
    }

    public void getAllTaskFiles(int taskid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.getAllTaskFiles(taskid));
    }

    public void removeTaskFile(int fileid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.removeTaskFile(fileid));
    }

    public void downloadTaskFile(int fileid) {
        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.downloadTaskFile(fileid));
    }

//    public void KB_getDashboard() {
//        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.KD_getDashboard());
//    }
//
//    public void KB_getProjectById(int projectid) {
//        new KanboardAsync().executeOnExecutor(threadPoolExecutor, KanboardRequest.KD_getProjectById(projectid));
//    }

    // TODO: add API calls

    public static boolean StringToBoolean(String s) {
        return s.equalsIgnoreCase("true") || s.equalsIgnoreCase("yes") || s.equalsIgnoreCase("1");
    }

    @Nullable
    public static Integer parseColorString(String colorstring) {
        try {
            return Color.parseColor(colorstring);
        } catch (IllegalArgumentException e) {
            Pattern c = Pattern.compile("rgb *\\( *([0-9]+), *([0-9]+), *([0-9]+) *\\)");
            Matcher m = c.matcher(colorstring);

            if (m.matches())
            {
                return Color.rgb(Integer.valueOf(m.group(1)),  // r
                        Integer.valueOf(m.group(2)),  // g
                        Integer.valueOf(m.group(3))); // b
            }

        }
        return null;
    }
} @SuppressWarnings("unused")
public class KanboardActivity implements Serializable {
    private String Title;
    private String Content;
    private String Creator;
    private String CreatorUserName;
    private int CreatorId;
    private int Id;
    private int ProjectId;
    private int TaskId;
    private Date DateCreation;

    public KanboardActivity(JSONObject json) {
        Title = json.optString("event_title");
        Content = json.optString("event_content");
        Creator = json.optString("author");
        CreatorUserName = json.optString("author_username");
        CreatorId = json.optInt("creator_id");
        Id = json.optInt("id");
        ProjectId = json.optInt("project_id");
        TaskId = json.optInt("task_id");
        DateCreation = new Date(json.optLong("date_creation") * 1000);
    }

    public String getTitle() {
        return Title;
    }

    public String getContent() {
        return Content;
    }

    public String getCreator() {
        return Creator;
    }

    public String getCreatorUserName() {
        return CreatorUserName;
    }

    public int getCreatorId() {
        return CreatorId;
    }

    public int getId() {
        return Id;
    }

    public int getProjectId() {
        return ProjectId;
    }

    public int getTaskId() {
        return TaskId;
    }

    public Date getDateCreation() {
        return DateCreation;
    }

    @Override
    public String toString() {
        return this.Title + " " + this.DateCreation.toString();
    }
} @SuppressWarnings("unused")
public class KanboardCategory implements Serializable {
    private int Id;
    private String Name;
    private int ProjectId;

    public KanboardCategory(JSONObject category) {
        Id = category.optInt("id");
        Name = category.optString("name");
        ProjectId = category.optInt("project_id");
    }

    public int getId() {
        return Id;
    }

    public String getName() {
        return Name;
    }

    public int getProjectId() {
        return ProjectId;
    }

    @Override
    public String toString() {
        return this.Name;
    }
} @SuppressWarnings("unused")
public class KanboardColor implements Serializable {
    private String Id;
    private String Name;
    private Integer Background;
    private Integer Border;

    public KanboardColor(String id, JSONObject color) {
        Id = id;
        Name = color.optString("name");
        Background = KanboardAPI.parseColorString(color.optString("background"));
        Border = KanboardAPI.parseColorString(color.optString("border"));
    }

    public String getId() {
        return Id;
    }

    public String getName() {
        return Name;
    }

    public Integer getBackground() {
        return Background;
    }

    public Integer getBorder() {
        return Border;
    }
} @SuppressWarnings("unused")
public class KanboardColumn implements Comparable<KanboardColumn>, Serializable {
    private int Id;
    private int Position;
    private int TaskLimit;
    private int NumberTasks;
    private String Title;
    private String Description;
    private int ProjectId;
    private boolean HideInDashboard;

    public int getId() {
        return Id;
    }

    public int getPosition() {
        return Position;
    }

    public int getTaskLimit() {
        return TaskLimit;
    }

    public int getNumberTasks() {
        return NumberTasks;
    }

    public String getTitle() {
        return Title;
    }

    public String getDescription() {
        return Description;
    }

    public int getProjectId() {
        return ProjectId;
    }

    public boolean getHideInDashboard() {
        return HideInDashboard;
    }

    public KanboardColumn(JSONObject json) {
        Id = json.optInt("id");
        Position = json.optInt("position");
        TaskLimit = json.optInt("task_limit");
        NumberTasks = json.optInt("nb_tasks");
        Title = json.optString("title");
        Description = json.optString("description");
        ProjectId = json.optInt("project_id");
        HideInDashboard = KanboardAPI.StringToBoolean(json.optString("hide_in_dashboard"));
    }

    @Override
    public int compareTo(@NonNull KanboardColumn o) {
        return this.Title.compareTo(o.Title);
    }

    @Override
    public String toString() {
        return this.Title;
    }
} @SuppressWarnings("unused")
public class KanboardDashboard implements Serializable {
    private List<KanboardProject> Projects;
    private List<KanboardTask> Tasks;
    private List<KanboardSubtask> Subtasks;
    private Dictionary<Integer, List<KanboardTask>> GroupedTasks;
    private List<KanboardTask> OverdueTasks = new ArrayList<>();
    private List<KanboardActivity> Activities = new ArrayList<>();

    private boolean newDashFormat = false;

    public KanboardDashboard(@NonNull Object dashboard) throws MalformedURLException {
        this(dashboard, null, null);
    }

    public KanboardDashboard(@NonNull Object dashboard, @Nullable JSONArray overdue, @Nullable JSONArray activities) throws MalformedURLException {
        GroupedTasks = new Hashtable<>();
        Projects = new ArrayList<>();
        if (dashboard instanceof JSONObject) {
            Log.i(Constants.TAG, "Old Dashboard");
            JSONObject dash = (JSONObject) dashboard;
            JSONArray projects = dash.optJSONArray("projects");
            if (projects != null)
                for (int i = 0; i < projects.length(); i++) {
                    KanboardProject tmpProject = new KanboardProject(projects.optJSONObject(i));
                    Projects.add(tmpProject);
                    GroupedTasks.put(tmpProject.getId(), new ArrayList<KanboardTask>());
                }
            Tasks = new ArrayList<>();
            JSONArray tasks = dash.optJSONArray("tasks");
            if (tasks != null)
                for (int i = 0; i < tasks.length(); i++) {
                    KanboardTask tmpTask = new KanboardTask(tasks.optJSONObject(i));
                    Tasks.add(tmpTask);
                    GroupedTasks.get(tmpTask.getProjectId()).add(tmpTask);
                }
            Subtasks = new ArrayList<>();
            JSONArray subtasks = dash.optJSONArray("subtasks");
            if (subtasks != null)
                for (int i = 0; i < subtasks.length(); i++)
                    Subtasks.add(new KanboardSubtask(subtasks.optJSONObject(i)));
        } else {
            Log.i(Constants.TAG, "New Dashboard");
            newDashFormat = true;
            Tasks = new ArrayList<>();
            JSONArray dash = (JSONArray) dashboard;
            for (int i = 0; i < dash.length(); i++) {
                JSONObject item = dash.optJSONObject(i);
                KanboardTask tmpTask = new KanboardTask(item);
                Tasks.add(tmpTask);
                if (!((Hashtable) GroupedTasks).containsKey(tmpTask.getProjectId())) {
                    GroupedTasks.put(tmpTask.getProjectId(), new ArrayList<KanboardTask>());
                    try {
                        Projects.add(new KanboardProject(new JSONObject(String.format("{\"id\": \"%d\",\"name\": \"%s\"}", tmpTask.getProjectId(), tmpTask.getProjectName()))));
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
                GroupedTasks.get(tmpTask.getProjectId()).add(tmpTask);
            }
        }

        if (overdue != null) {
            OverdueTasks = new ArrayList<>();
            for (int i = 0; i < overdue.length(); i++) {
                OverdueTasks.add(new KanboardTask(overdue.optJSONObject(i)));
            }
        }

        if (activities != null) {
            Activities = new ArrayList<>();
            for (int i = 0; i < activities.length(); i++) {
                Activities.add(new KanboardActivity(activities.optJSONObject(i)));
            }
        }
    }

    public void setExtra(List<KanboardTask> overdueTasks, List<KanboardActivity> activities, List<KanboardProject> projectList) {
        OverdueTasks = overdueTasks;
        Activities = activities;
        if (newDashFormat) {
            List<KanboardProject> tmpProject = new ArrayList<>();
            for (KanboardProject pn : projectList) {
                for (KanboardProject po : Projects) {
                    if (po.getId() == pn.getId()) {
                        tmpProject.add(pn);
                        break;
                    }
                }
            }
            if (projectList.size() == Projects.size())
                Projects = tmpProject;
        }
    }

    public List<KanboardProject> getProjects() {
        return Projects;
    }

    public List<KanboardTask> getTasks() {
        return Tasks;
    }

    public List<KanboardSubtask> getSubtasks() {
        return Subtasks;
    }

    public Dictionary<Integer, List<KanboardTask>> getGroupedTasks() {
        return GroupedTasks;
    }

    public List<KanboardTask> getOverdueTasks() {
        return OverdueTasks;
    }

    public List<KanboardActivity> getActivities() {
        return Activities;
    }
} @SuppressWarnings("unused")
public class KanboardError {
    public final int Code;
    public final String Message;
    public final KanboardRequest Request;
    public final int HTTPReturnCode;

    public KanboardError(KanboardRequest request, JSONObject json, int httpreturcode) {
        if (json != null) {
            Code = json.optInt("code");
            Message = json.optString("message");
        } else {
            Code = -2;
            Message = "Null Object";
        }
        Request = request;
        HTTPReturnCode = httpreturcode;
    }
} @SuppressWarnings("unused")
public class KanboardProject implements Comparable<KanboardProject>, Serializable {
    private int Id;
    private String Name;
    private int OwnerId;
    private String Description;
    private String Identifier;
    private String Token;
    private boolean IsActive;
    private boolean IsPublic;
    private boolean IsPrivate;
    private boolean IsEverybodyAllowed;
    private boolean HasHiddenSwimlanes = false;
    private Date StartDate;
    private Date EndDate;
    private Date LastModified;
    private int NumberActiveTasks;
    private URL ListURL;
    private URL BoardURL;
    private URL CalendarURL;
    private List<KanboardColumn> Columns;
    private List<KanboardCategory> Categories;
    private List<KanboardSwimlane> Swimlanes;
    private List<KanboardTask> ActiveTasks;
    private Dictionary<Integer, Dictionary<Integer, List<KanboardTask>>> GroupedActiveTasks;
    private List<KanboardTask> InactiveTasks;
    private Dictionary<Integer, List<KanboardTask>> GroupedInactiveTasks;
    private List<KanboardTask> OverdueTasks;
    private Dictionary<Integer, List<KanboardTask>> GroupedOverdueTasks;
    private Dictionary<Integer, KanboardTask> TaskHashtable;
    private Dictionary<Integer, KanboardCategory> CategoryHashtable;
    private Dictionary<Integer, String> ProjectUsers;
    // TODO: add priority values to project details

    public KanboardProject(@NonNull JSONObject project) throws MalformedURLException {
        this(project, null, null, null, null, null, null, null);
    }

    public KanboardProject(@NonNull JSONObject project, @Nullable JSONArray columns, @Nullable JSONArray swimlanes,
                           @Nullable JSONArray categories, @Nullable JSONArray activetasks,
                           @Nullable JSONArray inactivetasks, @Nullable JSONArray overduetasks,
                           @Nullable JSONObject projectusers) throws MalformedURLException {
        Id = project.optInt("id");
        Name = project.optString("name");
        OwnerId = project.optInt("owner_id");
        Object desc = project.opt("description");
        Description = desc == null ? "" : desc.toString();
        Identifier = project.optString("identifier");
        Token = project.optString("token");
        IsActive = KanboardAPI.StringToBoolean(project.optString("is_active"));
        IsPublic = KanboardAPI.StringToBoolean(project.optString("is_public"));
        IsPrivate = KanboardAPI.StringToBoolean(project.optString("is_private"));
        IsEverybodyAllowed = KanboardAPI.StringToBoolean(project.optString("is_everybody_allowed"));
        long tmpDate = project.optLong("start_date");
        if (tmpDate > 0)
            StartDate = new Date(tmpDate * 1000);
        else
            StartDate = null;
        tmpDate = project.optLong("end_date");
        if (tmpDate > 0)
            EndDate = new Date(tmpDate * 1000);
        else
            EndDate = null;
        tmpDate = project.optLong("last_modified");
        if (tmpDate > 0)
            LastModified = new Date(tmpDate * 1000);
        else
            LastModified = null;
        NumberActiveTasks = project.optInt("nb_active_tasks");
        JSONObject urls = project.optJSONObject("url");
        if (urls != null) {
            ListURL = new URL(urls.optString("list"));
            BoardURL = new URL(urls.optString("board"));
            if (urls.has("calendar")) {
                CalendarURL = new URL(urls.optString("calendar"));
            } else {
                CalendarURL = null;
            }
        } else {
            ListURL = null;
            BoardURL = null;
            CalendarURL = null;
        }

        GroupedActiveTasks = new Hashtable<>();
        GroupedInactiveTasks = new Hashtable<>();
        GroupedOverdueTasks = new Hashtable<>();
        TaskHashtable = new Hashtable<>();
        CategoryHashtable = new Hashtable<>();

        Columns = new ArrayList<>();
        JSONArray cols = project.optJSONArray("columns");
        if (columns != null) {
            for (int i = 0; i < columns.length(); i++) {
                KanboardColumn tmpCol = new KanboardColumn(columns.optJSONObject(i));
                Columns.add(tmpCol);
                GroupedActiveTasks.put(tmpCol.getId(), new Hashtable<Integer, List<KanboardTask>>());
            }
        }
        else if (cols != null) {
            for (int i = 0; i < cols.length(); i++)
                Columns.add(new KanboardColumn(cols.optJSONObject(i)));
        }

        Swimlanes = new ArrayList<>();
        if (swimlanes != null) {
            for (int i = 0; i < swimlanes.length(); i++) {
                KanboardSwimlane tmpSwim = new KanboardSwimlane(swimlanes.optJSONObject(i));
                Swimlanes.add(tmpSwim);
                for (KanboardColumn c: Columns) {
                    GroupedActiveTasks.get(c.getId()).put(tmpSwim.getId(), new ArrayList<KanboardTask>());

                GroupedInactiveTasks.put(tmpSwim.getId(), new ArrayList<KanboardTask>());
                GroupedOverdueTasks.put(tmpSwim.getId(), new ArrayList<KanboardTask>());
                }
            }
        }

        Categories = new ArrayList<>();
        if (categories != null) {
            for (int i = 0; i < categories.length(); i++) {
                KanboardCategory tmpCategory = new KanboardCategory(categories.optJSONObject(i));
                Categories.add(tmpCategory);
                CategoryHashtable.put(tmpCategory.getId(), tmpCategory);
            }
        }

        ActiveTasks = new ArrayList<>();
        if (activetasks != null)
            for (int i = 0; i < activetasks.length(); i++) {
                KanboardTask tmpActiveTask = new KanboardTask(activetasks.optJSONObject(i));
                TaskHashtable.put(tmpActiveTask.getId(), tmpActiveTask);
                ActiveTasks.add(tmpActiveTask);
                if (Swimlanes.size() > 0) {
                    if (((Hashtable<Integer, List<KanboardTask>>) GroupedActiveTasks.get(tmpActiveTask.getColumnId())).containsKey(tmpActiveTask.getSwimlaneId())) {
                        GroupedActiveTasks.get(tmpActiveTask.getColumnId()).get(tmpActiveTask.getSwimlaneId()).add(tmpActiveTask);
                    } else {
                        HasHiddenSwimlanes = true;
                    }
                }
            }

        InactiveTasks = new ArrayList<>();
        if (inactivetasks != null)
            for (int i = 0; i < inactivetasks.length(); i++) {
                KanboardTask tmpInactiveTask = new KanboardTask(inactivetasks.optJSONObject(i));
                TaskHashtable.put(tmpInactiveTask.getId(), tmpInactiveTask);
                InactiveTasks.add(tmpInactiveTask);
                if (Swimlanes.size() > 0) {
                    if (((Hashtable<Integer, List<KanboardTask>>) GroupedInactiveTasks).containsKey(tmpInactiveTask.getSwimlaneId())) {
                        GroupedInactiveTasks.get(tmpInactiveTask.getSwimlaneId()).add(tmpInactiveTask);
                    } else {
                        HasHiddenSwimlanes = true;
                    }
                }
            }

        OverdueTasks = new ArrayList<>();
        if (overduetasks != null)
            for (int i = 0; i < overduetasks.length(); i++) {
                KanboardTask tmpOverdueTask = new KanboardTask(overduetasks.optJSONObject(i));
                OverdueTasks.add(TaskHashtable.get(tmpOverdueTask.getId()));
                if (Swimlanes.size() > 0) {
                    if (((Hashtable<Integer, List<KanboardTask>>) GroupedOverdueTasks).containsKey(tmpOverdueTask.getSwimlaneId())) {
                        GroupedOverdueTasks.get(TaskHashtable.get(tmpOverdueTask.getId()).getSwimlaneId()).add(TaskHashtable.get(tmpOverdueTask.getId()));
                    } else {
                        HasHiddenSwimlanes = true;
                    }
                }
            }

        ProjectUsers = new Hashtable<>();
        if (projectusers != null) {
            for (Iterator<String> iter = projectusers.keys(); iter.hasNext();) {
                String key = iter.next();
                ProjectUsers.put(Integer.parseInt(key), projectusers.optString(key));
            }
        }
    }

    public void setExtra(@NonNull List<KanboardColumn> columns, @NonNull List<KanboardSwimlane> swimlanes,
                          @NonNull List<KanboardCategory> categories, @NonNull List<KanboardTask> activetasks,
                          @NonNull List<KanboardTask> inactivetasks, @NonNull List<KanboardTask> overduetasks,
                          @NonNull Dictionary<Integer, String> projectusers) {
        Columns = columns;
        Swimlanes = swimlanes;
        for (KanboardColumn col: Columns) {
            Dictionary<Integer, List<KanboardTask>> tmpTable = new Hashtable<>();
            for (KanboardSwimlane swim: Swimlanes) {
                tmpTable.put(swim.getId(), new ArrayList<KanboardTask>());
                GroupedInactiveTasks.put(swim.getId(), new ArrayList<KanboardTask>());
                GroupedOverdueTasks.put(swim.getId(), new ArrayList<KanboardTask>());
            }
            GroupedActiveTasks.put(col.getId(), tmpTable);
        }

        Categories = categories;
        for (KanboardCategory cat: Categories)
            CategoryHashtable.put(cat.getId(), cat);

        ActiveTasks = activetasks;
        Collections.sort(ActiveTasks);
        for (KanboardTask task: ActiveTasks) {
            if (Swimlanes.size() > 0) {
                if (((Hashtable<Integer, List<KanboardTask>>) GroupedActiveTasks.get(task.getColumnId())).containsKey(task.getSwimlaneId())) {
                    GroupedActiveTasks.get(task.getColumnId()).get(task.getSwimlaneId()).add(task);
                } else {
                    HasHiddenSwimlanes = true;
                }
            }
            TaskHashtable.put(task.getId(), task);
        }

        InactiveTasks = inactivetasks;
        Collections.sort(InactiveTasks);
        for (KanboardTask task: InactiveTasks) {
            if (Swimlanes.size() > 0) {
                if (((Hashtable<Integer, List<KanboardTask>>) GroupedInactiveTasks).containsKey(task.getSwimlaneId())) {
                    GroupedInactiveTasks.get(task.getSwimlaneId()).add(task);
                } else {
                    HasHiddenSwimlanes = true;
                }
            }
            TaskHashtable.put(task.getId(), task);
        }

        for (KanboardTask task: overduetasks) {
            OverdueTasks.add(TaskHashtable.get(task.getId()));
            if (Swimlanes.size() > 0) {
                if (((Hashtable<Integer, List<KanboardTask>>) GroupedOverdueTasks).containsKey(task.getSwimlaneId())) {
                    GroupedOverdueTasks.get(TaskHashtable.get(task.getId()).getSwimlaneId()).add(TaskHashtable.get(task.getId()));
                } else {
                    HasHiddenSwimlanes = true;
                }
            }
        }
        Collections.sort(OverdueTasks);
        Enumeration<Integer> enumSwimlanes = GroupedOverdueTasks.keys();
        while (enumSwimlanes.hasMoreElements()) {
            Integer swimKey = enumSwimlanes.nextElement();
            Collections.sort(GroupedOverdueTasks.get(swimKey));
        }
        ProjectUsers = projectusers;
    }

    public int getId() {
        return Id;
    }

    @Nullable
    public String getName() {
        return Name;
    }

    public int getOwnerId() {
        return OwnerId;
    }

    @NonNull
    public String getDescription() {
        return Description;
    }

    @Nullable
    public String getIdentifier() {
        return Identifier;
    }

    @Nullable
    public String getToken() {
        return Token;
    }

    public boolean getIsActive() {
        return IsActive;
    }

    public boolean getIsPublic() {
        return IsPublic;
    }

    public boolean getIsPrivate() {
        return IsPrivate;
    }

    public boolean getIsEverybodyAllowed() {
        return IsEverybodyAllowed;
    }

    @Nullable
    public Date getStartDate() {
        return StartDate;
    }

    @Nullable
    public Date getEndDate() {
        return EndDate;
    }

    @Nullable
    public Date getLastModified() {
        return LastModified;
    }

    public int getNumberActiveTasks() {
        return NumberActiveTasks;
    }

    @Nullable
    public URL getListURL() {
        return ListURL;
    }

    @Nullable
    public URL getBoardURL() {
        return BoardURL;
    }

    @Nullable
    public URL getCalendarURL() {
        return CalendarURL;
    }

    @NonNull
    public List<KanboardColumn> getColumns() {
        return Columns;
    }

    @NonNull
    public List<KanboardCategory> getCategories() {
        return Categories;
    }

    @NonNull
    public List<KanboardSwimlane> getSwimlanes() {
        return Swimlanes;
    }

    @NonNull
    public List<KanboardTask> getActiveTasks() {
        return ActiveTasks;
    }

    @NonNull
    public Dictionary<Integer, Dictionary<Integer, List<KanboardTask>>> getGroupedActiveTasks() {
        return GroupedActiveTasks;
    }

    @NonNull
    public List<KanboardTask> getInactiveTasks() {
        return InactiveTasks;
    }

    public boolean hasHiddenSwimlanes() {
        return HasHiddenSwimlanes;
    }

    @NonNull
    public Dictionary<Integer, List<KanboardTask>> getGroupedInactiveTasks() {
        return GroupedInactiveTasks;
    }

    @NonNull
    public List<KanboardTask> getOverdueTasks() {
        return OverdueTasks;
    }

    @NonNull
    public Dictionary<Integer, List<KanboardTask>> getGroupedOverdueTasks() {
        return GroupedOverdueTasks;
    }

    @NonNull
    public Dictionary<Integer, KanboardTask> getTaskHashtable() {
        return TaskHashtable;
    }

    @NonNull
    public Dictionary<Integer, KanboardCategory> getCategoryHashtable() {
        return CategoryHashtable;
    }

    @NonNull
    public Dictionary<Integer, String> getProjectUsers() {
        return ProjectUsers;
    }

    @Override
    public int compareTo(@NonNull KanboardProject o) {
        return this.Name.compareTo(o.Name);
    }

    @Override
    public String toString() {
        return this.Name;
    }
} @SuppressWarnings("unused")
public class KanboardSwimlane implements Serializable {
    private int Id;
    private String Name;
    private String Description;
    private int Position;
    private int ProjectId;
    private boolean IsActive;
    private boolean IsDefault = false;

    public KanboardSwimlane(JSONObject swimlane) {
        if (swimlane.has("default_swimlane")) {
            Id = 0;
            Name = swimlane.optString("default_swimlane");
            IsActive = KanboardAPI.StringToBoolean(swimlane.optString("show_default_swimlane"));
            IsDefault = true;
        } else {
            Id = swimlane.optInt("id");
            Name = swimlane.optString("name");
            Description = swimlane.optString("description");
            Position = swimlane.optInt("position");
            ProjectId = swimlane.optInt("project_id");
            IsActive = KanboardAPI.StringToBoolean(swimlane.optString("is_active"));
        }
    }

    public int getId() {
        return Id;
    }

    public String getName() {
        return Name;
    }

    public String getDescription() {
        return Description.contentEquals("null") ? null : Description;
    }

    public int getPosition() {
        return Position;
    }

    public int getProjectId() {
        return ProjectId;
    }

    public boolean getIsActive() {
        return IsActive;
    }

    public boolean getIsDefault() {
        return IsDefault;
    }

    @Override
    public String toString() {
        return this.Name;
    }
} @SuppressWarnings("unused")
public class KanboardTask implements Comparable<KanboardTask>, Serializable {
    private int ID;
    private int ProjectID;
    private int ColumnID;
    private int SwimlaneID;
    private int CategoryID;
    private int CreatorID;
    private int OwnerID;
    private int Priority;
    private int Position;
    private boolean IsActive;
    private String Title;
    private String Description;
    private URL Url;
    private double TimeEstimated;
    private double TimeSpent;
    private String ColorId;
    private Integer ColorBackground = null;
    // Dashboard properties
    private String ProjectName;
    private String ColumnTitle;
    private Date DueDate;
    private Date CompletedDate;
    private Date StartedDate;
    private Date CreationDate;
    private Date ModificationDate;
    private Date MovedDate;
    // TODO: getTaskByID has additional properties
    // TODO: add color

    public KanboardTask(JSONObject json) throws MalformedURLException {
        ID = json.optInt("id", -1);
        ProjectID = json.optInt("project_id", -1);
        ColumnID = json.optInt("column_id", -1);
        SwimlaneID = json.optInt("swimlane_id", -1);
        CategoryID = json.optInt("category_id", -1);
        CreatorID = json.optInt("creator_id", -1);
        OwnerID = json.optInt("owner_id", -1);
        Priority = json.optInt("priority", -1);
        Position = json.optInt("position", -1);
        IsActive = KanboardAPI.StringToBoolean(json.optString("is_active"));
        Title = json.optString("title");
        Description = json.optString("description");
        if (json.has("url"))
            Url = new URL(json.optString("url"));
        else
            Url = null;
        TimeEstimated = json.optDouble("time_estimated");
        TimeSpent = json.optDouble("time_spent");
        // Dashboard properties
        ProjectName = json.optString("project_name");
        ColumnTitle = json.optString("column_title");
        long tmpTime = json.optLong("date_due");
        // Kanboard returns 0 when there was no time set
        if (tmpTime > 0)
            DueDate = new Date(tmpTime * 1000);
        else
            DueDate = null;
        tmpTime = json.optLong("date_completed");
        if (tmpTime > 0)
            CompletedDate = new Date(tmpTime * 1000);
        else
            CompletedDate = null;
        tmpTime = json.optLong("date_started");
        if (tmpTime > 0)
            StartedDate = new Date(tmpTime * 1000);
        else
            StartedDate = null;
        tmpTime = json.optLong("date_creation");
        if (tmpTime > 0)
            CreationDate = new Date(tmpTime * 1000);
        else
            CreationDate = null;
        tmpTime = json.optLong("date_modification");
        if (tmpTime > 0)
            ModificationDate = new Date(tmpTime * 1000);
        else
            ModificationDate = null;
        tmpTime = json.optLong("date_moved");
        if (tmpTime > 0)
            MovedDate = new Date(tmpTime * 1000);
        else
            MovedDate = null;

        ColorId = json.optString("color_id", "");

        if (json.has("color"))
            ColorBackground = KanboardAPI.parseColorString(json.optJSONObject("color").optString("background"));
    }

    public int getId() {
        return ID;
    }

    public int getProjectId() {
        return ProjectID;
    }

    public int getColumnId() {
        return ColumnID;
    }

    public int getSwimlaneId() {
        return SwimlaneID;
    }

    public int getCategoryId() {
        return CategoryID;
    }

    public int getOwnerId() {
        return OwnerID;
    }

    public int getCreatorId() {
        return CreatorID;
    }

    public int getPriority() {
        return Priority;
    }

    public int getPosition() {
        return Position;
    }

    public boolean getIsActive() {
        return IsActive;
    }

    public String getDescription() {
        return Description;
    }

    public String getTitle() {
        return Title;
    }

    public URL getUrl() {
        return Url;
    }

    public double getTimeEstimated() {
        return TimeEstimated;
    }

    public double getTimeSpent() {
        return TimeSpent;
    }

    public String getProjectName() {
        return ProjectName;
    }

    public String getColumnTitle() {
        return ColumnTitle;
    }

    public Date getDateCompleted() {
        return CompletedDate;
    }

    public Date getDateStarted() {
        return StartedDate;
    }

    public Date getDateDue() {
        return DueDate;
    }

    public Date getDateCreation() {
        return CreationDate;
    }

    public Date getDateModification() {
        return ModificationDate;
    }

    public Date getDateMoved() {
        return MovedDate;
    }

    public String getColorId() {
        return ColorId;
    }

    public Integer getColorBackground() {
        return ColorBackground;
    }

    @Override
    public int compareTo(@NonNull KanboardTask o) {
        return this.getPosition() - o.getPosition();
//        return this.Title.compareTo(o.Title);
    }

    @Override
    public String toString() {
        return this.Title;
    }
} @SuppressWarnings("unused")
public class KanboardUserInfo implements Comparable<KanboardUserInfo>, Serializable {
    private int Id;
    private String Username;
    private String Role;
    private boolean IsLDAPUser;
    private String Name;
    private String Email;
    private String GoogleId;
    private String GithubId;
    private String GitlabId;
    private int NotificationsEnabled;
    private String Timezone;
    private String Language;
    private int DisableLoginForm;
    private boolean TwofactorActivated;
    private String TwofactorSecret;
    private String Token;
    private int NotificationsFilter;
    private int NumberFailedLogin;
    private int LockExpirationDate;
    private boolean IsActive;
    private String AvatarPath;

    KanboardUserInfo(int id, String username, String role, boolean isLDAPUser, String name,
                     String email, String googleId, String githubId, String gitlabId,
                     int notificationsEnabled, String timezone, String language,
                     int disableLoginForm, boolean twofactorActivated, String twofactorSecret,
                     String token, int notificationsFilter, int numberFailedLogin,
                     int lockExpirationDate, boolean isActive, String avatarPath) {
        Id = id;
        Username = username;
        Role = role;
        IsLDAPUser = isLDAPUser;
        Name = name;
        Email = email;
        GoogleId = googleId;
        GithubId = githubId;
        GitlabId = gitlabId;
        NotificationsEnabled = notificationsEnabled;
        Timezone = timezone;
        Language = language;
        DisableLoginForm = disableLoginForm;
        TwofactorActivated = twofactorActivated;
        TwofactorSecret = twofactorSecret;
        Token = token;
        NotificationsFilter = notificationsFilter;
        NumberFailedLogin = numberFailedLogin;
        LockExpirationDate = lockExpirationDate;
        IsActive = isActive;
        AvatarPath = avatarPath;
    }

    KanboardUserInfo(JSONObject json) {
        Id = json.optInt("id", -1);
        Username = json.optString("username", "");
        Role = json.optString("role", "");
        IsLDAPUser = json.optBoolean("is_ldap_user", false);
        Name = json.optString("name", "");
        Email = json.optString("email", "");
        GoogleId = json.optString("google_id", "");
        GithubId = json.optString("github_id", "");
        GitlabId = json.optString("gitlab_id", "");
        NotificationsEnabled = json.optInt("notifications_enabled", -1);
        Timezone = json.optString("timezone", "");
        Language = json.optString("language", "");
        DisableLoginForm = json.optInt("disable_login_form", -1);
        TwofactorActivated = json.optBoolean("twofactor_activated", false);
        TwofactorSecret = json.optString("twofactor_secret", "");
        Token = json.optString("token", "");
        NotificationsFilter = json.optInt("notifications_filter", -1);
        NumberFailedLogin = json.optInt("nb_failed_login", -1);
        LockExpirationDate = json.optInt("lock_expiration_date", -1);
        IsActive = json.optBoolean("is_active", false);
        AvatarPath = json.optString("avatar_path", "");

    }

    public int getId() {
        return Id;
    }

    public String getUsername() {
        return Username;
    }

    public String getRole() {
        return Role;
    }

    public boolean getIsLDAPUser() {
        return IsLDAPUser;
    }

    public String getName() {
        return Name;
    }

    public String getEmail() {
        return Email;
    }

    public String getGoogleId() {
        return GoogleId;
    }

    public String getGithubId() {
        return GithubId;
    }

    public String getGitlabId() {
        return GitlabId;
    }

    public int getNotificationsEnabled() {
        return NotificationsEnabled;
    }

    public String getTimezone() {
        return Timezone;
    }

    public String getLanguage() {
        return Language;
    }

    public int getDisableLoginForm() {
        return DisableLoginForm;
    }

    public boolean getTwofactorActivated() {
        return TwofactorActivated;
    }

    public String getTwofactorSecret() {
        return TwofactorSecret;
    }

    public String getToken() {
        return Token;
    }

    public int getNotificationsFilter() {
        return NotificationsFilter;
    }

    public int getNumberFailedLogin() {
        return NumberFailedLogin;
    }

    public int getLockExpirationDate() {
        return LockExpirationDate;
    }

    public boolean getIsActive() {
        return IsActive;
    }

    public String getAvatarPath() {
        return AvatarPath;
    }

    @Override
    public int compareTo(@NonNull KanboardUserInfo o) {
        return this.Username.compareTo(o.Username);
    }
} public interface OnErrorListener {
    void onError(KanboardError error);
} public interface OnGetActiveSwimlanesListener {
    void onGetActiveSwimlanes(boolean success, List<KanboardSwimlane> result);
} public interface OnGetAllCategoriesListener {
    void onGetAllCategories(boolean success, List<KanboardCategory> result);
} public interface OnGetAllTasksListener {
    void onGetAllTasks(boolean success, int status, List<KanboardTask> result);
} public interface OnGetColumnsListener {
    void onGetColumns(boolean success, List<KanboardColumn> result);
} public interface OnGetDefaultColorsListener {
    void onGetDefaultColors(boolean success, Dictionary<String, KanboardColor> colors);
} public interface OnGetMeListener {
    void onGetMe(boolean success, KanboardUserInfo result);

} public interface OnGetMyActivityStreamListener {
    void onGetMyActivityStream(boolean success, List<KanboardActivity> result);
} public interface OnGetMyDashboardListener {
    void onGetMyDashboard(boolean success, KanboardDashboard result);
} public interface OnGetMyOverdueTasksListener {
    void onGetMyOverdueTasks(boolean success, List<KanboardTask> result);
} public interface OnGetMyProjectsListener {
    void onGetMyProjects(boolean success, List<KanboardProject> result);
} public interface OnGetOverdueTasksByProjectListener {
    void onGetOverdueTasksByProject(boolean success, List<KanboardTask> result);
} public interface OnGetProjectByIdListener {
    void onGetProjectById(boolean success, KanboardProject result);
} public interface OnGetProjectUsersListener {
    void onGetProjectUsers(boolean success, Dictionary<Integer, String> result);
}
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.NavigationView;
import android.support.design.widget.Snackbar;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.PagerTitleStrip;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import in.andres.kandroid.ArrayPagerAdapter;
import in.andres.kandroid.BuildConfig;
import in.andres.kandroid.Constants;
import in.andres.kandroid.R;
import in.andres.kandroid.kanboard.KanboardAPI;
import in.andres.kandroid.kanboard.KanboardActivity;
import in.andres.kandroid.kanboard.KanboardCategory;
import in.andres.kandroid.kanboard.KanboardColor;
import in.andres.kandroid.kanboard.KanboardColumn;
import in.andres.kandroid.kanboard.KanboardDashboard;
import in.andres.kandroid.kanboard.KanboardError;
import in.andres.kandroid.kanboard.KanboardProject;
import in.andres.kandroid.kanboard.KanboardSwimlane;
import in.andres.kandroid.kanboard.KanboardTask;
import in.andres.kandroid.kanboard.KanboardUserInfo;
import in.andres.kandroid.kanboard.events.OnErrorListener;
import in.andres.kandroid.kanboard.events.OnGetActiveSwimlanesListener;
import in.andres.kandroid.kanboard.events.OnGetAllCategoriesListener;
import in.andres.kandroid.kanboard.events.OnGetAllTasksListener;
import in.andres.kandroid.kanboard.events.OnGetColumnsListener;
import in.andres.kandroid.kanboard.events.OnGetDefaultColorsListener;
import in.andres.kandroid.kanboard.events.OnGetMeListener;
import in.andres.kandroid.kanboard.events.OnGetMyActivityStreamListener;
import in.andres.kandroid.kanboard.events.OnGetMyDashboardListener;
import in.andres.kandroid.kanboard.events.OnGetMyOverdueTasksListener;
import in.andres.kandroid.kanboard.events.OnGetMyProjectsListener;
import in.andres.kandroid.kanboard.events.OnGetOverdueTasksByProjectListener;
import in.andres.kandroid.kanboard.events.OnGetProjectByIdListener;
import in.andres.kandroid.kanboard.events.OnGetProjectUsersListener;
/*
 * 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;




public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {

    private String serverURL;
    private String username;
    private String password;

    private Context self;
    private ViewPager mViewPager;
    private PagerTitleStrip mTitleStrip;
    private ArrayPagerAdapter mArrayPager;
    private View mMainView;
    private View mProgress;
    private MenuItem refreshAction;
    private boolean progressVisible = false;
    private int progressBarCount = 0;

    private int mode = 0;

    private KanboardAPI kanboardAPI;
    private KanboardUserInfo Me;
//    private List<KanboardProjectInfo> mProjects;
    private KanboardProject mProject = null;
    private KanboardDashboard mDashboard = null;
    private Dictionary<String, KanboardColor> mColors = null;
    private List<KanboardProject> mProjectList = null;

    private List<KanboardActivity> mMyActivities = null;
    private List<KanboardTask> mMyOverduetasks = null;
    private List<KanboardColumn> mColumns = null;
    private List<KanboardSwimlane> mSwimlanes = null;
    private List<KanboardCategory> mCategories = null;
    private List<KanboardTask> mActiveTasks = null;
    private List<KanboardTask> mInactiveTasks = null;
    private List<KanboardTask> mOverdueTasks = null;
    private Dictionary<Integer, String> mProjectUsers = null;

    private OnErrorListener errorListener = new OnErrorListener() {
        @Override
        public void onError(KanboardError error) {
            new AlertDialog.Builder(self)
                    .setTitle("Error")
                    .setMessage("Code: " + Integer.toString(error.Code) + "\n" +
                            "Message: " + error.Message + "\n" +
                            "HTTP Response: " + Integer.toString(error.HTTPReturnCode))
                    .setNeutralButton("Dismiss", null)
                    .setIcon(android.R.drawable.ic_dialog_alert)
                    .show();
        }
    };
    private OnGetMeListener getMeListener = new OnGetMeListener() {
        @Override
        public void onGetMe(boolean success, KanboardUserInfo result) {
            boolean prog = !showProgress(false);
            if (success) {
                Me = result;
                ((TextView) findViewById(R.id.nav_serverurl)).setText(Me.getName());
                if (prog) {
                    if (mode == 0)
                        combineDashboard();
                    else
                        combineProject();
                }
            }
        }
    };
    private OnGetMyDashboardListener getMyDashboardListener = new OnGetMyDashboardListener() {
        @Override
        public void onGetMyDashboard(boolean success, KanboardDashboard result) {
            if (success) {
                mDashboard = result;
                if (!showProgress(false)) {
                    combineDashboard();
                }
            }
        }
    };
    private OnGetMyActivityStreamListener getMyActivityStreamListener = new OnGetMyActivityStreamListener() {