// ReactNativeSlangFitnessAssistantModule.java

package in.slanglabs;

import static in.slanglabs.assistants.fitness.appstates.GreetingsAppState.CONTINUE;

import android.app.Application;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import java.net.URL;
import java.util.Locale;
import java.util.Map;

import in.slanglabs.assistants.base.BaseUI;
import in.slanglabs.assistants.fitness.AssistantConfiguration;
import in.slanglabs.assistants.fitness.AssistantError;
import in.slanglabs.assistants.fitness.AssistantUI;
import in.slanglabs.assistants.fitness.FitnessUserJourney;
import in.slanglabs.assistants.fitness.Food;
import in.slanglabs.assistants.fitness.FoodInfo;
import in.slanglabs.assistants.fitness.FoodLoggingUserJourney;
import in.slanglabs.assistants.fitness.NavigationInfo;
import in.slanglabs.assistants.fitness.NavigationUserJourney;
import in.slanglabs.assistants.fitness.SlangFitnessAssistant;
import in.slanglabs.assistants.fitness.SmallTalkUserJourney;
import in.slanglabs.assistants.fitness.SugarInfo;
import in.slanglabs.assistants.fitness.SugarLoggingUserJourney;
import in.slanglabs.assistants.fitness.WeightInfo;
import in.slanglabs.assistants.fitness.WeightLoggingUserJourney;
import in.slanglabs.assistants.fitness.appstates.FarewellAppState;
import in.slanglabs.assistants.fitness.appstates.FitnessCondition;
import in.slanglabs.assistants.fitness.appstates.FoodLoggingAppState;
import in.slanglabs.assistants.fitness.appstates.FoodLoggingCompleteAppState;
import in.slanglabs.assistants.fitness.appstates.GreetingsAppState;
import in.slanglabs.assistants.fitness.appstates.NavigationAppState;
import in.slanglabs.assistants.fitness.appstates.SmallTalkAppState;
import in.slanglabs.assistants.fitness.appstates.SugarLoggingAppState;
import in.slanglabs.assistants.fitness.appstates.SugarLoggingCompleteAppState;
import in.slanglabs.assistants.fitness.appstates.UnsupportedAppState;
import in.slanglabs.assistants.fitness.appstates.WaitingAppState;
import in.slanglabs.assistants.fitness.appstates.WeightLoggingAppState;
import in.slanglabs.assistants.fitness.appstates.WeightLoggingCompleteAppState;


public class RNSlangFitnessAssistantModule extends ReactContextBaseJavaModule {

    private final ReactApplicationContext mReactContext;

    private static final String TAG = "RNSlangFitnessAssistant";
    private static final String CONFIG_ASSISTANT_ID = "assistantId";
    private static final String CONFIG_API_KEY = "apiKey";
    private static final String CONFIG_LOCALE = "defaultLocale";
    private static final String CONFIG_REQUESTED_LOCALES = "requestedLocales";
    private static final String CONFIG_TRIGGER_POSITION = "triggerPosition";
    private static final String CONFIG_CUSTOM_TRIGGER_ENABLE = "enableCustomTrigger";
    private static final String CONFIG_ENABLE_STRICT_MODE = "enableStrictMode";
    private static final String CONFIG_ONBOARDING_INFO_TITLE = "onBoardingInfoTitle";
    private static final String CONFIG_ONBOARDING_INFO_DESCRIPTION = "onBoardingInfoDescription";
    private static final String CONFIG_DEFAULT_SUBDOMAIN = "defaultSubdomain";
    private static final String CONFIG_BRAND_COLOR = "brandColor";
    private static final String CONFIG_DEFAULT_USER_JOURNEY = "defaultUserJourney";
    private static final String CONFIG_ENVIRONMENT = "environment";
    private static final String CONFIG_ONBOARDING_IDENTIFIER = "onboardingIdentifier";
    private static final String CONFIG_ONBOARDING_INFO_TITLE_SIZE = "onboardingInfoTitleSize";
    private static final String CONFIG_ONBOARDING_PRIMARY_TITLE = "onboardingPrimaryTitle";
    private static final String CONFIG_ONBOARDING_PRIMARY_DESCRIPTION = "onboardingPrimaryDescription";
    private static final String CONFIG_AUTOMATIC_APPEARANCE_LIMIT = "automaticAppearanceLimit";
    private static final String CONFIG_COACHMARK_VISIBILITY = "coachmarkVisibility";
    private static final String CONFIG_DISABLE_ONBOARDING_LOCALE_SELECTION = "disableOnboardingLocaleSelection";
    private static final String CONFIG_DISABLE_SURFACE_LOCALE_SELECTION = "disableSurfaceLocaleSelection";
    private static final String CONFIG_DISABLE_COACHMARK = "disableCoachMark";
    private static final String CONFIG_INFO_TEXT_COLOR = "infoTextColor";
    private static final String CONFIG_ACCENT_COLOR = "accentColor";
    private static final String CONFIG_TRIGGER_STYLE = "triggerStyle";
    private static final String CONFIG_SURFACE_STYLE = "surfaceStyle";

    private FoodLoggingUserJourney mFoodLoggingUserJourney;
    private SugarLoggingUserJourney mSugarLoggingUserJourney;
    private NavigationUserJourney mNavigationUserJourney;
    private  WeightLoggingUserJourney mWeightLoggingUserJourney;

    public RNSlangFitnessAssistantModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.mReactContext = reactContext;
        setAction();
    }

    @Override
    public String getName() {
        return "RNSlangFitnessAssistant";
    }

    /******************************** General Methods ******************************************/

    @ReactMethod
    public void initialize(ReadableMap configOptions) {
        Log.e(TAG, "initialize");
        RNSlangUtils.setConfigOptions(configOptions);
        try {
            AssistantConfiguration.Builder configBuilder = new AssistantConfiguration.Builder()
                    .setAssistantId(RNSlangUtils.getStringConfig(CONFIG_ASSISTANT_ID))
                    .setAPIKey(RNSlangUtils.getStringConfig(CONFIG_API_KEY))
                    .setDefaultLocale(RNSlangLocaleMap.getDefaultLocale(RNSlangUtils.getStringConfig(
                            CONFIG_LOCALE)))
                    .setRequestedLocales(
                            RNSlangLocaleMap.getRequestedLocales(RNSlangUtils.getStringListConfig(
                                    CONFIG_REQUESTED_LOCALES)))
                    .setUIPosition(RNSlangUtils.getAssistantUIConfig(CONFIG_TRIGGER_POSITION))
                    .enableStrictMode(RNSlangUtils.getBooleanConfig(CONFIG_ENABLE_STRICT_MODE))
                    .setDefaultSubDomain(RNSlangUtils.getAssistantDefaultSubdomainConfig(CONFIG_DEFAULT_SUBDOMAIN))
                    .setOnboardingInfoTitle(RNSlangUtils.getLocaleStringMapConfig(CONFIG_ONBOARDING_INFO_TITLE))
                    .setOnboardingInfoDescription(RNSlangUtils.getLocaleStringMapConfig(CONFIG_ONBOARDING_INFO_DESCRIPTION))
                    .enableCustomTrigger(RNSlangUtils.getBooleanConfig(CONFIG_CUSTOM_TRIGGER_ENABLE))
                    .setTriggerStyle(RNSlangUtils.getTriggerStyle(CONFIG_TRIGGER_STYLE))
                    .setSurfaceStyle(RNSlangUtils.getSurfaceStyle(CONFIG_SURFACE_STYLE));

            if (RNSlangUtils.configHasKey(CONFIG_DEFAULT_USER_JOURNEY)) configBuilder.setDefaultUserJourney(FitnessUserJourney.fromString(RNSlangUtils.getStringConfig(CONFIG_DEFAULT_USER_JOURNEY)));
            if (RNSlangUtils.configHasKey(CONFIG_ONBOARDING_IDENTIFIER)) configBuilder.setOnboardingIdentifer(RNSlangUtils.getStringConfig(CONFIG_ONBOARDING_IDENTIFIER));
            if (RNSlangUtils.configHasKey(CONFIG_ONBOARDING_PRIMARY_TITLE)) configBuilder.setOnboardingPrimaryTitle(RNSlangUtils.getLocaleStringMapConfig(CONFIG_ONBOARDING_PRIMARY_TITLE));
            if (RNSlangUtils.configHasKey(CONFIG_ONBOARDING_PRIMARY_DESCRIPTION)) configBuilder.setOnboardingPrimaryDescription(RNSlangUtils.getLocaleStringMapConfig(CONFIG_ONBOARDING_PRIMARY_DESCRIPTION));
            if (RNSlangUtils.configHasKey(CONFIG_AUTOMATIC_APPEARANCE_LIMIT)) configBuilder.setAutomaticAppearanceLimit(RNSlangUtils.getIntConfig(CONFIG_AUTOMATIC_APPEARANCE_LIMIT));
            if (RNSlangUtils.configHasKey(CONFIG_COACHMARK_VISIBILITY)) configBuilder.setCoachmarkVisibility(RNSlangUtils.getBooleanConfig(CONFIG_COACHMARK_VISIBILITY));
            if (RNSlangUtils.configHasKey(CONFIG_DISABLE_ONBOARDING_LOCALE_SELECTION)) configBuilder.disableOnboardingLocaleSelection(RNSlangUtils.getBooleanConfig(CONFIG_DISABLE_ONBOARDING_LOCALE_SELECTION));
            if (RNSlangUtils.configHasKey(CONFIG_DISABLE_SURFACE_LOCALE_SELECTION)) configBuilder.disableSurfaceLocaleSelection(RNSlangUtils.getBooleanConfig(CONFIG_DISABLE_SURFACE_LOCALE_SELECTION));
            if (RNSlangUtils.configHasKey(CONFIG_DISABLE_COACHMARK)) configBuilder.disableCoachMark(RNSlangUtils.getBooleanConfig(CONFIG_DISABLE_COACHMARK));
            if (RNSlangUtils.configHasKey(CONFIG_INFO_TEXT_COLOR)) configBuilder.setInfoTextColor(RNSlangUtils.getStringConfig(CONFIG_INFO_TEXT_COLOR));
            if (RNSlangUtils.configHasKey(CONFIG_ACCENT_COLOR)) configBuilder.setAccentColor(RNSlangUtils.getStringConfig(CONFIG_ACCENT_COLOR));
            if (RNSlangUtils.configHasKey(CONFIG_BRAND_COLOR)) configBuilder.setBrandColor(RNSlangUtils.getStringConfig(CONFIG_BRAND_COLOR));
            if (RNSlangUtils.configHasKey(CONFIG_ONBOARDING_INFO_TITLE_SIZE)) configBuilder.setOnboardingInfoTitleSize(RNSlangUtils.getFloatConfig(CONFIG_ONBOARDING_INFO_TITLE_SIZE));
            if (RNSlangUtils.configHasKey(CONFIG_ENVIRONMENT)) configBuilder.setEnvironment(RNSlangUtils.getEnvironmentFromString(RNSlangUtils.getStringConfig(CONFIG_ENVIRONMENT)));
//            if (RNSlangUtils.configHasKey(CONFIG_BRAND_COLOR)) configBuilder.setConfigOverrides();

            AssistantConfiguration config = configBuilder.build();

            if (mReactContext.getApplicationContext() instanceof Application) {
                SlangFitnessAssistant.initialize((Application) mReactContext.getApplicationContext(), config);
            } else if (getCurrentActivity() != null) {
                SlangFitnessAssistant.initialize(getCurrentActivity().getApplication(), config);
            } else {
                WritableMap params = Arguments.createMap();
                params.putString("type", "Invalid call to initialize");
                sendEvent("onAssistantInitFailure", params);
            }
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setAction() {
        try {
            SlangFitnessAssistant.setAction(new SlangFitnessAssistant.Action() {
                @Override
                public FoodLoggingAppState onFoodLog(final FoodInfo foodInfo, FoodLoggingUserJourney foodLoggingUserJourney) {
                    mFoodLoggingUserJourney = foodLoggingUserJourney;
                    new Handler().post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                WritableMap foodInfoMap = RNSlangUtils.mapFoodInfo(foodInfo);
                                sendEvent("onFoodLog", foodInfoMap);
                            } catch (Exception exception) {
                                sendAssistantError(exception);
                            }
                        }
                    });
                    return new WaitingAppState();
                }

                @Override
                public NavigationAppState onNavigation(final NavigationInfo navigationInfo, NavigationUserJourney navigationUserJourney) {
                    mNavigationUserJourney = navigationUserJourney;
                    new Handler().post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                WritableMap navigationInfoMap = RNSlangUtils.mapNavigationInfo(navigationInfo);
                                sendEvent("onNavigation", navigationInfoMap);
                            } catch (Exception exception) {
                                sendAssistantError(exception);
                            }
                        }
                    });
                    return new UnsupportedAppState();
                }

                @Override
                public SugarLoggingAppState onSugarLog(final SugarInfo sugarInfo, SugarLoggingUserJourney sugarLoggingUserJourney) {
                    mSugarLoggingUserJourney = sugarLoggingUserJourney;
                    new Handler().post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                WritableMap sugarInfoMap = RNSlangUtils.mapSugarInfo(sugarInfo);
                                sendEvent("onSugarLog", sugarInfoMap);
                            } catch (Exception exception) {
                                sendAssistantError(exception);
                            }
                        }
                    });
                    return new WaitingAppState();
                }

                @Override
                public WeightLoggingAppState onWeightLog(final WeightInfo weightInfo, WeightLoggingUserJourney weightLoggingUserJourney) {
                    mWeightLoggingUserJourney = weightLoggingUserJourney;
                    new Handler().post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                WritableMap weightInfoMap = RNSlangUtils.mapWeightInfo(weightInfo);
                                sendEvent("onWeightLog", weightInfoMap);
                            } catch (Exception exception) {
                                sendAssistantError(exception);
                            }
                        }
                    });
                    return new WaitingAppState();
                }

                @Override
                public SmallTalkAppState onSmallTalk(SmallTalkUserJourney.SmallTalkType smallTalkType, SmallTalkUserJourney smallTalkUserJourney) {
                    if (smallTalkType == SmallTalkUserJourney.SmallTalkType.GREETING) {
                        return new GreetingsAppState(CONTINUE);
                    }
                    else if (smallTalkType == SmallTalkUserJourney.SmallTalkType.FAREWELL) {
                        return new FarewellAppState(FarewellAppState.COMPLETE);
                    }
                    return null;
                }

                @Override
                public void onAssistantError(AssistantError assistantError) {
                    Log.d(TAG, String.valueOf(assistantError));
                    WritableMap params = Arguments.createMap();
                    params.putInt("type", assistantError.getType());
                    sendEvent("onAssistantError", params);
                }
            });
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setLifecycleObserver() {
        try {
            SlangFitnessAssistant.setLifecycleObserver(new SlangFitnessAssistant.LifecycleObserver() {
                @Override
                public void onAssistantInitSuccess() {
                    sendEvent("onAssistantInitSuccess", Arguments.createMap());
                }

                @Override
                public void onAssistantInitFailure(String s) {
                    Log.d(TAG, s);
                    WritableMap params = Arguments.createMap();
                    params.putString("type", s);
                    sendEvent("onAssistantInitFailure", params);
                }

                @Override
                public void onAssistantInvoked() {
                    sendEvent("onAssistantInvoked", Arguments.createMap());
                }

                @Override
                public void onAssistantClosed(boolean b) {
                    WritableMap params = Arguments.createMap();
                    params.putBoolean("isCancelled", b);
                    sendEvent("onAssistantClosed", params);
                }

                @Override
                public void onAssistantLocaleChanged(Locale locale) {
                    WritableMap localeMap = Arguments.createMap();
                    localeMap.putString("country", locale.getCountry().toUpperCase());
                    localeMap.putString("language", locale.getLanguage());
                    sendEvent("onAssistantLocaleChanged", localeMap);
                }

                @Override
                public boolean onUnrecognisedUtterance(String s) {
                    WritableMap params = Arguments.createMap();
                    params.putString("utterance", s);
                    sendEvent("onUnrecognisedUtterance", params);
                    return false;
                }

                @Override
                public void onUtteranceDetected(String s) {
                    WritableMap params = Arguments.createMap();
                    params.putString("utterance", s);
                    sendEvent("onUtteranceDetected", params);
                }

                @Override
                public void onOnboardingSuccess() {
                    sendEvent("onOnboardingSuccess", Arguments.createMap());
                }

                @Override
                public void onOnboardingFailure() {
                    sendEvent("onOnboardingFailure", Arguments.createMap());
                }

                @Override
                public void onMicPermissionDenied() {
                    sendEvent("onMicPermissionDenied", Arguments.createMap());
                }

                @Override
                public void onMicPermissionGranted() {
                    sendEvent("onMicPermissionGranted", Arguments.createMap());
                }
            });
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void startConversation(String userJourney) {
        if (getCurrentActivity() != null) {
            try {
                SlangFitnessAssistant.startConversation(FitnessUserJourney.fromString(userJourney),
                        getCurrentActivity());
            } catch (Exception exception) {
                sendAssistantError(exception);
            }
        }
    }

    @ReactMethod
    public void prepareActiveFoodLoggingUserJourney() {
        try {
            mFoodLoggingUserJourney = SlangFitnessAssistant.getActiveFoodLoggingUserJourney();
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }
    @ReactMethod
    public void prepareActiveSugarLoggingUserJourney() {
        try {
            mSugarLoggingUserJourney = SlangFitnessAssistant.getActiveSugarLoggingUserJourney();
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void prepareActiveWeightLoggingUserJourney() {
        try {
            mWeightLoggingUserJourney = SlangFitnessAssistant.getActiveWeightLoggingUserJourney();
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void cancelSession() {
        if (getCurrentActivity() != null) {
            try {
                SlangFitnessAssistant.cancelSession();
            } catch (Exception exception) {
                sendAssistantError(exception);
            }
        }
    }

    @ReactMethod
    public void hideTrigger() {
        if (getCurrentActivity() != null) {
            try {
                SlangFitnessAssistant.getUI().hideTrigger(getCurrentActivity());
            } catch (Exception exception) {
                sendAssistantError(exception);
            }
        }
    }

    @ReactMethod
    public void showTrigger() {
        if (getCurrentActivity() != null) {
            try {
                SlangFitnessAssistant.getUI().showTrigger(getCurrentActivity());
            } catch (Exception exception) {
                sendAssistantError(exception);
            }
        }
    }

    @ReactMethod
    public void setUserProperties(String userId, ReadableMap rnUserProperties) {
        Log.d(TAG, String.format(
                Locale.ENGLISH, "UserId : %s, UserProperties : %s", userId,
                rnUserProperties));
        try {
            Map<String, String> userPropertiesInfoMap = RNSlangUtils.convertReadableMapToMap(rnUserProperties);
            SlangFitnessAssistant.setUserProperties(userPropertiesInfoMap);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void trackCustomEvent(String eventName, ReadableMap eventMetadata){
        if (getCurrentActivity() != null) {
            try {
                Map<String, String> eventMetaDataMap = RNSlangUtils.convertReadableMapToMap(eventMetadata);
                SlangFitnessAssistant.trackCustomEvent(eventName, eventMetaDataMap);
            } catch (Exception exception) {
                sendAssistantError(exception);
            }
        }
    }

    @ReactMethod
    public void notifyFoodLoggingActionCompleted(final String foodLoggingAppState, String foodLoggingAppStateCondition) {
        Log.d(TAG, "notifyFoodLoggingActionCompleted: " + foodLoggingAppState);
        try {
            if (mFoodLoggingUserJourney != null) {
                try {
                    if (foodLoggingAppState.equals("UNSUPPORTED")) {
                        mFoodLoggingUserJourney.notifyAppState(new UnsupportedAppState());
                        return;
                    }

                    if (foodLoggingAppState.equals("WAITING")) {
                        return;
                    }

                    FoodLoggingCompleteAppState.Condition fitnessCondition;

                    switch (foodLoggingAppStateCondition) {
                        case "FAILURE":
                            fitnessCondition = FoodLoggingCompleteAppState.FAILURE;
                            break;
                        case "MEAL_TYPE_NOT_SPECIFIED":
                            fitnessCondition = FoodLoggingCompleteAppState.MEAL_TYPE_REQUIRED;
                            break;
                        case "DATE_NOT_SPECIFIED":
                            fitnessCondition = FoodLoggingCompleteAppState.DATE_REQUIRED;
                            break;
                        case "INVALID_DATE_SPECIFIED":
                            fitnessCondition = FoodLoggingCompleteAppState.INVALID_DATE_SPECIFIED;
                            break;
                        case "TIME_NOT_SPECIFIED":
                            fitnessCondition = FoodLoggingCompleteAppState.TIME_REQUIRED;
                            break;
                        case "FOODS_NOT_SPECIFIED":
                            fitnessCondition = FoodLoggingCompleteAppState.FOODS_REQUIRED;
                            break;
                        case "FOODS_LIST_INCOMPLETE":
                            fitnessCondition = FoodLoggingCompleteAppState.FOODS_LIST_INCOMPLETE;
                            break;
                        case "FOODS_FOOD_NAME_DISAMBIGUATE":
                            fitnessCondition = FoodLoggingCompleteAppState.FOODS_FOOD_NAME_DISAMBIGUATE;
                            break;
                        case "CONFIRMATION_BAILOUT":
                            fitnessCondition = FoodLoggingCompleteAppState.CONFIRMATION_BAILOUT;
                            break;
                        case "CONFIRMATION_REQUIRED":
                            fitnessCondition = FoodLoggingCompleteAppState.CONFIRMATION_REQUIRED;
                            break;
                        case "VERIFICATION_REQUIRED":
                            fitnessCondition = FoodLoggingCompleteAppState.VERIFICATION_REQUIRED;
                            break;
                        default:
                            fitnessCondition = FoodLoggingCompleteAppState.SUCCESS;
                            break;
                    }
                    mFoodLoggingUserJourney.notifyAppState(new FoodLoggingCompleteAppState(fitnessCondition));

                } catch (Exception e) {
                    Log.e(TAG, "" + e.getLocalizedMessage());
                }
            }
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void notifySugarLoggingActionCompleted(final String sugarLoggingAppState, String sugarLoggingAppStateCondition) {
        Log.d(TAG, "notifySugarLoggingActionCompleted: " + sugarLoggingAppState);
        try {
            if (mSugarLoggingUserJourney != null) {
                try {
                    if (sugarLoggingAppState.equals("UNSUPPORTED")) {
                        mSugarLoggingUserJourney.notifyAppState(new UnsupportedAppState());
                        return;
                    }

                    if (sugarLoggingAppState.equals("WAITING")) {
                        return;
                    }

                    SugarLoggingCompleteAppState.Condition fitnessCondition;

                    switch (sugarLoggingAppStateCondition) {
                        case "FAILURE":
                            fitnessCondition = SugarLoggingCompleteAppState.FAILURE;
                            break;
                        case "SUGAR_LEVEL_REQUIRED":
                            fitnessCondition = SugarLoggingCompleteAppState.SUGAR_LEVEL_REQUIRED;
                            break;
                        case "DATE_REQUIRED":
                            fitnessCondition = SugarLoggingCompleteAppState.DATE_REQUIRED;
                            break;
                        case "INVALID_DATE_SPECIFIED":
                            fitnessCondition = SugarLoggingCompleteAppState.INVALID_DATE_SPECIFIED;
                            break;
                        case "TIME_REQUIRED":
                            fitnessCondition = SugarLoggingCompleteAppState.TIME_REQUIRED;
                            break;
                        case "CONFIRMATION_BAILOUT":
                            fitnessCondition = SugarLoggingCompleteAppState.CONFIRMATION_BAILOUT;
                            break;
                        case "CONFIRMATION_REQUIRED":
                            fitnessCondition = SugarLoggingCompleteAppState.CONFIRMATION_REQUIRED;
                            break;
                        case "VERIFICATION_REQUIRED":
                            fitnessCondition = SugarLoggingCompleteAppState.VERIFICATION_REQUIRED;
                            break;
                        default:
                            fitnessCondition = SugarLoggingCompleteAppState.SUCCESS;
                            break;
                    }
                    mSugarLoggingUserJourney.notifyAppState(new SugarLoggingCompleteAppState(fitnessCondition));

                } catch (Exception e) {
                    Log.e(TAG, "" + e.getLocalizedMessage());
                }
            }
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void notifyWeightLoggingActionCompleted(final String weightLoggingAppState, String weightLoggingAppStateCondition) {
        Log.d(TAG, "notifyWeightLoggingActionCompleted: " + weightLoggingAppState);
        try {
            if (mWeightLoggingUserJourney != null) {
                try {
                    if (weightLoggingAppState.equals("UNSUPPORTED")) {
                        mWeightLoggingUserJourney.notifyAppState(new UnsupportedAppState());
                        return;
                    }

                    if (weightLoggingAppState.equals("WAITING")) {
                        return;
                    }

                    WeightLoggingCompleteAppState.Condition fitnessCondition;

                    switch (weightLoggingAppStateCondition) {
                        case "FAILURE":
                            fitnessCondition = WeightLoggingCompleteAppState.FAILURE;
                            break;
                        case "WEIGHT_REQUIRED":
                            fitnessCondition = WeightLoggingCompleteAppState.WEIGHT_REQUIRED;
                            break;
                        case "DATE_REQUIRED":
                            fitnessCondition = WeightLoggingCompleteAppState.DATE_REQUIRED;
                            break;
                        case "INVALID_DATE_SPECIFIED":
                            fitnessCondition = WeightLoggingCompleteAppState.INVALID_DATE_SPECIFIED;
                            break;
                        case "TIME_REQUIRED":
                            fitnessCondition = WeightLoggingCompleteAppState.TIME_REQUIRED;
                            break;
                        case "CONFIRMATION_BAILOUT":
                            fitnessCondition = WeightLoggingCompleteAppState.CONFIRMATION_BAILOUT;
                            break;
                        case "CONFIRMATION_REQUIRED":
                            fitnessCondition = WeightLoggingCompleteAppState.CONFIRMATION_REQUIRED;
                            break;
                        case "VERIFICATION_REQUIRED":
                            fitnessCondition = WeightLoggingCompleteAppState.VERIFICATION_REQUIRED;
                            break;
                        default:
                            fitnessCondition = WeightLoggingCompleteAppState.SUCCESS;
                            break;
                    }
                    mWeightLoggingUserJourney.notifyAppState(new WeightLoggingCompleteAppState(fitnessCondition));

                } catch (Exception e) {
                    Log.e(TAG, "" + e.getLocalizedMessage());
                }
            }
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    /******************************** Methods for CONFIG ******************************************/

    @ReactMethod
    public void setBackgroundColorGradient(ReadableArray colors) {
        try {
            String[] backgroundColorGradients = new String[colors.size()];
            for (int i = 0; i < colors.size(); i++) {
                String color = "";
                if (colors.getType(i) == ReadableType.String) {
                    color = colors.getString(i);
                }
                backgroundColorGradients[i] = color;
            }
            SlangFitnessAssistant.getUI().setBackgroundColorGradient(backgroundColorGradients);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setWaveColorGradient(ReadableArray colors) {
        try {
            String[] foregroundColorGradients = new String[colors.size()];
            for (int i = 0; i < colors.size(); i++) {
                String color = "";
                if (colors.getType(i) == ReadableType.String) {
                    color = colors.getString(i);
                }
                foregroundColorGradients[i] = color;
            }
            SlangFitnessAssistant.getUI().setForegroundColorGradient(foregroundColorGradients);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }


    @ReactMethod
    public void setTextColor(String color) {
        try {
            SlangFitnessAssistant.getUI().setTextColor(color);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setSecondaryTextColor(String color) {
        try {
            SlangFitnessAssistant.getUI().setSecondaryTextColor(color);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setTextHighlightColor(String color) {
        try {
            SlangFitnessAssistant.getUI().setTextHighlightColor(color);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setSettingsButtonBackgroundColor(String color) {
        try {
            SlangFitnessAssistant.getUI().setSettingsButtonBackgroundColor(color);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setControlButtonBackgroundColor(String color) {
        try {
            SlangFitnessAssistant.getUI().setControlButtonBackgroundColor(color);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setTheme(String theme) {
        try {
            BaseUI.BaseTheme baseTheme = AssistantUI.Theme.LIGHT;
            if ("DARK".equals(theme)) {
                baseTheme = AssistantUI.Theme.DARK;
            }
            SlangFitnessAssistant.getUI().setTheme(baseTheme);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setMode(String mode) {
        try {
            BaseUI.BaseMode baseMode = AssistantUI.Mode.LIGHT;
            if ("DARK".equals(mode)) {
                baseMode = AssistantUI.Mode.DARK;
            }
            SlangFitnessAssistant.getUI().setMode(baseMode);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void disableLocaleSelection(boolean disable) {
        try {
            SlangFitnessAssistant.getUI().disableLocaleSelection(disable);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setWaveVelocity(double velocity) {
        try {
            SlangFitnessAssistant.getUI().setWaveVelocity((float) velocity);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setTriggerSize(int width, int height) {
        try {
            SlangFitnessAssistant.getUI().setTriggerSize(width, height);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    void setAppDefaultUserJourney(String userJourney) {
        try {
            SlangFitnessAssistant.setAppDefaultUserJourney(FitnessUserJourney.fromString(userJourney));
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    public void setTriggerImageResource(String uriString) {
        Log.d(TAG, "setTriggerImageResource: " + uriString);
        Uri uri = Uri.parse(uriString);
        try {
            // Debug
            if (uriString.startsWith("http")) {
                URL url = new URL(uriString);
                Bitmap image = BitmapFactory.decodeStream(url.openConnection().getInputStream());
                Drawable drawable = new BitmapDrawable(mReactContext.getResources(), image);
                SlangFitnessAssistant.getUI().setTriggerImageDrawable(drawable);
            } else {
                // Release
                int resourceId = mReactContext.getResources().getIdentifier(uriString, "drawable", mReactContext.getPackageName());
                Bitmap image = BitmapFactory.decodeResource(mReactContext.getResources(), resourceId);
                SlangFitnessAssistant.getUI().setTriggerImageResource(resourceId);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /***************************** Methods for CONTEXT ********************************************/

    @ReactMethod
    void setDisambiguationIndexInContext(int disambiguationIndex) {
        try {
            FoodLoggingUserJourney.getContext().setDisambiguationIndex(disambiguationIndex);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    @ReactMethod
    void setFoodInfoAtIndexInContext(int index, ReadableMap food) {
        try {
            Food foodInfo = RNSlangUtils.mapToFood(food);
            FoodLoggingUserJourney.getContext().setFoodInfoAtIndex(index, foodInfo);
        } catch (Exception exception) {
            sendAssistantError(exception);
        }
    }

    private void sendAssistantError(Exception exception) {
        WritableMap params = Arguments.createMap();
        params.putString("type", exception.getMessage());
        sendEvent("onAssistantError", params);
    }

    private void sendEvent(String name, WritableMap params) {
        mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(name, params);
    }

}