/*******************************************************************************
 * Copyright (c) 2015 Aptoide.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 ******************************************************************************/
package com.aptoide.amethyst.utils;

import android.accounts.AccountManager;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Toast;

import com.aptoide.amethyst.Aptoide;
import com.aptoide.amethyst.BuildConfig;
import com.aptoide.amethyst.R;
import com.aptoide.amethyst.adapters.SpannableRecyclerAdapter;
import com.aptoide.amethyst.configuration.AptoideConfiguration;
import com.aptoide.amethyst.database.AptoideDatabase;
import com.aptoide.amethyst.events.BusProvider;
import com.aptoide.amethyst.events.OttoEvents;
import com.aptoide.amethyst.preferences.AptoidePreferences;
import com.aptoide.amethyst.preferences.EnumPreferences;
import com.aptoide.amethyst.preferences.ManagerPreferences;
import com.aptoide.amethyst.preferences.Preferences;
import com.aptoide.amethyst.preferences.SecurePreferences;
import com.aptoide.amethyst.social.WebViewFacebook;
import com.aptoide.amethyst.social.WebViewTwitter;
import com.aptoide.amethyst.webservices.AddApkFlagRequest;
import com.aptoide.amethyst.webservices.ChangeUserRepoSubscription;
import com.aptoide.amethyst.webservices.ChangeUserSettingsRequest;
import com.aptoide.amethyst.webservices.Errors;
import com.aptoide.amethyst.webservices.GetAppRequest;
import com.aptoide.amethyst.webservices.GetUserRepoSubscriptions;
import com.aptoide.amethyst.webservices.SearchRequest;
import com.aptoide.amethyst.webservices.json.GetUserRepoSubscriptionJson;
import com.aptoide.amethyst.webservices.listeners.CheckSimpleStoreListener;
import com.aptoide.amethyst.webservices.v2.AddApkCommentVoteRequest;
import com.aptoide.amethyst.webservices.v3.RateAppRequest;
import com.aptoide.dataprovider.webservices.GetSimpleStoreRequest;
import com.aptoide.dataprovider.webservices.json.GenericResponseV2;
import com.aptoide.dataprovider.webservices.models.Constants;
import com.aptoide.dataprovider.webservices.models.Defaults;
import com.aptoide.dataprovider.webservices.models.ErrorResponse;
import com.aptoide.dataprovider.webservices.models.v7.GetAppMeta;
import com.aptoide.dataprovider.webservices.v7.CommunityGetstoreRequest;
import com.aptoide.dataprovider.webservices.v7.GetListViewItemsRequestv7;
import com.aptoide.dataprovider.webservices.v7.GetMoreVersionsAppRequest;
import com.aptoide.dataprovider.webservices.v7.GetStoreRequestv7;
import com.aptoide.dataprovider.webservices.v7.GetStoreWidgetRequestv7;
import com.aptoide.models.ApkPermission;
import com.aptoide.models.ApkPermissionGroup;
import com.aptoide.models.stores.Login;
import com.aptoide.models.stores.Store;
import com.crashlytics.android.Crashlytics;
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.octo.android.robospice.SpiceManager;
import com.octo.android.robospice.persistence.exception.SpiceException;
import com.octo.android.robospice.request.listener.RequestListener;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.text.WordUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.DateFormatSymbols;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.UnknownFormatConversionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;

/**
 * Created with IntelliJ IDEA.
 * User: rmateus
 * Date: 15-10-2013
 * Time: 16:39
 * To change this template use File | Settings | File Templates.
 */
public class AptoideUtils {

    public static SharedPreferences getSharedPreferences() {
        return PreferenceManager.getDefaultSharedPreferences(Aptoide.getContext());
    }

    public static int getPixels(Context context, int dipValue) {
        Resources r = context.getResources();
        int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, r.getDisplayMetrics());
        Logger.d("getPixels", "" + px);
        return px;
    }

    public static class UI {


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

        public static int getVerCode(Context context) {
            PackageManager manager = context.getPackageManager();
            try {
                PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
                return info.versionCode;
            } catch (PackageManager.NameNotFoundException e) {
                return -1;
            }
        }

        public static void toast(String text) {
            Toast.makeText(Aptoide.getContext(), text, Toast.LENGTH_LONG).show();
        }

        public static int getToolbarHeight(Context context) {
            final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
                    new int[]{R.attr.actionBarSize});
            int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
            styledAttributes.recycle();

            return toolbarHeight;
        }

        public static int getTabsHeight(Context context) {
            return (int) context.getResources().getDimension(R.dimen.tabsHeight);
        }

        private static char[] c = new char[]{'k', 'm', 'b', 't'};

        /**
         * Recursive implementation, invokes itself for each factor of a thousand, increasing the class on each invokation.
         *
         * @param n         the number to format
         * @param iteration in fact this is the class from the array c
         * @return a String representing the number n formatted in a cool looking way.
         */
        public static String coolFormat(double n, int iteration) {
            double d = ((long) n / 100) / 10.0;
            boolean isRound = (d * 10) % 10 == 0;//true if the decimal part is equal to 0 (then it's trimmed anyway)
            return (d < 1000 ? //this determines the class, i.e. 'k', 'm' etc
                    ((d > 99.9 || isRound || (!isRound && d > 9.99) ? //this decides whether to trim the decimals
                            (int) d * 10 / 10 : d + "" // (int) d * 10 / 10 drops the decimal
                    ) + "" + c[iteration])
                    : coolFormat(d, iteration + 1));
        }

        /**
         * Check if thread calling this method is the UI Thread.
         *
         * @return true if UI Thread
         */
        public static boolean isUiThread() {
            return Looper.getMainLooper().getThread() == Thread.currentThread();
        }

        public static String screenshotToThumb(Context context, String imageUrl, String orientation) {

            String screen = null;

            try {

                if (imageUrl.contains("_screen")) {

                    String sizeString = IconSizeUtils.generateSizeStringScreenshots(context, orientation);

                    String[] splitUrl = imageUrl.split("\\.(?=[^\\.]+$)");
                    screen = splitUrl[0] + "_" + sizeString + "." + splitUrl[1];

                } else {

                    String[] splitString = imageUrl.split("/");
                    StringBuilder db = new StringBuilder();
                    for (int i = 0; i != splitString.length - 1; i++) {
                        db.append(splitString[i]);
                        db.append("/");
                    }

                    db.append("thumbs/mobile/");
                    db.append(splitString[splitString.length - 1]);
                    screen = db.toString();
                }

            } catch (Exception e) {
                Logger.printException(e);
                Crashlytics.setString("imageUrl", imageUrl);
                Crashlytics.logException(e);
            }

            return screen;
        }

        public static String getScreenshotThumbnail(String imageUrl, String orientation) {

            String screen;
            String sizeString;

            if (imageUrl.contains("_screen")) {

                if(orientation != null && orientation.equals("portrait")){
                    sizeString = "192x320";
                }else{
                    sizeString = "256x160";
                }

                String[] splittedUrl = imageUrl.split("\\.(?=[^\\.]+$)");
                screen = splittedUrl[0] + "_" + sizeString + "." + splittedUrl[1];

            } else {


                String[] splitedString = imageUrl.split("/");
                StringBuilder db = new StringBuilder();
                for (int i = 0; i != splitedString.length - 1; i++) {
                    db.append(splitedString[i]);
                    db.append("/");
                }
                db.append("thumbs/mobile/");
                db.append(splitedString[splitedString.length - 1]);
                screen = db.toString();
            }

            return screen;
        }

        /**
         * Sets a color to a Drawable
         *
         * @param drawable
         * @param color
         */
        public static void setDrawableColor(Drawable drawable, int color) {

            // Assuming "color" is your target color
            float r = Color.red(color) / 255f;
            float g = Color.green(color) / 255f;
            float b = Color.blue(color) / 255f;
            float a = Color.alpha(color) / 255f;


            ColorMatrix cm = new ColorMatrix(new float[]{
                    r, r, r, r, r, //red
                    g, g, g, g, g, //green
                    b, b, b, b, b, //blue
                    1, 1, 1, 1, 1 //alpha
            });

            ColorMatrixColorFilter cf = new ColorMatrixColorFilter(cm);

            drawable.setColorFilter(cf);
        }

        public static void toastError(List<ErrorResponse> errors) {
            if (errors == null) {
                return;
            }
            for (ErrorResponse error : errors) {
                String localizedError = null;
                try {
                    localizedError = Aptoide.getContext().getString(Errors.getErrorsMap().get(error.code));
                } catch (NullPointerException ignored) {
                }
                if (localizedError == null) {
                    localizedError = error.msg;
                }
                if (localizedError != null)
                    Toast.makeText(Aptoide.getContext(), localizedError, Toast.LENGTH_LONG).show();
            }
        }

        public static int getBucketSize() {
            int bucket = 1;
            float screenWidth = getScreenWidthInDip();

            int magicNumber = Aptoide.getContext().getResources().getInteger(R.integer.bucket_size_magic_number);
            if (screenWidth > magicNumber) {
                bucket = (int) (screenWidth / magicNumber);
            }

            Logger.d("APTOIDEUTILS", "bucketsize = " + bucket);

            return bucket;
        }
        public static int getEditorChoiceBucketSize() {
            int bucket = 1;
            float screenWidth = getScreenWidthInDip();

            if (screenWidth > 300) {
                bucket = (int) (screenWidth / 300);
            }

            Logger.d("APTOIDEUTILS", "bucketsize = " + bucket);

            return bucket;
        }

        public static int getStoreBucketSize() {
            int bucket = 1;
            float screenWidth = getScreenWidthInDip();

            if (screenWidth > 150) {
                bucket = (int) (screenWidth / 150);
            }

            Logger.d("APTOIDEUTILS", "storeBucketsize = " + bucket);

            return bucket;
        }

        protected static float getScreenWidthInDip() {
            WindowManager wm = ((WindowManager) Aptoide.getContext().getSystemService(Context.WINDOW_SERVICE));
            DisplayMetrics dm = new DisplayMetrics();
            wm.getDefaultDisplay().getMetrics(dm);
            int screenWidth_in_pixel = dm.widthPixels;
            return screenWidth_in_pixel / dm.density;
        }

        /**
         * On v7 webservices there is no attribute of HD icon. <br />Instead,
         * the logic is that if the filename ends with <b>_icon</b> it is an HD icon.
         *
         * @param context Context of the app
         * @param iconUrl The String with the URL of the icon
         * @return A String with
         */
        public static String parseIcon(Context context, String iconUrl) {
            try {
                if (iconUrl.contains("_icon")) {
                    String sizeString = IconSizeUtils.generateSizeString(context);
                    String[] splittedUrl = iconUrl.split("\\.(?=[^\\.]+$)");
                    iconUrl = splittedUrl[0] + "_" + sizeString + "." + splittedUrl[1];
                }
            } catch (Exception e) {
                Logger.printException(e);
            }
            return iconUrl;
        }

        /**
         * Parse dp to pixels
         *
         * @param context Context of the app
         * @param dps The int mesuare in dps
         * @return An integer with the pixels value
         */
        public static int parseDpsToPixels(Context context, int dps) {
            final float scale = context.getResources().getDisplayMetrics().density;
            return (int) (dps * scale + 0.5f);
        }

        /**
        * Unbind all views
        *
         */
        public static void unbindDrawables(View view) {
            final Drawable background = view.getBackground();
            if (background != null) {
                background.setCallback(null);
                view.setBackgroundDrawable(null);
            }
            if (view instanceof ImageView) {
                ((ImageView)view).setImageDrawable(null);
            } else if (view instanceof ViewGroup) {
                for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                    unbindDrawables(((ViewGroup) view).getChildAt(i));
                }
                ((ViewGroup) view).removeAllViews();
            }
        }


        /**
         * this method ensure that the returned span size is a valid one
         * The recyclerView must have a GridLayoutManager and the adapter must be an instance of SpannableRecyclerAdapter.
         * @param recyclerView where the item will be placed
         * @param position item position
         * @return a valid span size for the given item
         */
        public static int getSpanSize(RecyclerView recyclerView, int position) {
            if (!(recyclerView.getAdapter() instanceof SpannableRecyclerAdapter) || !(recyclerView.getLayoutManager() instanceof GridLayoutManager)) {
                String errorMsg = "The recyclerView must have a GridLayoutManager and the adapter must be an instance of SpannableRecyclerAdapter.";
                Crashlytics.logException(new ClassCastException(errorMsg));
                Logger.e(TAG, errorMsg);
                return 1;
            }
            int spanSize = ((SpannableRecyclerAdapter) recyclerView.getAdapter()).getSpanSize(position);
            if (spanSize >= ((GridLayoutManager) recyclerView.getLayoutManager()).getSpanCount()) {
                return ((GridLayoutManager) recyclerView.getLayoutManager()).getSpanCount();
            } else if (spanSize < 1) {
                return 1; //min span size
            } else {
                return spanSize;
            }
        }
    }


    /**
     * Algorithms Utils
     */
    public static class Algorithms {

        /**
         * method to copy a string to clipboard
         * @param text texto to copy to clipBoard
         * @return true if it was copied successfully false otherwise
         */
        public static boolean copyToClipBoard(Context context, String text) {
            if (text==null || text.isEmpty()) {
                return false;
            } else {
                if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
                    android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
                    clipboard.setText(text);
                } else {
                    android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
                    android.content.ClipData clip = android.content.ClipData.newPlainText(text, text);
                    clipboard.setPrimaryClip(clip);
                }
            }
            return true;
        }

        public static String computeSHA1sumFromBytes(byte[] bytes)
                throws NoSuchAlgorithmException, UnsupportedEncodingException {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(bytes, 0, bytes.length);
            byte[] sha1hash = md.digest();
            return convToHexWithColon(sha1hash);
        }

        private static String convToHexWithColon(byte[] data) {
            StringBuilder buf = new StringBuilder();
            for (int i = 0; i < data.length; i++) {
                int halfbyte = (data[i] >>> 4) & 0x0F;
                int two_halfs = 0;
                do {
                    if ((0 <= halfbyte) && (halfbyte <= 9))
                        buf.append((char) ('0' + halfbyte));
                    else
                        buf.append((char) ('a' + (halfbyte - 10)));
                    halfbyte = data[i] & 0x0F;

                } while (two_halfs++ < 1);

                if (i < data.length - 1) {
                    buf.append(":");
                }

            }
            return buf.toString();
        }

        public static String computeSHA1sum(String text)
                throws NoSuchAlgorithmException, UnsupportedEncodingException {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(text.getBytes("iso-8859-1"), 0, text.length());
            byte[] sha1hash = md.digest();
            return convToHex(sha1hash);
        }

        private static String convToHex(byte[] data) {
            StringBuilder buf = new StringBuilder();
            for (int i = 0; i < data.length; i++) {
                int halfbyte = (data[i] >>> 4) & 0x0F;
                int two_halfs = 0;
                do {
                    if ((0 <= halfbyte) && (halfbyte <= 9))
                        buf.append((char) ('0' + halfbyte));
                    else
                        buf.append((char) ('a' + (halfbyte - 10)));
                    halfbyte = data[i] & 0x0F;
                } while (two_halfs++ < 1);
            }
            return buf.toString();
        }

        public static String md5Calc(File f) {
            byte[] buffer = new byte[1024];
            int read, i;
            String md5hash;
            try {
                MessageDigest digest = MessageDigest.getInstance("MD5");
                InputStream is = new FileInputStream(f);
                while ((read = is.read(buffer)) > 0) {
                    digest.update(buffer, 0, read);
                }
                byte[] md5sum = digest.digest();
                BigInteger bigInt = new BigInteger(1, md5sum);
                md5hash = bigInt.toString(16);
                is.close();
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }

            if (md5hash.length() != 33) {
                String tmp = "";
                for (i = 1; i < (33 - md5hash.length()); i++) {
                    tmp = tmp.concat("0");
                }
                md5hash = tmp.concat(md5hash);
            }

            return md5hash;
        }

        public static String computeHmacSha1(String value, String keyString)
                throws InvalidKeyException, IllegalStateException,
                UnsupportedEncodingException, NoSuchAlgorithmException {
            System.out.println(value);
            System.out.println(keyString);
            SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"),
                    "HmacSHA1");
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(key);

            byte[] bytes = mac.doFinal(value.getBytes("UTF-8"));

            return convToHex(bytes);

        }
    }


    /**
     * Network Utils
     */
    public static class NetworkUtils {

        private static int TIME_OUT = 15000; // 15s

        private static String getUserId() {
            String user_id;

            SharedPreferences sPref = PreferenceManager.getDefaultSharedPreferences(Aptoide.getContext());

            user_id = sPref.getString("advertisingIdClient", null);

            // Fallback para user
            if (isNullOrEmpty(user_id)) {
                user_id = android.provider.Settings.Secure.getString(Aptoide.getContext().getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
            }

            // Fallback para UUID
            if (isNullOrEmpty(user_id)) {
                user_id = sPref.getString(EnumPreferences.APTOIDE_CLIENT_UUID.name(), "NoInfo");
            }

            return user_id;
        }

        public static String getUserAgentString(Context mctx, boolean update) {
            SharedPreferences sPref = PreferenceManager.getDefaultSharedPreferences(mctx);
            String myid = getUserId();
            String myscr = sPref.getInt(EnumPreferences.SCREEN_WIDTH.name(), 0) + "x" + sPref.getInt(EnumPreferences.SCREEN_HEIGHT.name(), 0);
            String verString = null;
            try {
                verString = mctx.getPackageManager().getPackageInfo(mctx.getPackageName(), 0).versionName;
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }

            String extraId = Aptoide.getConfiguration().getExtraId();

            return "aptoide-" + verString + ";" + HWSpecifications.TERMINAL_INFO + ";" + myscr + ";id:" + myid + ";" + sPref.getString(AptoideConfiguration.LOGIN_USER_LOGIN, "") + ";" + extraId;
        }

        public static boolean isIconDownloadPermitted(Context context) {

            return true;
//            return isAvailable(context,
//                    new DownloadPermissions(
//                            getSharedPreferences().getBoolean("wifi", true),
//                            getSharedPreferences().getBoolean("ethernet", true),
//                            getSharedPreferences().getBoolean("4g", true),
//                            getSharedPreferences().getBoolean("3g", true)));
        }

        public static boolean isGeneralDownloadPermitted(Context context) {
            final SharedPreferences preferences = getSharedPreferences();
            final boolean wifiAllowed = preferences.getBoolean("generalnetworkwifi", true);
            final boolean wifiAvailable = isAvailable(context, TYPE_WIFI);
            final boolean mobileAllowed = preferences.getBoolean("generalnetworkmobile", true);
            final boolean mobileAvailable = isAvailable(context, TYPE_MOBILE);
            return !(wifiAvailable && !wifiAllowed) && !(mobileAvailable && !mobileAllowed);
        }

        public static boolean isAvailable(Context context, int networkType) {
            final ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            return Build.VERSION.SDK_INT < 21
                    ? isAvailableSdk1(manager, networkType)
                    : isAvailableSdk21(manager, networkType);
        }

        private static boolean isAvailableSdk1(final ConnectivityManager manager,
                                               final int networkType) {
            final NetworkInfo info = manager.getActiveNetworkInfo();
            return info != null && info.getType() == networkType;
        }

        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        private static boolean isAvailableSdk21(final ConnectivityManager manager,
                                                final int networkType) {
            for (final Network network : manager.getAllNetworks()) {
                final NetworkInfo info = manager.getNetworkInfo(network);
                if (info != null && info.isConnected() && info.getType() == networkType) {
                    return true;
                }
            }
            return false;
        }

        public static boolean isNetworkAvailable(Context context) {
            ConnectivityManager connectivityManager
                    = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
            return activeNetworkInfo != null && activeNetworkInfo.isConnected();
        }

        public static int checkServerConnection(final String string, final String username, final String password) throws Exception {


            HttpURLConnection client = (HttpURLConnection) new URL(string
                    + "info.xml").openConnection();
            if (username != null && password != null) {
                String basicAuth = "Basic "
                        + new String(Base64.encode(
                        (username + ":" + password).getBytes(),
                        Base64.NO_WRAP));
                client.setRequestProperty("Authorization", basicAuth);
            }

            client.setRequestMethod("HEAD");
            client.setConnectTimeout(TIME_OUT);
            client.setReadTimeout(TIME_OUT);

            String contentType = client.getContentType();
            int responseCode = client.getResponseCode();
            client.disconnect();
            if (Aptoide.DEBUG_MODE)
                Logger.i("CheckServerConnection", "Checking on: " + client.getURL().toString());
            if (contentType.equals("application/xml")) {
                return 0;
            } else {
                return responseCode;
            }
        }

        public static String getConnectionType() {
            final Context context = Aptoide.getContext();
            final ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            final NetworkInfo info = manager.getActiveNetworkInfo();
            // Used only for backward compatibility, should be replaced with info.getTypeName()
            if (info != null) {
                switch (info.getType()) {
                    case TYPE_ETHERNET:
                        return "ethernet";
                    case TYPE_WIFI:
                        return "mobile";
                    case TYPE_MOBILE:
                        return "mobile";
                }
            }
            return "unknown";
        }
    }


    /**
     * Repo/Store Utils
     */
    public static class RepoUtils {


        public static String checkStoreUrl(String uri_str) {
            uri_str = uri_str.trim();
            if (!uri_str.contains(".")) {
                uri_str = uri_str.concat(".store.aptoide.com");
            }

            uri_str = RepoUtils.formatRepoUri(uri_str);

            if (uri_str.contains("bazaarandroid.com")) {
                uri_str = uri_str.replaceAll("bazaarandroid\\.com", "store.aptoide.com");
            }

            return uri_str;
        }

        public static String split(String repo) {
            Logger.d("Aptoide-RepoUtils", "Splitting " + repo);
            repo = formatRepoUri(repo);
            return repo.split("http://")[1].split("\\.store")[0].split("\\.bazaarandroid.com")[0];
        }

        public static String formatRepoUri(String uri_str) {

            uri_str = uri_str.toLowerCase(Locale.ENGLISH);

            if (uri_str.contains("http//")) {
                uri_str = uri_str.replaceFirst("http//", "http://");
            }

            if (uri_str.length() != 0 && uri_str.charAt(uri_str.length() - 1) != '/') {
                uri_str = uri_str + '/';
                Logger.d("Aptoide-ManageRepo", "repo uri: " + uri_str);
            }
            if (!uri_str.startsWith("http://")) {
                uri_str = "http://" + uri_str;
                Logger.d("Aptoide-ManageRepo", "repo uri: " + uri_str);
            }

            return uri_str;
        }


        public static void startParse(final String storeName, final Context context, final SpiceManager spiceManager) {
            Toast.makeText(context, AptoideUtils.StringUtils.getFormattedString(context, R.string.subscribing, storeName), Toast.LENGTH_SHORT).show();

            GetSimpleStoreRequest request = new GetSimpleStoreRequest();
            request.store_name = storeName;

            CheckSimpleStoreListener checkStoreListener = new CheckSimpleStoreListener();
            checkStoreListener.callback = new CheckSimpleStoreListener.Callback() {
                @Override
                public void onSuccess() {
                    addStoreOnCloud(storeName, context, spiceManager);
                }
            };

            spiceManager.execute(request, checkStoreListener);
        }

        /**
         * Adds the default store
         */
        public static void addDefaultAppsStore(Context context) {
            AptoideDatabase database = new AptoideDatabase(Aptoide.getDb());
            if (database.existsStore(Defaults.DEFAULT_STORE_ID)) {
                return;
            }

            final Store store = new Store();
            store.setId(Defaults.DEFAULT_STORE_ID);
            store.setName(Defaults.DEFAULT_STORE_NAME);
            store.setDownloads("");


            String sizeString = IconSizeUtils.generateSizeStringAvatar(Aptoide.getContext());

            String avatar = "http://pool.img.aptoide.com/apps/b62b1c9459964d3a876b04c70036b10a_ravatar.png";

            // integrate the icon into a drawable ?
            if (avatar != null) {
                String[] splittedUrl = avatar.split("\\.(?=[^\\.]+$)");
                avatar = splittedUrl[0] + "_" + sizeString + "." + splittedUrl[1];
            }

            store.setAvatar(avatar);
            store.setDescription(context.getResources().getString(R.string.aptoide_description));
            store.setTheme("default");
            store.setView("list");
            store.setBaseUrl("apps");


            try {
                long l = database.insertStore(store);
                database.updateStore(store, l);
            } catch (Exception e) {
                Logger.printException(e);
            }
        }


        /**
         * Try to sync all your repos
         *
         * @param context
         * @param spiceManager
         */
        public static void syncRepos(Context context, SpiceManager spiceManager) {
            final SharedPreferences defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

            if (!defaultSharedPreferences.getBoolean(AptoidePreferences.REPOS_SYNCED, false)) {

                GetUserRepoSubscriptions subscriptions = new GetUserRepoSubscriptions();
                spiceManager.execute(subscriptions, new RequestListener<GetUserRepoSubscriptionJson>() {
                    @Override
                    public void onRequestFailure(SpiceException spiceException) {
                    }

                    @Override
                    public void onRequestSuccess(final GetUserRepoSubscriptionJson getUserRepoSubscriptionJson) {
                        // Call database operations off the UI Thread
                        new Thread(new Runnable() {
                            @Override
                            public void run() {

                                boolean storeInserted = false;

                                for (GetUserRepoSubscriptionJson.RepoInfo subscription : getUserRepoSubscriptionJson.getSubscription()) {

                                    try {
                                        final Store store = new Store();

                                        store.setId(subscription.getId().longValue());
                                        store.setName(subscription.getName());
                                        store.setDownloads(subscription.getDownloads());

                                        String sizeString = IconSizeUtils.generateSizeStringAvatar(Aptoide.getContext());
                                        String avatar = subscription.getAvatar();

                                        if (avatar != null) {
                                            String[] splitUrl = avatar.split("\\.(?=[^\\.]+$)");
                                            avatar = splitUrl[0] + "_" + sizeString + "." + splitUrl[1];
                                        }

                                        store.setAvatar(avatar);
                                        store.setDescription(subscription.getDescription());
                                        store.setTheme(subscription.getTheme());
                                        store.setView(subscription.getView());
                                        store.setBaseUrl(subscription.getName());

                                        AptoideDatabase database = new AptoideDatabase(Aptoide.getDb());

                                        try {
                                            long l = database.insertStore(store);
                                            database.updateStore(store, l);
                                            storeInserted = true;

                                        } catch (Exception e) {
                                            Logger.printException(e);
                                        }

                                    } catch (Exception e) {
                                        Logger.printException(e);
                                    }
                                }

                                defaultSharedPreferences.edit().putBoolean(AptoidePreferences.REPOS_SYNCED, true).apply();

                                if (storeInserted) {
                                    BusProvider.getInstance().post(new OttoEvents.RepoAddedEvent());
                                }
                            }
                        }).start();
                    }
                });
            }
        }

        /**
         * If there's a local account created, add the store to its aptoide account
         *
         * @param storeName
         * @param context
         * @param spiceManager
         */
        private static void addStoreOnCloud(String storeName, Context context, SpiceManager spiceManager) {
            if (AccountManager.get(context).getAccountsByType(Aptoide.getConfiguration().getAccountType()).length > 0) {
                ChangeUserRepoSubscription changeUserRepoSubscription = new ChangeUserRepoSubscription();
                ChangeUserRepoSubscription.RepoSubscription repoSubscription = new ChangeUserRepoSubscription.RepoSubscription(storeName, true);
                changeUserRepoSubscription.setRepoSubscription(repoSubscription);
                spiceManager.execute(changeUserRepoSubscription, null);
            }
        }

        public static void removeStoreOnCloud(Store store, Context context, SpiceManager spiceManager) {
            if (AccountManager.get(context).getAccountsByType(Aptoide.getConfiguration().getAccountType()).length > 0) {
                ChangeUserRepoSubscription changeUserRepoSubscription = new ChangeUserRepoSubscription();
                ChangeUserRepoSubscription.RepoSubscription repoSubscription = new ChangeUserRepoSubscription.RepoSubscription(store.getName(), false);
                changeUserRepoSubscription.setRepoSubscription(repoSubscription);
                spiceManager.execute(changeUserRepoSubscription, null);
            }
        }

        public static GetMoreVersionsAppRequest GetMoreAppVersionsRequest(String packageName, int limit, int offset) {
            GetMoreVersionsAppRequest request = new GetMoreVersionsAppRequest(AptoideUtils.UI.getBucketSize());
            request.filters = Aptoide.filters;
            request.mature = PreferenceManager.getDefaultSharedPreferences(Aptoide.getContext()).getBoolean(Constants.MATURE_CHECK_BOX, false);
            request.aptoideVercode = AptoideUtils.UI.getVerCode(Aptoide.getContext());
            request.lang = AptoideUtils.StringUtils.getMyCountryCode(Aptoide.getContext());
            request.packageName = packageName;
            request.limit = limit;
            request.offset = offset;
            return request;
        }

        public static GetStoreRequestv7 buildStoreRequest(long storeId, String context) {
            GetStoreRequestv7 request = buildGenericStoreRequest(AptoideUtils.UI.getBucketSize());
            request.storeId = storeId;
            request.context = context;
            final Login login = new AptoideDatabase(Aptoide.getDb()).getStoreLogin(storeId);
            if (login != null) {
                request.user = login.getUsername();
                request.password = login.getPasswordSha1();
            }
            return request;
        }

        public static final int NUMBER_OF_LINES = 4;

        public static GetStoreRequestv7 buildStoreRequest(long storeId, String context, int bucketSize) {

            CommunityGetstoreRequest request = new CommunityGetstoreRequest(bucketSize, UI.getEditorChoiceBucketSize());
            request.loggedIn = AccountUtils.isLoggedIn(Aptoide.getContext());
            request.nview = "response";
            request.filters = Aptoide.filters;
            request.mature = PreferenceManager.getDefaultSharedPreferences(Aptoide.getContext()).getBoolean(Constants.MATURE_CHECK_BOX, false);
            request.aptoideVercode = AptoideUtils.UI.getVerCode(Aptoide.getContext());
            request.lang = AptoideUtils.StringUtils.getMyCountryCode(Aptoide.getContext());
            request.storeId = storeId;
            request.context = context;
            final Login login = new AptoideDatabase(Aptoide.getDb()).getStoreLogin(storeId);
            if (login != null) {
                request.user = login.getUsername();
                request.password = login.getPasswordSha1();
            }
            return request;
        }

        public static GetStoreRequestv7 buildStoreRequest(String storeName, String context) {
            GetStoreRequestv7 request = buildGenericStoreRequest(AptoideUtils.UI.getBucketSize());
            request.storeName = storeName;
            request.context = context;
            final Login login = new AptoideDatabase(Aptoide.getDb()).getStoreLogin(storeName);
            if (login != null) {
                request.user = login.getUsername();
                request.password = login.getPasswordSha1();
            }
            return request;
        }

        public static GetSimpleStoreRequest buildSimpleStoreRequest(String storeName) {
            GetSimpleStoreRequest request = new GetSimpleStoreRequest();
            request.store_name = storeName;
            final Login login = new AptoideDatabase(Aptoide.getDb()).getStoreLogin(storeName);
            request.login = login;
            return request;
        }

        private static GetStoreRequestv7 buildGenericStoreRequest(int bucketSize) {
            GetStoreRequestv7 request = new GetStoreRequestv7(bucketSize);
            request.loggedIn = AccountUtils.isLoggedIn(Aptoide.getContext());
            request.nview = "response";
            request.filters = Aptoide.filters;
            request.mature = PreferenceManager.getDefaultSharedPreferences(Aptoide.getContext()).getBoolean(Constants.MATURE_CHECK_BOX, false);
            request.aptoideVercode = AptoideUtils.UI.getVerCode(Aptoide.getContext());
            request.lang = AptoideUtils.StringUtils.getMyCountryCode(Aptoide.getContext());
            return request;
        }

        public static GetStoreWidgetRequestv7 buildStoreWidgetRequest(long storeId, String actionUrl) {
            GetStoreWidgetRequestv7 request = new GetStoreWidgetRequestv7(actionUrl, AptoideUtils.UI.getBucketSize());
            request.loggedIn = AccountUtils.isLoggedIn(Aptoide.getContext());
            request.nview = "response";
            request.filters = Aptoide.filters;
            request.mature = PreferenceManager.getDefaultSharedPreferences(Aptoide.getContext()).getBoolean(Constants.MATURE_CHECK_BOX, false);
            request.aptoideVercode = AptoideUtils.UI.getVerCode(Aptoide.getContext());
            request.lang = AptoideUtils.StringUtils.getMyCountryCode(Aptoide.getContext());
            final Login login = new AptoideDatabase(Aptoide.getDb()).getStoreLogin(storeId);
            if (login != null) {
                request.user = login.getUsername();
                request.password = login.getPasswordSha1();
            }
            return request;
        }

        public static GetListViewItemsRequestv7 buildViewItemsRequest(String storeName, String actionUrl, String layout, int offset) {
            GetListViewItemsRequestv7 request = new GetListViewItemsRequestv7(actionUrl, layout, UI.getBucketSize(), offset);
            request.loggedIn = AccountUtils.isLoggedIn(Aptoide.getContext());
            request.nview = "response";
            request.filters = Aptoide.filters;
            request.mature = PreferenceManager.getDefaultSharedPreferences(Aptoide.getContext()).getBoolean(Constants.MATURE_CHECK_BOX, false);
            request.aptoideVercode = AptoideUtils.UI.getVerCode(Aptoide.getContext());
            request.lang = AptoideUtils.StringUtils.getMyCountryCode(Aptoide.getContext());
            final Login login = new AptoideDatabase(Aptoide.getDb()).getStoreLogin(storeName);
            if (login != null) {
                request.user = login.getUsername();
                request.password = login.getPasswordSha1();
            }
            return request;
        }

        private static GetAppRequest buildGetAppRequest(String storeName) {
            GetAppRequest request = new GetAppRequest(UI.getBucketSize());
            request.token = SecurePreferences.getInstance().getString("access_token", null);
            request.filters = Aptoide.filters;
            request.mature = PreferenceManager.getDefaultSharedPreferences(Aptoide.getContext()).getBoolean(Constants.MATURE_CHECK_BOX, false);
            request.aptoideVercode = AptoideUtils.UI.getVerCode(Aptoide.getContext());
            request.lang = AptoideUtils.StringUtils.getMyCountryCode(Aptoide.getContext());
            request.storeName = storeName;
            final Login login = new AptoideDatabase(Aptoide.getDb()).getStoreLogin(storeName);
            if (login != null) {
                request.user = login.getUsername();
                request.password = login.getPasswordSha1();
            }
            return request;
        }

        public static GetAppRequest buildGetAppRequestFromAppId(long appId, String storeName, String packageName) {
            GetAppRequest request = buildGetAppRequest(storeName);
            request.appId = appId;
            request.packageName = packageName;
            return request;
        }

        public static GetAppRequest buildGetAppRequestFromMd5(String md5, String storeName) {
            GetAppRequest request = buildGetAppRequest(storeName);
            request.md5 = md5;
            return request;
        }

        public static GetAppRequest buildGetAppRequestFromPackageName(String packageName) {
            GetAppRequest request = buildGetAppRequest(null);
            request.packageName = packageName;
            return request;
        }

        public static RateAppRequest buildRateRequest(long appId, float rate) {
            return new RateAppRequest(appId, rate);
        }

        public static AddApkFlagRequest buildFlagAppRequest(String storeName, String flag, String md5sum) {
            AddApkFlagRequest request = new AddApkFlagRequest();
            request.setFlag(flag);
            request.setMd5sum(md5sum);
            request.setRepo(storeName);
            return request;
        }

        public static SearchRequest buildSearchRequest(String query, int limit, int otherReposLimit, int offset, int otherReposOffset) {
            SearchRequest request = new SearchRequest();
            request.setSearchQuery(query);
            ArrayList<String> strings = new ArrayList<>();
            AptoideDatabase db = new AptoideDatabase(Aptoide.getDb());
            Cursor servers = db.getStoresCursor();
            for (servers.moveToFirst(); !servers.isAfterLast(); servers.moveToNext()) {
                String name = servers.getString(servers.getColumnIndex("name"));
                strings.add(name);
            }
            String[] arraysStrings = new String[strings.size()];
            strings.toArray(arraysStrings);
            request.setRepos(arraysStrings);
            request.setLimit(limit);
            request.setOtherReposLimit(otherReposLimit);
            request.setU_offset(otherReposOffset);
            request.setOffset(offset);
            return request;
        }

        public static SearchRequest buildSearchRequest(String query, int searchLimit, int otherReposSearchLimit, int offset, String storeName) {
            SearchRequest request = buildSearchRequest(query,searchLimit,otherReposSearchLimit,offset,0);
            if (storeName!=null && !TextUtils.isEmpty(storeName)) {
                String[] arraysStrings = new String[1];
                arraysStrings[0] = storeName;
                request.setRepos(arraysStrings);
            }
            return request;
        }
    }


    public static class HWSpecifications {

        public static final String TERMINAL_INFO = getModel() + "(" + getProduct() + ")"
                + ";v" + getRelease() + ";" + System.getProperty("os.arch");

        public static String getProduct() {
            return android.os.Build.PRODUCT.replace(";", " ");
        }

        public static String getModel() {
            return android.os.Build.MODEL.replaceAll(";", " ");
        }

        public static String getRelease() {
            return android.os.Build.VERSION.RELEASE.replaceAll(";", " ");
        }

//        private static String cpuAbi2;
//
//        public static String getDeviceId(Context context) {
//            return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
//        }
//

        /**
         * @return the sdkVer
         */

        public static int getSdkVer() {
            return Build.VERSION.SDK_INT;
        }

        /**
         * @return the screenSize
         */
        public static int getScreenSize(Context context) {
            return context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
        }

        public static int getNumericScreenSize(Context context) {
            int size = context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
            return (size + 1) * 100;
        }


        /**
         * @return the esglVer
         */
        public static String getGlEsVer(Context context) {
            return ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getDeviceConfigurationInfo().getGlEsVersion();
        }

        public static int getDensityDpi(Context context) {

            DisplayMetrics metrics = new DisplayMetrics();
            WindowManager manager = (WindowManager) context.getSystemService(Service.WINDOW_SERVICE);
            manager.getDefaultDisplay().getMetrics(metrics);

            int dpi = metrics.densityDpi;


            if (dpi <= 120) {
                dpi = 120;
            } else if (dpi <= 160) {
                dpi = 160;
            } else if (dpi <= 213) {
                dpi = 213;
            } else if (dpi <= 240) {
                dpi = 240;
            } else if (dpi <= 320) {
                dpi = 320;
            } else if (dpi <= 480) {
                dpi = 480;
            } else {
                dpi = 640;
            }

            return dpi;
        }

        public static String getScreenDensity() {

            Context context = Aptoide.getContext();
            int density = context.getResources().getDisplayMetrics().densityDpi;

            switch (density) {
                case DisplayMetrics.DENSITY_LOW:
                    return "ldpi";
                case DisplayMetrics.DENSITY_MEDIUM:
                    return "mdpi";
                case DisplayMetrics.DENSITY_HIGH:
                    return "hdpi";
                case DisplayMetrics.DENSITY_XHIGH:
                    return "xhdpi";
                case DisplayMetrics.DENSITY_XXHIGH:
                    return "xxhdpi";
                case DisplayMetrics.DENSITY_XXXHIGH:
                    return "xxxhdpi";

                default:
                    return "hdpi";
            }

        }

        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        @SuppressWarnings("deprecation")
        public static String getAbis() {
            final String[] abis = getSdkVer() >= 21
                    ? Build.SUPPORTED_ABIS
                    : new String[]{Build.CPU_ABI, Build.CPU_ABI2};
            final StringBuilder builder = new StringBuilder();
            for (int i = 0; i < abis.length; i++) {
                builder.append(abis[i]);
                if (i < abis.length - 1) {
                    builder.append(",");
                }
            }
            return builder.toString();
        }

//        public static String getCpuAbi() {
//            return Build.CPU_ABI;
//        }
//
//        public static String getCpuAbi2() {
//
//            if (getSdkVer() >= 8 && !Build.CPU_ABI2.equals(Build.UNKNOWN)) {
//                return Build.CPU_ABI2;
//            } else {
//                return "";
//            }
//        }

        @Nullable
        public static String filters(Context context) {
            final ManagerPreferences managerPreferences = ManagerPreferences.getInstance(context);
            if (!managerPreferences.preferences.getBoolean("hwspecsChkBox", true)) {
                return null;
            }

            int minSdk = AptoideUtils.HWSpecifications.getSdkVer();
            String minScreen = Filters.Screen.values()
                    [AptoideUtils.HWSpecifications.getScreenSize(context)]
                    .name()
                    .toLowerCase(Locale.ENGLISH);
            String minGlEs = AptoideUtils.HWSpecifications.getGlEsVer(context);


            final int density = AptoideUtils.HWSpecifications.getDensityDpi(context);

            String cpuAbi = AptoideUtils.HWSpecifications.getAbis();

            int myversionCode = 0;
            PackageManager manager = context.getPackageManager();
            try {
                myversionCode = manager.getPackageInfo(context.getPackageName(), 0).versionCode;
            } catch (PackageManager.NameNotFoundException ignore) {
            }


            String filters = (Build.DEVICE.equals("alien_jolla_bionic") ? "apkdwn=myapp&" : "") + "maxSdk=" + minSdk + "&maxScreen=" + minScreen + "&maxGles=" + minGlEs + "&myCPU=" + cpuAbi + "&myDensity=" + density + "&myApt=" + myversionCode;

            return Base64.encodeToString(filters.getBytes(), 0).replace("=", "").replace("/", "*").replace("+", "_").replace("\n", "");
        }

//        public static final String TERMINAL_INFO = getModel() + "("+ getProduct() + ")"
//                +";v"+getRelease()+";"+System.getProperty("os.arch");
//
//        public static String getProduct(){
//            return android.os.Build.PRODUCT.replace(";", " ");
//        }
//
//        public static String getModel(){
//            return android.os.Build.MODEL.replaceAll(";", " ");
//        }
//
//
//        public static String getRelease(){
//            return android.os.Build.VERSION.RELEASE.replaceAll(";", " ");
//        }

    }


    public static class StringUtils {

        public static String getMyCountryCode(Context context) {
            return context.getResources().getConfiguration().locale.getLanguage() + "_" + context.getResources().getConfiguration().locale.getCountry();
        }

        public static String getMyCountry(Context context) {
            return context.getResources().getConfiguration().locale.getLanguage();
        }

        public static String formatBits(long bytes) {
            int unit = 1024;
            if (bytes < unit) return bytes + " B";
            int exp = (int) (Math.log(bytes) / Math.log(unit));
            String pre = ("KMGTPE").charAt(exp - 1) + "";
            return String.format(Locale.ENGLISH, "%.1f %sb", bytes / Math.pow(unit, exp), pre);
        }

        public static String formatEta(long eta, String left) {

            if (eta > 0) {
                long days = eta / (1000 * 60 * 60 * 24);
                eta -= days * 1000 * 60 * 60 * 24;
                long hours = eta / (1000 * 60 * 60);
                eta -= hours * 1000 * 60 * 60;
                long minutes = eta / (1000 * 60);
                eta -= minutes * 1000 * 60;
                long seconds = eta / 1000;

                String etaString = "";
                if (days > 0) {
                    etaString += days + "d ";
                }
                if (hours > 0) {
                    etaString += hours + "h ";
                }
                if (minutes > 0) {
                    etaString += minutes + "m ";
                }
                if (seconds > 0) {
                    etaString += seconds + "s";
                }

                return etaString + " " + left;
            }
            return "";
        }

        public static Map<String, String> splitQuery(URI uri) throws UnsupportedEncodingException {
            Map<String, String> query_pairs = new LinkedHashMap<String, String>();
            String query = uri.getQuery();
            String[] pairs = query.split("&");
            for (String pair : pairs) {
                int idx = pair.indexOf("=");
                query_pairs.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
            }
            return query_pairs;
        }

        public static String withSuffix(String count) {
            long countl = 0;
            try {
                countl = Long.valueOf(count);
            } catch (Exception e) {
                Logger.printException(e);
            }

            return withSuffix(countl);
        }

        public static String withSuffix(long count) {
            if (count < 1000) return String.valueOf(count);
            int exp = (int) (Math.log(count) / Math.log(1000));
            return String.format("%d %c",
                    (int) (count / Math.pow(1000, exp)),
                    "kMGTPE".charAt(exp - 1));
        }

        public static String withDecimalSuffix(long count) {
            double result = (double) count;
            if (result < 1000) return "" + (int)result;
            int exp = (int) (Math.log(result) / Math.log(1000));
            String aux = new DecimalFormat("#.#").format(result / Math.pow(1000, exp));
            return String.format("%s %c", aux, "kMGTPE".charAt(exp - 1));
        }

        /**
         * <p>Joins the elements of the provided {@code Iterator} into
         * a single String containing the provided elements.</p>
         *
         * <p>No delimiter is added before or after the list.
         * A {@code null} separator is the same as an empty String ("").</p>
         *
         * @param iterator  the {@code Iterator} of values to join together, may be null
         * @param separator  the separator character to use, null treated as ""
         * @return the joined String, {@code null} if null iterator input
         */
        public static String join(final Iterator<?> iterator, final String separator) {

            // handle null, zero and one elements before building a buffer
            if (iterator == null) {
                return null;
            }
            if (!iterator.hasNext()) {
                return "";
            }
            final Object first = iterator.next();
            if (!iterator.hasNext()) {
                @SuppressWarnings( "deprecation" ) // ObjectUtils.toString(Object) has been deprecated in 3.2
                final String result = ObjectUtils.toString(first);
                return result;
            }

            // two or more elements
            final StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small
            if (first != null) {
                buf.append(first);
            }

            while (iterator.hasNext()) {
                if (separator != null) {
                    buf.append(separator);
                }
                final Object obj = iterator.next();
                if (obj != null) {
                    buf.append(obj);
                }
            }
            return buf.toString();
        }

        /**
         * <p>Joins the elements of the provided {@code Iterable} into
         * a single String containing the provided elements.</p>
         *
         * <p>No delimiter is added before or after the list.
         * A {@code null} separator is the same as an empty String ("").</p>
         *
         * @param iterable  the {@code Iterable} providing the values to join together, may be null
         * @param separator  the separator character to use, null treated as ""
         * @return the joined String, {@code null} if null iterator input
         * @since 2.3
         */
        public static String join(final Iterable<?> iterable, final String separator) {
            if (iterable == null) {
                return null;
            }
            return join(iterable.iterator(), separator);
        }

        public static String parseLocalyticsTag(String str) {
            return WordUtils.capitalize(str.replace('-', ' '));
        }

        public static String getFormattedString(Context context, @StringRes int resId, Object... formatArgs) {
            String result;
            final Resources resources = context.getResources();
            try {
                result = resources.getString(resId, formatArgs);
            }catch (UnknownFormatConversionException ex){
                final String resourceEntryName = resources.getResourceEntryName(resId);
                final String displayLanguage = Locale.getDefault().getDisplayLanguage();
                Logger.e("UnknownFormatConversion", "String: " + resourceEntryName + " Locale: " + displayLanguage);
                Crashlytics.log(3, "UnknownFormatConversion", "String: " + resourceEntryName + " Locale: " + displayLanguage);
                result = resources.getString(resId);
            }
            return result;
        }

        /**
         * get rounded value from the given double and remove the .0 if exact number
         *
         * @param number number to be rounded
         * @return rounded number in string format
         */
        public static String getRoundedValueFromDouble(double number) {
            if (number == (long) number) {
                return String.valueOf((long) number);
            } else {
                return String.format("%.1f", number);
            }
        }

        public static String commaSeparatedValues(List<?> list) {
            String s = new String();

            if (list.size() > 0) {
                s = list.get(0).toString();

                for (int i = 1; i < list.size(); i++) {
                    s += "," + list.get(i).toString();
                }
            }

            return s;
        }
    }


    public static class AccountUtils {

        public static boolean isLoggedIn(Context context) {
            AccountManager manager = AccountManager.get(context);

            return manager.getAccountsByType(Aptoide.getConfiguration().getAccountType()).length != 0;
        }

        public static boolean isLoggedInOrAsk(Activity activity) {
            final AccountManager manager = AccountManager.get(activity);

            if (manager.getAccountsByType(Aptoide.getConfiguration().getAccountType()).length == 0) {
                manager.addAccount(Aptoide.getConfiguration().getAccountType(),
                        AptoideConfiguration.AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS, null, null, activity, null, null);

                return false;
            }
            return true;
        }

    }


    public static class SocialMedia {

        public static void showTwitter(Context context) {
            if (AppUtils.isAppInstalled(context, "com.twitter.android")) {
                String url = "http://www.twitter.com/aptoide";
                Intent twitterIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                context.startActivity(twitterIntent);
//            FlurryAgent.logEvent("Opened_Twitter_App");
            } else {
                Intent intent = new Intent(context, WebViewTwitter.class);
                context.startActivity(intent);
//            FlurryAgent.logEvent("Opened_Twitter_Webview");
            }
        }

        public static void showFacebook(Context context) {
            if (AppUtils.isAppInstalled(context, "com.facebook.katana")) {
                Intent sharingIntent;
                try {
                    context.getPackageManager().getPackageInfo("com.facebook.katana", 0);
                    sharingIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("fb://page/225295240870860"));
                    context.startActivity(sharingIntent);
                } catch (PackageManager.NameNotFoundException e) {
                    e.printStackTrace();
                } catch (ActivityNotFoundException notFound) {
                    Toast.makeText(context, context.getString(R.string.not_found), Toast.LENGTH_SHORT).show();
                }
            } else {
                Intent intent = new Intent(context, WebViewFacebook.class);
                context.startActivity(intent);
//            FlurryAgent.logEvent("Opened_Facebook_Webview");
            }
        }

        public static void acceptTimeline() {
            Preferences.putBooleanAndCommit(Preferences.TIMELINE_ACEPTED_BOOL, true);

            Executors.newSingleThreadExecutor().execute(new Runnable() {
                @Override
                public void run() {
                    ChangeUserSettingsRequest request = new ChangeUserSettingsRequest();
                    request.addTimeLineSetting(ChangeUserSettingsRequest.TIMELINEACTIVE);
                    try {
                        request.loadDataFromNetwork();
                    } catch (Exception e) {
                        Logger.printException(e);
                    }
                }
            });
        }

    }


    public static class AppUtils {

        public static boolean isAppInstalled(Context context, String packageName) {
            PackageManager pm = context.getPackageManager();
            boolean installed;
            try {
                pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
                installed = true;
            } catch (PackageManager.NameNotFoundException e) {
                installed = false;
            }
            return installed;
        }

        public static PackageInfo getPackageInfo(Context context, String packageName) {
            PackageManager pm = context.getPackageManager();
            try {
                return pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
            } catch (PackageManager.NameNotFoundException e) {
                // Ignore
            }
            return null;
        }

        public static boolean isRooted() {
            return findBinary("su");
        }

        private static boolean findBinary(String binaryName) {
            boolean found = false;

            String[] places = {"/sbin/", "/system/bin/", "/system/xbin/", "/data/local/xbin/",
                    "/data/local/bin/", "/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/"};
            for (String where : places) {
                if (new File(where + binaryName).exists()) {
                    found = true;
                    break;
                }
            }

            return found;
        }

        @NonNull
        private static void iterateExtras(Bundle extras, String tag) {

            for (String key : extras.keySet()) {
                Object value = extras.get(key);
                if (value != null) {
                    Log.d(tag, String.format("%s %s (%s)", key, value.toString(), value.getClass().getName()));
                }
            }
        }


        public static void fillPermissionsForTableLayout(Context context, TableLayout mPermissionsTable, List<ApkPermissionGroup> apkPermissions) {
            final int ZERO_DIP = 0;
            final float WEIGHT_ONE = 1f;


            TableRow tr = new TableRow(context);
            tr.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT));
            LinearLayout linearLayout;
            int items = 0;


            for (int i = 0; i <= apkPermissions.size(); i++) {

                if (i >= apkPermissions.size()) {
                    if (tr.getChildCount() > 0) {
                        // there's still a TableRow left that needs to be added
                        tr.setPadding(0, 0, 0, 20);
                        mPermissionsTable.addView(tr, new TableLayout.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT, TableLayout.LayoutParams.WRAP_CONTENT));
                    }
                } else {
                    items ++;

                    ApkPermissionGroup apkPermission = apkPermissions.get(i);

                    if (apkPermission != null) {
                        linearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.row_permission, tr, false);
                        TextView name = (TextView) linearLayout.findViewById(R.id.permission_name);
                        name.setText(apkPermission.getName());

                        for (String s : apkPermission.getDescriptions()) {
                            TextView description  = (TextView) LayoutInflater.from(context).inflate(R.layout.row_description, linearLayout, false);
                            description.setText(s);
                            linearLayout.addView(description);
                        }

                        tr.addView(linearLayout, new TableRow.LayoutParams(ZERO_DIP, TableRow.LayoutParams.WRAP_CONTENT, WEIGHT_ONE));


                        if (items % 2 == 0) {
                            mPermissionsTable.addView(tr, new TableLayout.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT, TableLayout.LayoutParams.WRAP_CONTENT));
                            tr = new TableRow(context);
                            tr.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT));
                        }
                    }
                }
            }
        }

        public static List<ApkPermission> parsePermissions(Context context, List<String> permissionArray) {
            List<ApkPermission> list = new ArrayList<>();
            CharSequence csPermissionGroupLabel;
            CharSequence csPermissionLabel;
            PackageManager pm = context.getPackageManager();

            List<PermissionGroupInfo> lstGroups = pm.getAllPermissionGroups(PackageManager.PERMISSION_GRANTED);
            for (String permission : permissionArray) {

                for (PermissionGroupInfo pgi : lstGroups) {
                    try {
                        List<PermissionInfo> lstPermissions = pm.queryPermissionsByGroup(pgi.name, PackageManager.PERMISSION_GRANTED);
                        for (PermissionInfo pi : lstPermissions) {
                            if (pi.name.equals(permission)) {
                                csPermissionLabel = pi.loadLabel(pm);
                                csPermissionGroupLabel = pgi.loadLabel(pm);
                                list.add(new ApkPermission(csPermissionGroupLabel.toString(), csPermissionLabel.toString()));
                            }
                        }
                    } catch (Exception e) {
                        Logger.printException(e);
                    }
                }
            }

            Collections.sort(list, new Comparator<ApkPermission>() {
                @Override
                public int compare(ApkPermission lhs, ApkPermission rhs) {
                    return lhs.getName().compareTo(rhs.getName());
                }
            });

            return list;
        }


        public static ArrayList<ApkPermissionGroup> fillPermissionsGroups(List<ApkPermission> permissions) {
            ArrayList<ApkPermissionGroup> list = new ArrayList<>();
            String prevName = null;
            ApkPermissionGroup apkPermission = null;

            for (int i = 0; i <= permissions.size(); i++) {

                if (i >= permissions.size()) {
                    if (!list.contains(apkPermission)) {
                        list.add(apkPermission);
                    }
                } else {

                    ApkPermission permission = permissions.get(i);

                    if (!permission.getName().equals(prevName)) {
                        prevName = permission.getName();
                        apkPermission = new ApkPermissionGroup(permission.getName(), permission.getDescription());
                        list.add(apkPermission);
                    } else {
                        apkPermission.setDescription(permission.getDescription());
                    }
                }
            }

            return list;
        }


        public static void checkPermissions(Activity activity) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (activity.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
                    activity.requestPermissions(new String[]{Constants.WRITE_EXTERNAL_STORAGE}, 1);
                }
            }
        }


        public static long sumFileSizes(long fileSize, GetAppMeta.Obb obb) {

            if (obb == null || obb.main == null) {
                return fileSize;
            } else if (obb.patch == null) {
                return fileSize + obb.main.filesize.longValue();
            } else {
                return fileSize + obb.main.filesize.longValue() + obb.patch.filesize.longValue();
            }

        }


    }


    public static class DateTimeUtils extends DateUtils {

        private static String mTimestampLabelYesterday;
        private static String mTimestampLabelToday;
        private static String mTimestampLabelJustNow;
        private static String mTimestampLabelMinutesAgo;
        private static String mTimestampLabelHoursAgo;
        private static String mTimestampLabelHourAgo;
        private static String mTimestampLabelDaysAgo;
        private static String mTimestampLabelWeekAgo;
        private static String mTimestampLabelWeeksAgo;
        private static String mTimestampLabelMonthAgo;
        private static String mTimestampLabelMonthsAgo;
        private static String mTimestampLabelYearAgo;
        private static String mTimestampLabelYearsAgo;
        private static DateTimeUtils instance;

        /**
         * Singleton constructor, needed to get access to the application context & strings for i18n
         *
         * @param context Context
         * @return DateTimeUtils singleton instance
         * @throws Exception
         */
        public static DateTimeUtils getInstance(Context context) {
            if (instance == null) {
                instance = new DateTimeUtils();
                mTimestampLabelYesterday = context.getResources().getString(R.string.WidgetProvider_timestamp_yesterday);
                mTimestampLabelToday = context.getResources().getString(R.string.WidgetProvider_timestamp_today);
                mTimestampLabelJustNow = context.getResources().getString(R.string.WidgetProvider_timestamp_just_now);
                mTimestampLabelMinutesAgo = context.getResources().getString(R.string.WidgetProvider_timestamp_minutes_ago);
                mTimestampLabelHoursAgo = context.getResources().getString(R.string.WidgetProvider_timestamp_hours_ago);
                mTimestampLabelHourAgo = context.getResources().getString(R.string.WidgetProvider_timestamp_hour_ago);
                mTimestampLabelDaysAgo = context.getResources().getString(R.string.WidgetProvider_timestamp_days_ago);
                mTimestampLabelWeekAgo = context.getResources().getString(R.string.WidgetProvider_timestamp_week_ago2);
                mTimestampLabelWeeksAgo = context.getResources().getString(R.string.WidgetProvider_timestamp_weeks_ago);
                mTimestampLabelMonthAgo = context.getResources().getString(R.string.WidgetProvider_timestamp_month_ago);
                mTimestampLabelMonthsAgo = context.getResources().getString(R.string.WidgetProvider_timestamp_months_ago);
                mTimestampLabelYearAgo = context.getResources().getString(R.string.WidgetProvider_timestamp_year_ago);
                mTimestampLabelYearsAgo = context.getResources().getString(R.string.WidgetProvider_timestamp_years_ago);
            }
            return instance;
        }

        /**
         * Checks if the given date is yesterday.
         *
         * @param date - Date to check.
         * @return TRUE if the date is yesterday, FALSE otherwise.
         */
        private static boolean isYesterday(long date) {

            final Calendar currentDate = Calendar.getInstance();
            currentDate.setTimeInMillis(date);

            final Calendar yesterdayDate = Calendar.getInstance();
            yesterdayDate.add(Calendar.DATE, -1);

            return yesterdayDate.get(Calendar.YEAR) == currentDate.get(Calendar.YEAR) && yesterdayDate.get(Calendar.DAY_OF_YEAR) == currentDate.get(Calendar.DAY_OF_YEAR);
        }

        private static String[] weekdays = new DateFormatSymbols().getWeekdays(); // get day names
        private static final long millisInADay = 1000 * 60 * 60 * 24;


        /**
         * Displays a user-friendly date difference string
         *
         * @param timedate Timestamp to format as date difference from now
         * @return Friendly-formatted date diff string
         */
        public String getTimeDiffString(Context context, long timedate) {
            Calendar startDateTime = Calendar.getInstance();
            Calendar endDateTime = Calendar.getInstance();
            endDateTime.setTimeInMillis(timedate);
            long milliseconds1 = startDateTime.getTimeInMillis();
            long milliseconds2 = endDateTime.getTimeInMillis();
            long diff = milliseconds1 - milliseconds2;

            long hours = diff / (60 * 60 * 1000);
            long minutes = diff / (60 * 1000);
            minutes = minutes - 60 * hours;
            long seconds = diff / (1000);

            boolean isToday = DateTimeUtils.isToday(timedate);
            boolean isYesterday = DateTimeUtils.isYesterday(timedate);

            if (hours > 0 && hours < 12) {
                return hours == 1 ? AptoideUtils.StringUtils.getFormattedString(context, R.string.WidgetProvider_timestamp_hour_ago, hours) : AptoideUtils.StringUtils.getFormattedString(context, R.string.WidgetProvider_timestamp_hours_ago, hours);
            } else if (hours <= 0) {
                if (minutes > 0)
                    return AptoideUtils.StringUtils.getFormattedString(context, R.string.WidgetProvider_timestamp_minutes_ago, minutes);
                else {
                    return mTimestampLabelJustNow;
                }
            } else if (isToday) {
                return mTimestampLabelToday;
            } else if (isYesterday) {
                return mTimestampLabelYesterday;
            } else if (startDateTime.getTimeInMillis() - timedate < millisInADay * 6) {
                return weekdays[endDateTime.get(Calendar.DAY_OF_WEEK)];
            } else {
                return formatDateTime(context, timedate, DateUtils.FORMAT_NUMERIC_DATE);
            }
        }

        public String getTimeDiffAll(Context context, long time) {

            long diffTime = new Date().getTime() - time;

            if (isYesterday(time) || isToday(time)) {
                getTimeDiffString(context, time);
            } else {
                if (diffTime < DateUtils.WEEK_IN_MILLIS) {
                    int diffDays = Double.valueOf(Math.ceil(diffTime / millisInADay)).intValue();
                    return diffDays == 1 ? mTimestampLabelYesterday : AptoideUtils.StringUtils.getFormattedString(context, R.string.WidgetProvider_timestamp_days_ago, diffDays);
                } else if (diffTime < DateUtils.WEEK_IN_MILLIS * 4) {
                    int diffDays = Double.valueOf(Math.ceil(diffTime / WEEK_IN_MILLIS)).intValue();
                    return diffDays == 1 ? mTimestampLabelMonthAgo : AptoideUtils.StringUtils.getFormattedString(context, R.string.WidgetProvider_timestamp_months_ago, diffDays);
                } else if (diffTime < DateUtils.WEEK_IN_MILLIS * 4 * 12) {
                    int diffDays = Double.valueOf(Math.ceil(diffTime / (WEEK_IN_MILLIS * 4))).intValue();
                    return diffDays == 1 ? mTimestampLabelMonthAgo : AptoideUtils.StringUtils.getFormattedString(context, R.string.WidgetProvider_timestamp_months_ago, diffDays);
                } else {
                    int diffDays = Double.valueOf(Math.ceil(diffTime / (WEEK_IN_MILLIS * 4 * 12))).intValue();
                    return diffDays == 1 ? mTimestampLabelYearAgo : AptoideUtils.StringUtils.getFormattedString(context, R.string.WidgetProvider_timestamp_years_ago, diffDays);
                }
            }

            return getTimeDiffString(context, time);
        }
    }


    public static class VoteUtils {

        public static void voteComment(SpiceManager spiceManager, int commentId,
                                       String repoName,
                                       String token,
                                       RequestListener<GenericResponseV2> commentRequestListener,
                                       AddApkCommentVoteRequest.CommentVote vote) {


            AddApkCommentVoteRequest commentVoteRequest = new AddApkCommentVoteRequest();

            commentVoteRequest.setRepo(repoName);
            commentVoteRequest.setToken(token);
            commentVoteRequest.setCmtid(commentId);
            commentVoteRequest.setVote(vote);

            spiceManager.execute(commentVoteRequest, commentRequestListener);
            Toast.makeText(Aptoide.getContext(), Aptoide.getContext().getString(R.string.casting_vote), Toast.LENGTH_SHORT).show();
        }
    }


    public static class AdNetworks {

        public static String parseString(Context context, String clickUrl) throws IOException, GooglePlayServicesNotAvailableException, GooglePlayServicesRepairableException {

            String deviceId = android.provider.Settings.Secure.getString(Aptoide.getContext().getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
            String myid = PreferenceManager.getDefaultSharedPreferences(context).getString(EnumPreferences.APTOIDE_CLIENT_UUID.name(), "NoInfo");

            if (deviceId != null) {
                clickUrl = clickUrl.replace("[USER_ANDROID_ID]", deviceId);
            }

            if (myid != null) {
                clickUrl = clickUrl.replace("[USER_UDID]", myid);
            }

            clickUrl = replaceAdvertisementId(clickUrl, context);
            clickUrl = clickUrl.replace("[TIME_STAMP]", String.valueOf(new Date().getTime()));

            return clickUrl;
        }

        private static String replaceAdvertisementId(String clickUrl, Context context) throws IOException, GooglePlayServicesNotAvailableException, GooglePlayServicesRepairableException {

            String aaId = "";
            if (GoogleServices.checkGooglePlayServices(context)) {
                if (AptoideUtils.getSharedPreferences().contains("advertisingIdClient")) {
                    aaId = AptoideUtils.getSharedPreferences().getString("advertisingIdClient", "");
                } else {
                    try {
                        aaId = AdvertisingIdClient.getAdvertisingIdInfo(context).getId();
                    } catch (Exception e) {
                        // In case we try to do this from a Broadcast Receiver, exception will be thrown.
                        Logger.w("AptoideUtils", e.getMessage());
                    }
                }
            } else {
                byte[] data = new byte[16];
                String deviceId = android.provider.Settings.Secure.getString(context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
                SecureRandom secureRandom = new SecureRandom();
                secureRandom.setSeed(deviceId.hashCode());
                secureRandom.nextBytes(data);
                aaId = UUID.nameUUIDFromBytes(data).toString();
            }

            clickUrl = clickUrl.replace("[USER_AAID]", aaId);

            return clickUrl;
        }

        /**
         * Execute a simple request (knock at the door) to the given URL.
         * @param url
         */
        public static void knock(String url) {
            OkHttpClient client = new OkHttpClient();

            Request click = new Request.Builder().url(url).build();

            client.newCall(click).enqueue(new Callback() {
                @Override
                public void onFailure(Request request, IOException e) {
                }

                @Override
                public void onResponse(Response response) throws IOException {
                }
            });
        }

    }


    public static class GoogleServices {

        public static boolean checkGooglePlayServices(Context context) {
            return GooglePlayServicesUtil.isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
        }

    }

    public static class ServerConnectionUtils {

        public static String getErrorCodeFromErrorList(Context context, List<ErrorResponse> errors) {
            StringBuilder errorMessage = new StringBuilder();
            Integer errorCode;
            if (errors == null) {
                return "";
            }
            for (ErrorResponse error : errors) {
                if (errors.size()>1) {
                    errorMessage.append("\n");
                }
                errorCode = Errors.getErrorsMap().get(error.code);
                if (errorCode != null) {
                    errorMessage.append(context.getString(errorCode));
                } else {
                    errorMessage.append(context.getString(R.string.error_occured));
                }
            }

            if (errors.size()>0&& TextUtils.isEmpty(errorMessage.toString())) {
                errorMessage.append(context.getString(R.string.error_occured));
            }
            return errorMessage.toString();
        }
    }

    public static class Concurrency{
        private static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

        public static void post(final Activity activity, final Runnable runnable, long delayInMillis) {
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    activity.runOnUiThread(runnable);
                }
            }, delayInMillis, TimeUnit.MILLISECONDS);
        }
    }

    private static boolean isNullOrEmpty(Object o) {
        return o == null || "".equals(o);
    }


    /**
     * this class should have all the utils methods related to crashlytics
     */
    public static class CrashlyticsUtils{
        public static final String SCREEN_HISTORY = "SCREEN_HISTORY";
        public static final String NUMBER_OF_SCREENS = "NUMBER_OF_SCREENS";
        public static final String NUMBER_OF_SCREENS_ON_BACK_STACK = "NUMBER_OF_SCREENS_ON_BACK_STACK";
        /**
         * arrayList with all screen names history
         */
        private static ArrayList<String> history = new ArrayList<>();

        /**
         * this var sets the max screens should be added to history
         */
        private static int MAX_HISTORY = 10;

        /**
         * This saves the number of screens showed
         */
        private static int totalNumberScreens = 0;

        /**
         * This saves the number of screens on back stack
         */
        private static int numberScreensOnBackStack = 0;

        /**
         * this method adds a screen name to the history to be reported to crashlytics
         * @param screeName screen name that should be reported to crashlytics
         */
        public static void addScreenToHistory (String screeName) {
            if (BuildConfig.FABRIC_CONFIGURED) {
                addScreen(screeName);
                Crashlytics.setString(SCREEN_HISTORY, history.toString());
            }
        }

        /**
         * Adds the screen to history.
         * @param screeName Name of the screen to add to history.f
         */
        private static void addScreen(String screeName) {
            if (history.size() >= MAX_HISTORY) {
                history.remove(0);
            }
            history.add(screeName);
        }

        public static void subsctibeActivityLiveCycleEvent() {
            BusProvider.getInstance().register(new LifeCycleMonitor());
        }

        /**
         * Updates the total screens showed and the screens on back stack
         *
         * @param isAdding Indicates if it's to update the number due to a new screen (true) or not (false)
         */
        public static void updateNumberOfScreens(boolean isAdding) {
            if (isAdding) {
                totalNumberScreens++;
                numberScreensOnBackStack++;
                Crashlytics.setInt(NUMBER_OF_SCREENS, totalNumberScreens);
                Crashlytics.setInt(NUMBER_OF_SCREENS_ON_BACK_STACK, numberScreensOnBackStack);
            } else {
                numberScreensOnBackStack--;
                Crashlytics.setInt(NUMBER_OF_SCREENS_ON_BACK_STACK, numberScreensOnBackStack);
            }
        }

        public static void resetScreenHistory() {
            if (history != null) {
                history.clear();
            }
        }
    }
}