public class CONST {

    // TODO update this variable
    /**
     * Specific organization ID, received by successful integration.
     */
    public static final int ORGANIZATION_ID = 4;
    /**
     * ID used for simulate empty/null value
     */
    public static final int DEFAULT_EMPTY_ID = -131;

    // Volley requests tags
    public static final String SPLASH_REQUESTS_TAG = "splash_requests";
    public static final String DRAWER_REQUESTS_TAG = "drawer_requests";
    public static final String BANNER_REQUESTS_TAG = "banner_requests";
    public static final String CATEGORY_REQUESTS_TAG = "category_requests";
    public static final String PRODUCT_REQUESTS_TAG = "product_requests";
    public static final String LOGIN_DIALOG_REQUESTS_TAG = "login_dialog_requests";
    public static final String ACCOUNT_REQUESTS_TAG = "account_requests";
    public static final String CART_REQUESTS_TAG = "cart_requests";
    public static final String CART_DISCOUNTS_REQUESTS_TAG = "cart_discounts_requests";
    public static final String ORDER_CREATE_REQUESTS_TAG = "order_create_requests";
    public static final String DELIVERY_DIALOG_REQUESTS_TAG = "delivery_dialog_requests";
    public static final String WISHLIST_REQUESTS_TAG = "wishlist_requests";
    public static final String ACCOUNT_EDIT_REQUESTS_TAG = "account_edit_requests";
    public static final String SETTINGS_REQUESTS_TAG = "settings_requests";
    public static final String UPDATE_CART_ITEM_REQUESTS_TAG = "update_cart_item_requests";
    public static final String MAIN_ACTIVITY_REQUESTS_TAG = "main_activity_requests";
    public static final String PAGE_REQUESTS_TAG = "page_requests";
    public static final String ORDERS_HISTORY_REQUESTS_TAG = "orders_history_requests";
    public static final String ORDERS_DETAIL_REQUESTS_TAG = "orders_detail_requests";

    // Bundle constants
    public static final String BUNDLE_PASS_TARGET = "target";
    public static final String BUNDLE_PASS_TITLE = "title";
    /**
     * Volley request unknown status code
     */
    public static final int MissingStatusCode = 9999;

    /**
     * Possible visibility states of layout parts.
     */
    public enum VISIBLE {
        EMPTY, CONTENT, PROGRESS
    }
} public class MyApplication extends Application {
    public static final String PACKAGE_NAME = MyApplication.class.getPackage().getName();

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


    public static String APP_VERSION = "0.0.0";
    public static String ANDROID_ID = "0000000000000000";

    private static MyApplication mInstance;

    private RequestQueue mRequestQueue;


    public static synchronized MyApplication getInstance() {
        return mInstance;
    }


    /**
     * Method sets app specific language localization by selected shop.
     * Have to be called from every activity.
     *
     * @param lang language code.
     */
    public static void setAppLocale(String lang) {
        Resources res = mInstance.getResources();
        DisplayMetrics dm = res.getDisplayMetrics();
        android.content.res.Configuration conf = res.getConfiguration();
        conf.locale = new Locale(lang);
        Timber.d("Setting language: %s", lang);
        res.updateConfiguration(conf, dm);
    }

    /**
     * Method provides defaultRetryPolice.
     * First Attempt = 14+(14*1)= 28s.
     * Second attempt = 28+(28*1)= 56s.
     * then invoke Response.ErrorListener callback.
     *
     * @return DefaultRetryPolicy object
     */
    public static DefaultRetryPolicy getDefaultRetryPolice() {
        return new DefaultRetryPolicy(14000, 2, 1);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
        FacebookSdk.sdkInitialize(this);

        if (BuildConfig.DEBUG) {
            Timber.plant(new Timber.DebugTree());
        } else {
            Timber.d("Here should come error reporting library initialization and connection.", TAG);
            // TODO example of implementation custom crash reporting solution -  Crashlytics.
//            Fabric.with(this, new Crashlytics());
//            Timber.plant(new CrashReportingTree());
        }


        try {
            ANDROID_ID = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
            if (ANDROID_ID == null || ANDROID_ID.isEmpty()) {
                ANDROID_ID = "0000000000000000";
            }
        } catch (Exception e) {
            ANDROID_ID = "0000000000000000";
        }
        try {
            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
            APP_VERSION = packageInfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            // should never happen
            Timber.e(e, "App versionName not found. WTF?. This should never happen.");
        }
    }

    /**
     * Method check, if internet is available.
     *
     * @return true if internet is available. Else otherwise.
     */
    public boolean isDataConnected() {
        ConnectivityManager connectMan = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = null;
        if (connectMan != null) {
            activeNetworkInfo = connectMan.getActiveNetworkInfo();
        }
        return activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting();
    }

    public boolean isWiFiConnection() {
        ConnectivityManager connectMan = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = null;
        if (connectMan != null) {
            activeNetworkInfo = connectMan.getActiveNetworkInfo();
        }
        return activeNetworkInfo != null && activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////// Volley request ///////////////////////////////////////////////////////////////////////////////////////
    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(this, new OkHttpStack());
        }
        return mRequestQueue;
    }

    @VisibleForTesting
    public void setRequestQueue(RequestQueue requestQueue) {
        mRequestQueue = requestQueue;
    }

    @VisibleForTesting
    public IdlingResource getCountingIdlingResource() {
        return EspressoIdlingResource.getIdlingResource();
    }

    public <T> void addToRequestQueue(Request<T> req, String tag) {
        // set the default tag if tag is empty
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }

    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
    //////////////////////// end of Volley request. ///////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    // TODO example of implementation custom crash reporting solution -  Crashlytics.
//    /**
//     * A tree which logs important information for crash reporting.
//     */
//    private static class CrashReportingTree extends Timber.Tree {
//        @Override
//        protected void log(int priority, String tag, String message, Throwable t) {
//            // Define message log priority
//            if (priority <= android.util.Log.VERBOSE) {
//                return;
//            }
//
//            if (t != null) {
//                if (message != null) logMessage(tag, message);
//                Crashlytics.logException(t);
//            } else {
//                logMessage(tag, message);
//            }
//        }
//
//        private void logMessage(String tag, String message) {
//            if (tag != null)
//                Crashlytics.log("TAG: " + tag + ". MSG: " + message);
//            else
//                Crashlytics.log(message);
//        }
//    }

} public class SettingsMy {
    public static final String PREF_ACTUAL_SHOP = "pref_actual_shop";
    public static final String PREF_ACTIVE_USER = "pref_active_user";
    public static final String PREF_USER_EMAIL = "pref_user_email";

    public static final String SENT_TOKEN_TO_SERVER = "sentTokenToServer";
    public static final String REGISTRATION_COMPLETE = "registrationComplete";

    private static final String TAG = SettingsMy.class.getSimpleName();
    private static Shop actualShop;
    private static User activeUser;
    private static SharedPreferences sharedPref;

    private SettingsMy() {}

    /**
     * Get actually selected shop.
     *
     * @return actual shop or null if not selected.
     */
    public static Shop getActualShop() {
        if (actualShop != null) {
            Timber.d("%s - Returned actual shop", TAG);
            return actualShop;
        } else {
            SharedPreferences prefs = getSettings();
            String json = prefs.getString(PREF_ACTUAL_SHOP, "");
            if (json.isEmpty() || "null".equals(json)) {
                Timber.e("%s - Returned null shop", TAG);
                return null;
            } else {
                actualShop = Utils.getGsonParser().fromJson(json, Shop.class);
                Timber.d("%s - Returned shop from memory: %s", TAG, actualShop.toString());
                return actualShop;
            }
        }
    }

    /**
     * Set actually selected shop.
     *
     * @param actualShop selected shop or null for disable selection.
     */
    public static void setActualShop(Shop actualShop) {
        if (actualShop != null)
            Timber.d("%s - Set selected shop: %s", TAG, actualShop.toString());
        else
            Timber.d("%s - Disable selected shop", TAG);
        SettingsMy.actualShop = actualShop;

        String json = Utils.getGsonParser().toJson(SettingsMy.actualShop);
        SharedPreferences.Editor editor = getSettings().edit();
        editor.putString(PREF_ACTUAL_SHOP, json);
        editor.apply();
    }

    /**
     * Get actually selected shop.
     * If actually selected shop is null, then return to {@link SplashActivity}
     *
     * @param activity corresponding activity.
     * @return actually selected shop or empty shop (when the app will be closed).
     */
    @NonNull
    public static Shop getActualNonNullShop(Activity activity) {
        Shop shop = getActualShop();
        if (shop == null) {
            if (activity != null) {
                MsgUtils.showToast(activity, MsgUtils.TOAST_TYPE_INTERNAL_ERROR, null, MsgUtils.ToastLength.LONG);
                Intent intent = new Intent(activity.getApplicationContext(), SplashActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                activity.startActivity(intent);
            } else {
                Timber.e("Null shop detected also with null activity parameter.");
            }
            // Return an empty shop to prevent a null pointer exception before intent is processed.
            return new Shop();
        } else {
            return shop;
        }
    }

    /**
     * Get active user info.
     *
     * @return user or null if nobody logged in.
     */
    public static User getActiveUser() {
        if (activeUser != null) {
            Timber.d("%s - Returned active user", TAG);
            return activeUser;
        } else {
            SharedPreferences prefs = getSettings();
            String json = prefs.getString(PREF_ACTIVE_USER, "");
            if (json.isEmpty() || "null".equals(json)) {
                Timber.d("%s - Returned null", TAG);
                return null;
            } else {
                activeUser = Utils.getGsonParser().fromJson(json, User.class);
                Timber.d("%s - Returned active user from memory: %s", TAG, activeUser.toString());
                return activeUser;
            }
        }
    }

    /**
     * Set active user.
     *
     * @param user active user or null for disable user.
     */
    public static void setActiveUser(User user) {
        if (user != null)
            Timber.d("%s - Set active user with name: %s", TAG, user.toString());
        else
            Timber.d("%s - Deleting active user", TAG);
        SettingsMy.activeUser = user;

        String json = Utils.getGsonParser().toJson(SettingsMy.activeUser);
        SharedPreferences.Editor editor = getSettings().edit();
        editor.putString(PREF_ACTIVE_USER, json);
        editor.apply();
    }

    /**
     * Get user email. Used for login purpose.
     *
     * @return email of last logged user.
     */
    public static String getUserEmailHint() {
        SharedPreferences prefs = getSettings();
        String userEmail = prefs.getString(PREF_USER_EMAIL, "");
        Timber.d("%s - Obtained user email: %s", TAG, userEmail);
        return userEmail;
    }

    /**
     * Set user email to preferences.
     * Used for login purpose.
     *
     * @param userEmail email of last logged user.
     */
    public static void setUserEmailHint(String userEmail) {
        Timber.d("%s - Set user email: %s", TAG, userEmail);
        putParam(PREF_USER_EMAIL, userEmail);
    }

    /**
     * Get indicator, that GCM token was sent to third party server.
     *
     * @return true if successfully received by third party server. False otherwise.
     */
    public static Boolean getTokenSentToServer() {
        SharedPreferences prefs = getSettings();
        boolean tokenSent = prefs.getBoolean(SENT_TOKEN_TO_SERVER, false);
        Timber.d("%s - Obtained token sent to server: %s", TAG, tokenSent);
        return tokenSent;
    }

    /**
     * Set GCM token sent to third party server indicator.
     *
     * @param tokenSent true if successfully received by server.
     */
    public static void setTokenSentToServer(boolean tokenSent) {
        putParam(SENT_TOKEN_TO_SERVER, tokenSent);
    }

    /**
     * Obtain preferences instance.
     *
     * @return base instance of app SharedPreferences.
     */
    public static SharedPreferences getSettings() {
        if (sharedPref == null) {
            sharedPref = MyApplication.getInstance().getSharedPreferences(MyApplication.PACKAGE_NAME, Context.MODE_PRIVATE);
        }
        return sharedPref;
    }

    private static boolean putParam(String key, String value) {
        SharedPreferences.Editor editor = getSettings().edit();
        editor.putString(key, value);
        return editor.commit();
    }

    private static boolean putParam(String key, boolean value) {
        SharedPreferences.Editor editor = getSettings().edit();
        editor.putBoolean(key, value);
        return editor.commit();
    }
} public class EndPoints {

    /**
     * Base server url.
     */
   // private static final String API_URL                  = "http://77.93.198.186/v1.2/";    // staging
    private static final String API_URL                  = "http://private-4bdb8-bfashionapibfversion.apiary-mock.com/";    // mock

    public static final String SHOPS                    = API_URL.concat(CONST.ORGANIZATION_ID + "/shops");
    public static final String SHOPS_SINGLE             = API_URL.concat(CONST.ORGANIZATION_ID + "/shops/%d");
    public static final String NAVIGATION_DRAWER        = API_URL.concat("%d/navigation_drawer/women"); // TODO 'women' is just a group which can differentiate drawer items for different cases.
    public static final String BANNERS                  = API_URL.concat("%d/banners");
    public static final String PAGES_SINGLE             = API_URL.concat("%d/pages/%d");
    public static final String PAGES_TERMS_AND_COND     = API_URL.concat("%d/pages/terms");
    public static final String PRODUCTS                 = API_URL.concat("%d/products");
    public static final String PRODUCTS_SINGLE          = API_URL.concat("%d/products/%d");
    public static final String PRODUCTS_SINGLE_RELATED  = API_URL.concat("%d/products/%d?include=related");
    public static final String USER_REGISTER            = API_URL.concat("%d/users/register");
    public static final String USER_LOGIN_EMAIL         = API_URL.concat("%d/login/email");
    public static final String USER_LOGIN_FACEBOOK      = API_URL.concat("%d/login/facebook");
    public static final String USER_RESET_PASSWORD      = API_URL.concat("%d/users/reset-password");
    public static final String USER_SINGLE              = API_URL.concat("%d/users/%d");
    public static final String USER_CHANGE_PASSWORD     = API_URL.concat("%d/users/%d/password");
    public static final String CART                     = API_URL.concat("%d/cart");
    public static final String CART_INFO                = API_URL.concat("%d/cart/info");
    public static final String CART_ITEM                = API_URL.concat("%d/cart/%d");
    public static final String CART_DELIVERY_INFO       = API_URL.concat("%d/cart/delivery-info");
    public static final String CART_DISCOUNTS           = API_URL.concat("%d/cart/discounts");
    public static final String CART_DISCOUNTS_SINGLE    = API_URL.concat("%d/cart/discounts/%d");
    public static final String ORDERS                   = API_URL.concat("%d/orders");
    public static final String ORDERS_SINGLE            = API_URL.concat("%d/orders/%d");
    public static final String BRANCHES                 = API_URL.concat("%d/branches");
    public static final String WISHLIST                 = API_URL.concat("%d/wishlist");
    public static final String WISHLIST_SINGLE          = API_URL.concat("%d/wishlist/%d");
    public static final String WISHLIST_IS_IN_WISHLIST  = API_URL.concat("%d/wishlist/is-in-wishlist/%d");
    public static final String REGISTER_NOTIFICATION    = API_URL.concat("%d/devices");


    // Notifications parameters
    public static final String NOTIFICATION_LINK        = "link";
    public static final String NOTIFICATION_MESSAGE     = "message";
    public static final String NOTIFICATION_TITLE       = "title";
    public static final String NOTIFICATION_IMAGE_URL   = "image_url";
    public static final String NOTIFICATION_SHOP_ID     = "shop_id";
    public static final String NOTIFICATION_UTM         = "utm";

    private EndPoints() {}
} public class GsonRequest<T> extends Request<T> {
    /**
     * Default charset for JSON request.
     */
    private static final String PROTOCOL_CHARSET = "utf-8";

    /**
     * Class of object which is serializable from request.
     */
    private final Class<T> clazz;
    /**
     * User token for authorized requests.
     */
    private final String accessToken;
    private final String requestBody;
    private final Response.Listener<T> successListener;
    private String requestUrl;
    private int requestStatusCode;
    private FragmentManager fragmentManager;

    /**
     * Create a new authorized request.
     *
     * @param method          The HTTP method to use
     * @param requestUrl      URL to fetch the JSON from
     * @param requestBody     A {@link String} to post with the request. Null is allowed and
     *                        indicates no parameters will be posted along with request.
     * @param clazz           Relevant class object, for Gson's reflection
     * @param successListener Listener to retrieve successful response
     * @param errorListener   Error listener, or null to ignore errors
     * @param fragmentManager Manager to create re-login dialog on HTTP status 403. Null is allowed.
     * @param accessToken     Token identifying user used for user specific requests.
     */
    public GsonRequest(int method, String requestUrl, String requestBody, Class<T> clazz, Response.Listener<T> successListener,
                       Response.ErrorListener errorListener, FragmentManager fragmentManager, String accessToken) {
        super(method, requestUrl, errorListener);
        this.clazz = clazz;
        this.requestUrl = requestUrl;
        this.requestBody = requestBody;
        this.successListener = successListener;
        this.fragmentManager = fragmentManager;
        this.accessToken = accessToken;
    }

    /**
     * Create a new unauthorized request.
     *
     * @param method          The HTTP method to use
     * @param requestUrl      URL to fetch the JSON from
     * @param requestBody     A {@link String} to post with the request. Null is allowed and
     *                        indicates no parameters will be posted along with request.
     * @param clazz           Relevant class object, for Gson's reflection
     * @param successListener Listener to retrieve successful response
     * @param errorListener   Error listener, or null to ignore errors
     */
    public GsonRequest(int method, String requestUrl, String requestBody, Class<T> clazz, Response.Listener<T> successListener,
                       Response.ErrorListener errorListener) {
        this(method, requestUrl, requestBody, clazz, successListener, errorListener, null, null);
    }


    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        Map<String, String> headers = new HashMap<>();
        headers.put("Client-Version", MyApplication.APP_VERSION);
        headers.put("Device-Token", MyApplication.ANDROID_ID);

        // Determine if request should be authorized.
        if (accessToken != null && !accessToken.isEmpty()) {
            String credentials = accessToken + ":";
            String encodedCredentials = Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
            headers.put("Authorization", "Basic " + encodedCredentials);
        }
        return headers;
    }

    @Override
    public byte[] getBody() {
        try {
            return requestBody == null ? null : requestBody.getBytes(PROTOCOL_CHARSET);
        } catch (UnsupportedEncodingException uee) {
            VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", requestBody, PROTOCOL_CHARSET);
            return null;
        }
    }

    @Override
    protected void deliverResponse(@NonNull T response) {
        successListener.onResponse(response);
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            requestStatusCode = response.statusCode;
            if (BuildConfig.DEBUG)
                Timber.d("%s URL: %s. ResponseCode: %d", this.getClass().getSimpleName(), requestUrl, response.statusCode);

            // Parse response and return obtained object
            String json = new String(response.data, PROTOCOL_CHARSET);
            T result = Utils.getGsonParser().fromJson(json, clazz);
            if (result == null) return Response.error(new ParseError(new NullPointerException()));
            else return Response.success(result, HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        } catch (Exception e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected VolleyError parseNetworkError(VolleyError volleyError) {
        if (volleyError.networkResponse != null) {
            // Save request status code
            requestStatusCode = volleyError.networkResponse.statusCode;
            if (BuildConfig.DEBUG)
                Timber.e("%s URL: %s. ERROR: %s", this.getClass().getSimpleName(), requestUrl, new String(volleyError.networkResponse.data));

            // If AccessToken expired. Logout user and redirect to home page.
            if (getStatusCode() == HttpURLConnection.HTTP_FORBIDDEN && fragmentManager != null) {
                LoginDialogFragment.logoutUser();
                DialogFragment loginExpiredDialogFragment = new LoginExpiredDialogFragment();
                loginExpiredDialogFragment.show(fragmentManager, LoginExpiredDialogFragment.class.getSimpleName());
            }
        } else {
            requestStatusCode = CONST.MissingStatusCode;
        }
        return super.parseNetworkError(volleyError);
    }

    /**
     * Method returns result statusCode of invoked request.
     *
     * @return HTTP standard code.
     */
    public int getStatusCode() {
        return requestStatusCode;
    }
} public class JsonRequest extends JsonObjectRequest {

    /**
     * User token for authorized requests.
     */
    private final String accessToken;
    private int requestStatusCode;
    private FragmentManager fragmentManager;
    private String requestUrl;

    /**
     * Create a new authorized request.
     *
     * @param method          The HTTP method to use
     * @param requestUrl      URL to fetch the JSON from
     * @param jsonRequest     A {@link JSONObject} to post with the request. Null is allowed and
     *                        indicates no parameters will be posted along with request.
     * @param successListener Listener to retrieve successful response
     * @param errorListener   Error listener, or null to ignore errors
     * @param fragmentManager Manager to create re-login dialog on HTTP status 403. Null is allowed.
     * @param accessToken     Token identifying user used for user specific requests.
     */
    public JsonRequest(int method, String requestUrl, JSONObject jsonRequest, Response.Listener<JSONObject> successListener,
                       Response.ErrorListener errorListener, FragmentManager fragmentManager, String accessToken) {
        super(method, requestUrl, jsonRequest, successListener, errorListener);
        this.requestUrl = requestUrl;
        this.fragmentManager = fragmentManager;
        this.accessToken = accessToken;
    }

    /**
     * Create a new unauthorized request.
     *
     * @param method          The HTTP method to use
     * @param requestUrl      URL to fetch the JSON from
     * @param jsonRequest     A {@link JSONObject} to post with the request. Null is allowed and
     *                        indicates no parameters will be posted along with request.
     * @param successListener Listener to retrieve successful response
     * @param errorListener   Error listener, or null to ignore errors
     */
    public JsonRequest(int method, String requestUrl, JSONObject jsonRequest, Response.Listener<JSONObject> successListener,
                       Response.ErrorListener errorListener) {
        this(method, requestUrl, jsonRequest, successListener, errorListener, null, null);
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        Map<String, String> headers = new HashMap<>();
        headers.put("Client-Version", MyApplication.APP_VERSION);
        headers.put("Device-Token", MyApplication.ANDROID_ID);

        // Determine if request should be authorized.
        if (accessToken != null && !accessToken.isEmpty()) {
            String credentials = accessToken + ":";
            String encodedCredentials = Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
            headers.put("Authorization", "Basic " + encodedCredentials);
        }
        return headers;
    }

    @Override
    protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
        try {
            requestStatusCode = response.statusCode;
            if (BuildConfig.DEBUG)
                Timber.d("%s URL: %s. ResponseCode: %d", this.getClass().getSimpleName(), requestUrl, response.statusCode);
        } catch (Exception e) {
            return Response.error(new ParseError(e));
        }
        return super.parseNetworkResponse(response);
    }

    @Override
    protected VolleyError parseNetworkError(VolleyError volleyError) {
        if (volleyError.networkResponse != null) {
            // Save request status code
            requestStatusCode = volleyError.networkResponse.statusCode;
            if (BuildConfig.DEBUG)
                Timber.e("%s URL: %s. ERROR: %s", this.getClass().getSimpleName(), requestUrl, new String(volleyError.networkResponse.data));

            // If AccessToken expired. Logout user and redirect to home page.
            if (getStatusCode() == HttpURLConnection.HTTP_FORBIDDEN && fragmentManager != null) {
                LoginDialogFragment.logoutUser();
                DialogFragment loginExpiredDialogFragment = new LoginExpiredDialogFragment();
                loginExpiredDialogFragment.show(fragmentManager, LoginExpiredDialogFragment.class.getSimpleName());
            }
        } else {
            requestStatusCode = CONST.MissingStatusCode;
        }
        return super.parseNetworkError(volleyError);
    }

    /**
     * Method returns result statusCode of invoked request.
     *
     * @return HTTP status code.
     */
    public int getStatusCode() {
        return requestStatusCode;
    }
} public class ProductVariant {

    private long id;
    private ProductColor color;
    private ProductSize size;
    private String[] images;
    private String code;

    public ProductVariant() {
    }

    public ProductVariant(long id, ProductSize size) {
        this.id = id;
        this.size = size;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public ProductColor getColor() {
        return color;
    }

    public void setColor(ProductColor color) {
        this.color = color;
    }

    public ProductSize getSize() {
        return size;
    }

    public void setSize(ProductSize size) {
        this.size = size;
    }

    public String[] getImages() {
        return images;
    }

    public void setImages(String[] images) {
        this.images = images;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ProductVariant)) return false;

        ProductVariant that = (ProductVariant) o;

        if (getId() != that.getId()) return false;
        if (getColor() != null ? !getColor().equals(that.getColor()) : that.getColor() != null) return false;
        if (getSize() != null ? !getSize().equals(that.getSize()) : that.getSize() != null) return false;
        // Probably incorrect - comparing Object[] arrays with Arrays.equals
        if (!Arrays.equals(getImages(), that.getImages())) return false;
        return !(getCode() != null ? !getCode().equals(that.getCode()) : that.getCode() != null);

    }

    @Override
    public int hashCode() {
        int result = (int) (getId() ^ (getId() >>> 32));
        result = 31 * result + (getColor() != null ? getColor().hashCode() : 0);
        result = 31 * result + (getSize() != null ? getSize().hashCode() : 0);
        result = 31 * result + (getImages() != null ? Arrays.hashCode(getImages()) : 0);
        result = 31 * result + (getCode() != null ? getCode().hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "ProductVariant{" +
                "id=" + id +
                ", color=" + color +
                ", size=" + size +
                ", images=" + Arrays.toString(images) +
                ", code='" + code + '\'' +
                '}';
    }
} public class JsonUtils {

    // Server specific JSON tags
    public static final String TAG_ID = "id";
    public static final String TAG_FB_ID = "fb_id";
    public static final String TAG_FB_ACCESS_TOKEN = "fb_access_token";
    public static final String TAG_PRODUCT_VARIANT_ID = "product_variant_id";
    public static final String TAG_IS_IN_WISHLIST = "is_in_wishlist";
    public static final String TAG_WISHLIST_PRODUCT_ID = "wishlist_product_id";
    public static final String TAG_QUANTITY = "quantity";
    public static final String TAG_CODE = "code";
    public static final String TAG_PRODUCT_COUNT = "product_count";

    // Server specific JSON tags - user oriented
    public static final String TAG_EMAIL = "email";
    public static final String TAG_PASSWORD = "password";
    public static final String TAG_OLD_PASSWORD = "old_password";
    public static final String TAG_NEW_PASSWORD = "new_password";
    public static final String TAG_NAME = "name";
    public static final String TAG_STREET = "street";
    public static final String TAG_HOUSE_NUMBER = "house_number";
    public static final String TAG_CITY = "city";
    public static final String TAG_REGION = "region";
    public static final String TAG_ZIP = "zip";
    public static final String TAG_PHONE = "phone";
    public static final String TAG_GENDER = "gender";

    // ORDERS
    public static final String TAG_DATE_CREATED = "date_created";
    public static final String TAG_STATUS = "status";
    public static final String TAG_TOTAL = "total";
    public static final String TAG_SHIPPING_TYPE = "shipping_type";
    public static final String TAG_PAYMENT_TYPE = "payment_type";
    public static final String TAG_SHIPPING_NAME = "shipping_name";
    public static final String TAG_TOTAL_FORMATTED = "total_formatted";
    public static final String TAG_SHIPPING_PRICE_FORMATTED = "shipping_price_formatted";
    public static final String TAG_NOTE = "note";

    private JsonUtils() {}


    /**
     * @param order create json from Order object
     * @return return converted order JSON
     * @throws JSONException throw exception
     */
    public static JSONObject createOrderJson(Order order) throws JSONException {
        JSONObject jo = new JSONObject();

        jo.put(TAG_SHIPPING_TYPE, order.getShippingType());
        if (order.getPaymentType() == -1) {
            jo.put(TAG_PAYMENT_TYPE, null);
        } else {
            jo.put(TAG_PAYMENT_TYPE, order.getPaymentType());
        }

        jo.put(TAG_NAME, order.getName());
        jo.put(TAG_STREET, order.getStreet());
        jo.put(TAG_HOUSE_NUMBER, order.getHouseNumber());
        jo.put(TAG_CITY, order.getCity());
        jo.put(TAG_ZIP, order.getZip());
        jo.put(TAG_EMAIL, order.getEmail());
        jo.put(TAG_PHONE, order.getPhone());

        if (order.getNote() != null) {
            jo.put(TAG_NOTE, order.getNote());
        }

        if (order.getRegion() != null) {
            jo.put(TAG_REGION, order.getRegion().getId());
        }

        Timber.d("JSONParser postOrder: %s", jo.toString());
        return jo;
    }

    public static JSONObject createUserAuthentication(String email, String password) throws JSONException {
        JSONObject jo = new JSONObject();
        jo.put(JsonUtils.TAG_EMAIL, email);
        jo.put(JsonUtils.TAG_PASSWORD, password);
        return jo;
    }
} public class MsgUtils {

    public static final int TOAST_TYPE_MESSAGE = 1;
    public static final int TOAST_TYPE_INTERNAL_ERROR = 2;
    public static final int TOAST_TYPE_NO_NETWORK = 3;
    public static final int TOAST_TYPE_NO_SIZE_SELECTED = 5;

    public static void logErrorMessage(VolleyError error) {
        try {
            String errorData = new String(error.networkResponse.data);
            showMessage(null, new JSONObject(errorData));
        } catch (Exception e) {
            if (error.getMessage() != null && !error.getMessage().isEmpty())
                Timber.e(e, error.getMessage());
            else
                Timber.e(e, "Cannot parse error message");
        }
    }

    public static void logAndShowErrorMessage(Activity activity, VolleyError error) {
        try {
            String errorData = new String(error.networkResponse.data);
            showMessage(activity, new JSONObject(errorData));
        } catch (Exception e) {
            if (error.getMessage() != null && !error.getMessage().isEmpty())
                Timber.e(e, error.getMessage());
            else
                Timber.e(e, "Cannot parse error message");
            showToast(activity, TOAST_TYPE_INTERNAL_ERROR, null, ToastLength.SHORT);
        }
    }

    public static void showMessage(Activity activity, JSONObject message) {
        try {
            JSONArray body = message.getJSONArray("body");
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < body.length(); i++) {
                sb.append(body.get(i));
                sb.append("\n");
            }
            String result = sb.toString();
            Timber.e("Error message: %s", result);
            if (activity != null)
                MsgUtils.showToast(activity, TOAST_TYPE_MESSAGE, result, ToastLength.LONG);
        } catch (Exception e) {
            Timber.e(e, "ShowMessage exception");
            if (activity != null)
                MsgUtils.showToast(activity, TOAST_TYPE_INTERNAL_ERROR, null, ToastLength.SHORT);
        }
    }

    /**
     * Show custom Toast Message.
     *
     * @param activity  Activity for show toast.
     * @param toastType Type of toast.
     * @param message   String to show.
     */
    public static void showToast(Activity activity, int toastType, String message, ToastLength toastLength) {
        if (activity == null) {
            Timber.e(new RuntimeException(), "Called showToast with null activity.");
            return;
        }
        LayoutInflater inflater = activity.getLayoutInflater();
        View layout = inflater.inflate(R.layout.toast_custom, (ViewGroup) activity.findViewById(R.id.toast_layout_root));

        TextView text = layout.findViewById(R.id.toast_text);
        ImageView iv = layout.findViewById(R.id.toast_image);
        String str = "";
        int icon = 0;

        Toast toast = new Toast(activity);
        switch (toastLength) {
            case SHORT:
                toast.setDuration(Toast.LENGTH_SHORT);
                break;
            case LONG:
                toast.setDuration(Toast.LENGTH_LONG);
                break;
            default:
                Timber.e("Not implemented");
        }

        switch (toastType) {
            case TOAST_TYPE_MESSAGE:
                str = message;
                break;
            case TOAST_TYPE_INTERNAL_ERROR:
                str = activity.getString(R.string.Internal_error);
                break;
            case TOAST_TYPE_NO_NETWORK:
                str = activity.getString(R.string.No_network_connection);
                break;
            case TOAST_TYPE_NO_SIZE_SELECTED:
                str = activity.getString(R.string.Please_select_a_size);
                break;
        }

        text.setText(str);
        if (icon != 0) {
            iv.setImageResource(icon);
            iv.setVisibility(View.VISIBLE);
        } else {
            iv.setVisibility(View.GONE);
        }

        toast.setView(layout);
        toast.show();
    }

    public enum ToastLength {
        SHORT, LONG
    }
} public class CartColorTextSpinnerAdapter extends ArrayAdapter<ProductColor> {

    private static final int layoutID = R.layout.spinner_item_simple_text;
    private final LayoutInflater layoutInflater;

    private List<ProductColor> colors;

    /**
     * Creates an adapter for color selection. Color is represented only by text.
     *
     * @param context activity context.
     * @param colors  list of items.
     */
    public CartColorTextSpinnerAdapter(Context context, List<ProductColor> colors) {
        super(context, layoutID, colors);
        this.colors = colors;
        this.layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public int getCount() {
        return colors.size();
    }

    public ProductColor getItem(int position) {
        return colors.get(position);
    }

    public long getItemId(int position) {
        return colors.get(position).getId();
    }

    @Override
    public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) {
        return getCustomView(position, convertView, parent);
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        return getCustomView(position, convertView, parent);
    }

    private View getCustomView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        ListItemHolder holder;

        if (v == null) {
            v = layoutInflater.inflate(layoutID, parent, false);
            holder = new ListItemHolder();
            holder.colorText = v.findViewById(R.id.text);
            v.setTag(holder);
        } else {
            holder = (ListItemHolder) v.getTag();
        }

        holder.colorText.setText(getItem(position).getValue());
        return v;
    }

    static class ListItemHolder {
        TextView colorText;
    }
} public class CartSizeSpinnerAdapter extends ArrayAdapter<ProductVariant> {

    private static final int layoutID = R.layout.spinner_item_simple_text;
    private final LayoutInflater layoutInflater;

    private List<ProductVariant> sizes;

    /**
     * Creates an adapter for size selection.
     *
     * @param context activity context.
     * @param sizes   list of items.
     */
    public CartSizeSpinnerAdapter(Context context, List<ProductVariant> sizes) {
        super(context, layoutID, sizes);
        this.sizes = sizes;
        this.layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public int getCount() {
        return sizes.size();
    }

    public ProductVariant getItem(int position) {
        return sizes.get(position);
    }

    public long getItemId(int position) {
        return sizes.get(position).getId();
    }

    @Override
    public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) {
        return getCustomView(position, convertView, parent);
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        return getCustomView(position, convertView, parent);
    }

    private View getCustomView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        ListItemHolder holder;

        if (v == null) {
            v = layoutInflater.inflate(layoutID, parent, false);
            holder = new ListItemHolder();
            holder.text = v.findViewById(R.id.text);
            v.setTag(holder);
        } else {
            holder = (ListItemHolder) v.getTag();
        }

        if (getItem(position) != null && getItem(position).getSize() != null) {
            holder.text.setText(getItem(position).getSize().getValue());
        } else {
            Timber.e("Received null productSize in %s", this.getClass().getSimpleName());
        }

        return v;
    }

    static class ListItemHolder {
        TextView text;
    }
} public class QuantitySpinnerAdapter extends ArrayAdapter<ProductQuantity> {

    private static final int layoutID = R.layout.spinner_item_simple_text;
    private final LayoutInflater layoutInflater;

    private List<ProductQuantity> quantities;

    /**
     * Creates an adapter for quantity selection.
     *
     * @param context    activity context.
     * @param quantities list of items.
     */
    public QuantitySpinnerAdapter(Context context, List<ProductQuantity> quantities) {
        super(context, layoutID, quantities);
        this.quantities = quantities;
        this.layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public int getCount() {
        return quantities.size();
    }

    public ProductQuantity getItem(int position) {
        return quantities.get(position);
    }

    public long getItemId(int position) {
        return quantities.get(position).getQuantity();
    }

    @Override
    public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) {
        return getCustomView(position, convertView, parent);
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        return getCustomView(position, convertView, parent);
    }

    private View getCustomView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        ListItemHolder holder;

        if (v == null) {
            v = layoutInflater.inflate(layoutID, parent, false);
            holder = new ListItemHolder();
            holder.text = v.findViewById(R.id.text);
            v.setTag(holder);
        } else {
            holder = (ListItemHolder) v.getTag();
        }

        if (getItem(position) != null) {
            holder.text.setText(getItem(position).getValue());
        } else {
            Timber.e("Received null value in %s", this.getClass().getSimpleName());
        }

        return v;
    }

    static class ListItemHolder {
        TextView text;
    }

}
import android.app.Dialog;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.Spinner;
import android.widget.TextView;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import bf.io.openshop.CONST;
import bf.io.openshop.MyApplication;
import bf.io.openshop.R;
import bf.io.openshop.SettingsMy;
import bf.io.openshop.api.EndPoints;
import bf.io.openshop.api.GsonRequest;
import bf.io.openshop.api.JsonRequest;
import bf.io.openshop.entities.User;
import bf.io.openshop.entities.cart.CartProductItem;
import bf.io.openshop.entities.product.Product;
import bf.io.openshop.entities.product.ProductColor;
import bf.io.openshop.entities.product.ProductQuantity;
import bf.io.openshop.entities.product.ProductVariant;
import bf.io.openshop.interfaces.RequestListener;
import bf.io.openshop.utils.JsonUtils;
import bf.io.openshop.utils.MsgUtils;
import bf.io.openshop.UX.adapters.CartColorTextSpinnerAdapter;
import bf.io.openshop.UX.adapters.CartSizeSpinnerAdapter;
import bf.io.openshop.UX.adapters.QuantitySpinnerAdapter;
import timber.log.Timber;
        quantitySpinner = view.findViewById(R.id.dialog_update_cart_item_quantity_spin);
        quantitySpinner.setAdapter(adapterQuantity);

        getProductDetail(cartProductItem);
        return view;
    }

    // Prepare quantity spinner layout
    private List<ProductQuantity> getQuantities() {
        List<ProductQuantity> quantities = new ArrayList<>();
        for (int i = 1; i <= QUANTITY_MAX; i++) {
            ProductQuantity q = new ProductQuantity(i, i + "x");
            quantities.add(q);
        }
        return quantities;
    }

    private void getProductDetail(CartProductItem cartProductItem) {
        String url = String.format(EndPoints.PRODUCTS_SINGLE, SettingsMy.getActualNonNullShop(getActivity()).getId(), cartProductItem.getVariant().getProductId());

        setProgressActive(true);

        GsonRequest<Product> getProductRequest = new GsonRequest<>(Request.Method.GET, url, null, Product.class,
                new Response.Listener<Product>() {
                    @Override
                    public void onResponse(@NonNull Product response) {
                        setProgressActive(false);
                        setSpinners(response);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                setProgressActive(false);
                MsgUtils.logAndShowErrorMessage(getActivity(), error);
            }
        });
        getProductRequest.setRetryPolicy(MyApplication.getDefaultRetryPolice());
        getProductRequest.setShouldCache(false);
        MyApplication.getInstance().addToRequestQueue(getProductRequest, CONST.UPDATE_CART_ITEM_REQUESTS_TAG);
    }


    private void setSpinners(final Product product) {
        if (product != null && product.getVariants() != null && product.getVariants().size() > 0) {
            List<ProductColor> productColors = new ArrayList<>();

            for (ProductVariant pv : product.getVariants()) {
                ProductColor pac = pv.getColor();
                if (!productColors.contains(pac)) {
                    productColors.add(pac);
                }
            }

            if (productColors.size() > 1) {
                itemColorsSpinner.setVisibility(View.VISIBLE);
                CartColorTextSpinnerAdapter adapterColor = new CartColorTextSpinnerAdapter(getActivity(), productColors);
                itemColorsSpinner.setAdapter(adapterColor);
                ProductColor actualItemColor = cartProductItem.getVariant().getColor();
                if (actualItemColor != null) {
                    int index = productColors.indexOf(actualItemColor);
                    Timber.d("SetSpinners selectedColor: %s", actualItemColor.toString());
                    if (index == -1) {
                        itemColorsSpinner.setSelection(0);
                        Timber.e(new NullPointerException(), "Actual item color didn't match server received item colors");
                    } else {
                        itemColorsSpinner.setSelection(index);
                    }
                }

                itemColorsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                    @Override
                    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                        ProductColor productColor = (ProductColor) parent.getItemAtPosition(position);
                        if (productColor != null) {
                            Timber.d("ColorPicker selected color: %s", productColor.toString());
                            updateSizeSpinner(product, productColor);
                        } else {
                            Timber.e("Retrieved null color from spinner.");
                        }
                    }

                    @Override
                    public void onNothingSelected(AdapterView<?> parent) {
                        Timber.d("Nothing selected in product colors spinner.");
                    }
                });
            } else {
                itemColorsSpinner.setVisibility(View.GONE);
                updateSizeSpinner(product, product.getVariants().get(0).getColor());
            }

            int selectedPosition = cartProductItem.getQuantity() - 1;
            if (selectedPosition < 0) selectedPosition = 0;
            if (selectedPosition > (quantitySpinner.getCount() - 1))
                Timber.e(new RuntimeException(), "More item quantity that can be. Quantity: %d, max: %d", (selectedPosition + 1), quantitySpinner.getCount());
            else
                quantitySpinner.setSelection(selectedPosition);
        } else {
            Timber.e("Setting spinners for null product variants.");
            MsgUtils.showToast(getActivity(), MsgUtils.TOAST_TYPE_INTERNAL_ERROR, null, MsgUtils.ToastLength.SHORT);
        }
    }

    /**
     * Update size values in size adapter.
     *
     * @param product      updated product.
     * @param productColor actually selected color.
     */
    private void updateSizeSpinner(Product product, ProductColor productColor) {
        if (product != null) {
            ArrayList<ProductVariant> variantSizeArrayList = new ArrayList<>();

            for (ProductVariant pv : product.getVariants()) {
                if (pv.getColor().equals(productColor)) {
                    variantSizeArrayList.add(pv);
                }
            }

            // Show sizes
            CartSizeSpinnerAdapter adapterSize = new CartSizeSpinnerAdapter(getActivity(), variantSizeArrayList);