package de.apps4ics.mountainnavigation.handlers;

import android.app.Activity;
import android.app.FragmentManager;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;

import com.survivingwithandroid.weather.lib.WeatherClient;
import com.survivingwithandroid.weather.lib.WeatherCode;
import com.survivingwithandroid.weather.lib.WeatherConfig;
import com.survivingwithandroid.weather.lib.exception.WeatherLibException;
import com.survivingwithandroid.weather.lib.exception.WeatherProviderInstantiationException;
import com.survivingwithandroid.weather.lib.model.DayForecast;
import com.survivingwithandroid.weather.lib.model.HistoricalWeather;
import com.survivingwithandroid.weather.lib.model.Weather;
import com.survivingwithandroid.weather.lib.model.WeatherForecast;
import com.survivingwithandroid.weather.lib.provider.openweathermap.OpenweathermapProviderType;
import com.survivingwithandroid.weather.lib.request.WeatherRequest;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import de.apps4ics.mountainnavigation.InformDialog;
import de.apps4ics.mountainnavigation.InformListDialog;
import de.apps4ics.mountainnavigation.MainActivity;
import de.apps4ics.mountainnavigation.MyWeather;
import de.apps4ics.mountainnavigation.R;
import de.apps4ics.mountainnavigation.WeatherDatabase;
import de.apps4ics.mountainnavigation.adapters.WarningListAdapter;

/**
 * Created by Vinz on 06.03.2016.
 */
public class WeatherHandler {
    private static final String API_KEY = "fd4034defae557fd5f2fdaaf73c3402c"; //OpenWeatherMap API Key
    private Context context;
    private WeatherDatabase weatherDb;
    private WeatherClient weatherClient;
    private Weather todaysWeather;
    private Weather tomorrowsWeather;
    private HistoricalWeather histWeather;
    private DayForecast todaysForecast;
    private DayForecast tomorrowsForecast;
    private static float minHotTemp;
    private static float maxColdTemp;
    private static float minWindySpeed;

    private static Activity activity;
    private static FragmentManager fragmentManager;

    //TODO split into severe weather and foggy, windy, sunny, ...
    private static final Integer[] severeWeatherCodes = new Integer[]{
        WeatherCode.TORNADO.getCode(),
                WeatherCode.TROPICAL_STORM.getCode(),
                WeatherCode.HURRICANE.getCode(),
                WeatherCode.SEVERE_THUNDERSTORMS.getCode(),
                WeatherCode.THUNDERSTORMS.getCode(),
                WeatherCode.MIXED_RAIN_SNOW.getCode(),
                WeatherCode.MIXED_RAIN_SLEET.getCode(),
                WeatherCode.MIXED_SNOW_SLEET.getCode(),
                WeatherCode.FREEZING_DRIZZLE.getCode(),
                WeatherCode.FREEZING_RAIN.getCode(),
                WeatherCode.HEAVY_SHOWERS.getCode(),
                WeatherCode.SNOW_FLURRIES.getCode(),
                WeatherCode.LIGHT_SNOW_SHOWERS.getCode(),
                WeatherCode.BLOWING_SNOW.getCode(),
                WeatherCode.SNOW.getCode(),
                WeatherCode.HAIL.getCode(),
                WeatherCode.SLEET.getCode(),
                WeatherCode.FOGGY.getCode(),
                WeatherCode.HAZE.getCode(),
                WeatherCode.WINDY.getCode(),
                WeatherCode.COLD.getCode(),
                WeatherCode.SUNNY.getCode(),
                WeatherCode.MIXED_RAIN_AND_HAIL.getCode(),
                WeatherCode.ISOLATED_THUNDERSTORMS.getCode(),
                WeatherCode.SCATTERED_THUNDERSTORMS.getCode(),
                WeatherCode.HEAVY_SNOW.getCode(),
                WeatherCode.SCATTERED_SNOW_SHOWERS.getCode(),
                WeatherCode.THUNDERSHOWERS.getCode(),
                WeatherCode.SNOW_SHOWERS.getCode(),
                WeatherCode.ISOLATED_THUDERSHOWERS.getCode(),
                WeatherCode.TORNADO.getCode()
    };

    public WeatherHandler(Context context) {
        weatherDb = new WeatherDatabase(context);
        this.context = context;

        try {
            activity = (Activity) context;
            fragmentManager = activity.getFragmentManager();
        } catch(ClassCastException e) {
            Log.e(MainActivity.TAG, "Could not get fragment manager!");
            //TODO close app?
        }

        WeatherClient.ClientBuilder weatherBuilder = new WeatherClient.ClientBuilder();
        WeatherConfig weatherConfig = new WeatherConfig();
        weatherConfig.unitSystem = WeatherConfig.UNIT_SYSTEM.M;
        weatherConfig.ApiKey = API_KEY;
        weatherConfig.lang = MainActivity.getRes().getString(R.string.lang_identifier);
        weatherConfig.numDays = 2;

        minWindySpeed = 8; //http://www.wettergefahren-fruehwarnung.de/Artikel/beaufort.html
        if(weatherConfig.unitSystem.equals(WeatherConfig.UNIT_SYSTEM.I)){
            minHotTemp = celsiusToFahrenheit(30);
            maxColdTemp = celsiusToFahrenheit(10);
        } else {
            minHotTemp = 30;
            maxColdTemp = 10;
        }

        weatherClient = null;
        try {
            weatherClient = weatherBuilder.attach(context)
                    .provider(new OpenweathermapProviderType())
                    .httpClient(com.survivingwithandroid.weather.lib.client.okhttp.WeatherDefaultClient.class)
                    .config(weatherConfig)
                    .build();
        } catch (WeatherProviderInstantiationException e) {
            e.printStackTrace();
        }
    }

    public void getCurrentWeather() {
        getCurrentWeather(0);
    }

    /**
     *
     * @param offset number of days (in the past) to search for result in the cache, only for getCurrentWeatherCached
     */
    public MyWeather getCurrentWeather(int offset) {
        if(MainActivity.hasInternet()) return getCurrentWeatherOnline();
        else return getCurrentWeatherCached(offset);
    }

    private MyWeather getCurrentWeatherOnline() {
        //return new MyWeather();
        return null;
    }

    private MyWeather getCurrentWeatherCached(int offset) {
        //return new MyWeather();
        return null;
    }

    public void getForecast(double lat, double lon, int days) {
        getForecast(lat, lon, days, 0);
    }

    public void getForecast(double lat, double lon, int days, int offset) {
//TODO Move this to the not-yet-implemented upload method
        weatherClient.getForecastWeather(new WeatherRequest(lon, lat), new WeatherClient.ForecastWeatherEventListener() {
            @Override
            public void onWeatherRetrieved(WeatherForecast forecast) {
                todaysForecast = forecast.getForecast(0);
                tomorrowsForecast = forecast.getForecast(1);
                todaysWeather = todaysForecast.weather;
                tomorrowsWeather = tomorrowsForecast.weather;
                Log.d(MainActivity.TAG, "City [" + todaysWeather.location.getCity() + "] Current temp: " + todaysWeather.temperature.getTemp());

                String title = String.format("Weather for %s in %s", todaysWeather.location.getCity(), todaysWeather.location.getCountry());
                String msg = String.format("%1$.1f%2$s (%3$.1f%4$s/%5$.1f%6$s)\nSunrise: %7$s\nSunset: %8$s\nWeatherCode: %9$d\nWeatherId: %10$d",
                        todaysForecast.forecastTemp.day,
                        forecast.getUnit().tempUnit,
                        todaysForecast.forecastTemp.min,
                        forecast.getUnit().tempUnit,
                        todaysForecast.forecastTemp.max,
                        forecast.getUnit().tempUnit,
                        MainActivity.df_hm.format(new Date(todaysWeather.location.getSunrise() * 1000)),
                        MainActivity.df_hm.format(new Date(todaysWeather.location.getSunset() * 1000)),
                        todaysWeather.currentCondition.getWeatherCode().getCode(),
                        todaysWeather.currentCondition.getWeatherId());
                Log.d(MainActivity.TAG, title + ": " + msg);
                displayHints();
                displaySevereWeather();
            }

            @Override
            public void onWeatherError(WeatherLibException wle) {
                Log.e(MainActivity.TAG, "Weather error - parsing data");
                wle.printStackTrace();
            }

            @Override
            public void onConnectionError(Throwable t) {
                Log.e(MainActivity.TAG, "Connection Error");
                t.printStackTrace();
            }
        });
    }

    public MyWeather getForecastOnline(long start, long end, int step) {
        return new MyWeather(new WeatherForecast());
    }

    public MyWeather getForecastCached(long start, long end, int step, int offset) {
        return new MyWeather(new WeatherForecast());
    }

    public MyWeather getHistoricalWeather(float lat, float lon, long start, long end){
        Date startDate = new Date(start / 1000); //OWM uses seconds instead of milliseconds
        Date endDate = new Date(end / 1000);
        histWeather = null;
        weatherClient.getHistoricalWeather(new WeatherRequest(lon, lat), startDate, endDate, new WeatherClient.HistoricalWeatherEventListener() {
            @Override
            public void onWeatherRetrieved(HistoricalWeather historicalWeather) {
                //TODO check if it was sunny/rainy and set fountain options based on this
                histWeather = historicalWeather;
            }

            @Override
            public void onWeatherError(WeatherLibException wle) {
                Log.e(MainActivity.TAG, "Weather error - parsing data");
                wle.printStackTrace();
            }

            @Override
            public void onConnectionError(Throwable t) {
                Log.e(MainActivity.TAG, "Connection Error");
                t.printStackTrace();
            }
        });
        return new MyWeather(histWeather);
    }

    private void convertWeatherLibWeather(Weather weather) {

    }

    private void insertIntoDatabase(MyWeather weather) {
        weatherDb.insertWeather(weather);
    }

    private void displayHints() {
        Resources res = MainActivity.getRes();
        //TODO add more hints
        if (MainActivity.showWeatherHints()) {
            List<String> titles = new ArrayList<>();
            List<String> descs = new ArrayList<>();
            List<String> infos = new ArrayList<>();
            if (isSunny()) {
                titles.add(res.getString(R.string.hint_dialog_titles_sunny));
                descs.add(res.getString(R.string.hint_dialog_msgs_sunny));
                infos.add("");
            }
            if (isHot()) {
                titles.add(res.getString(R.string.hint_dialog_titles_hot));
                descs.add(res.getString(R.string.hint_dialog_msgs_hot));
                infos.add(res.getString(R.string.hint_dialog_info_hot));
            }
            if (isCold()) {
                titles.add(res.getString(R.string.hint_dialog_titles_cold));
                descs.add(res.getString(R.string.hint_dialog_msgs_cold));
                infos.add("");
            }
            if (isWindy()) {
                titles.add(res.getString(R.string.hint_dialog_titles_windy));
                descs.add(res.getString(R.string.hint_dialog_msgs_windy));
                infos.add("");
            }
            if (isAfterSunset()) {
                titles.add(res.getString(R.string.hint_dialog_titles_sunset));
                descs.add(res.getString(R.string.hint_dialog_msgs_sunset));
                infos.add("");
            }
            if (titles.size() > 0 && descs.size() > 0) {
                String[] titleArr = new String[titles.size()];
                String[] descArr = new String[descs.size()];
                String[] infosArr = new String[infos.size()];
                titleArr = titles.toArray(titleArr);
                descArr = descs.toArray(descArr);
                infosArr = infos.toArray(infosArr);
                WarningListAdapter warningAdapter = new WarningListAdapter(activity, titleArr, descArr, infosArr);
                InformListDialog weatherHintDialog = new InformListDialog(MainActivity.getRes().getString(R.string.hint_dialog_title), warningAdapter);
                weatherHintDialog.show(fragmentManager, "Warning Hint Dialog");
            }
        }
    }

    private void displaySevereWeather() {
        if (isSevereWeather(todaysWeather.currentCondition.getWeatherCode().getCode())) {
            Log.d(MainActivity.TAG, "It is severe weather! :(");
            Resources res = context.getResources();
            String warnMsg = String.format(res.getString(R.string.weather_warning_dialog_msg), todaysWeather.location.getCity(), todaysWeather.currentCondition.getWeatherCode().getLabel(context));
            InformDialog informAlertDialog = new InformDialog(res.getString(R.string.weather_warning_dialog_title), warnMsg);
            informAlertDialog.show(fragmentManager, "Inform Dialog - Weather Warning");
        }
    }

    private boolean isSevereWeather(int weatherCode){
        return Arrays.asList(severeWeatherCodes).contains(weatherCode);
    }

    private boolean isAfterSunset(){
        return isAfterSunset(System.currentTimeMillis());
    }

    private boolean isAfterSunset(long time){
        return time > (todaysWeather.location.getSunset() * 1000) && time < (tomorrowsWeather.location.getSunrise() * 1000);
    }

    private boolean isHot(){
        return todaysWeather.currentCondition.getWeatherCode().getCode() == WeatherCode.SUNNY.getCode() ||
                (todaysWeather.temperature.getTemp() >= minHotTemp || todaysWeather.temperature.getMaxTemp() >= minHotTemp);
    }

    private boolean isSunny(){
        return todaysWeather.currentCondition.getWeatherCode().getCode() == WeatherCode.SUNNY.getCode();
    }

    private boolean isCold(){
        return todaysWeather.currentCondition.getWeatherCode().getCode() == WeatherCode.COLD.getCode() ||
                (todaysWeather.temperature.getTemp() <= maxColdTemp || todaysWeather.temperature.getMinTemp() <= maxColdTemp);
    }

    private boolean isWindy(){
        return todaysWeather.currentCondition.getWeatherCode().getCode() == WeatherCode.WINDY.getCode() ||
                (todaysWeather.wind.getSpeed() >= minWindySpeed);
    }

    private boolean isDark(long length){
        return isDark(System.currentTimeMillis(), length);
    }

    private boolean isDark(long time, long length){
        return isAfterSunset(time) || isAfterSunset(time + length);
    }

    public static float celsiusToFahrenheit(float c) {
        return c * 9.0f/5.0f + 32;
    }

    public static float fahrenheitToCelsius(float f) {
        return (f - 32.0f) * 5.0f/9.0f;
    }
}
