package de.apps4ics.mountainnavigation.handlers;

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

import com.survivingwithandroid.weather.lib.WeatherClient;
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.CurrentWeather;
import com.survivingwithandroid.weather.lib.model.DayForecast;
import com.survivingwithandroid.weather.lib.model.HistoricalHourWeather;
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.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.OnSingleWeatherRetrieved;
import de.apps4ics.mountainnavigation.OnWeatherRetrieved;
import de.apps4ics.mountainnavigation.R;
import de.apps4ics.mountainnavigation.SettingsActivity;
import de.apps4ics.mountainnavigation.WeatherDatabase;
import de.apps4ics.mountainnavigation.adapters.WarningListAdapter;

/**
 * This file is part of MountainNavigation.
 *
 * MountainNavigation is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MountainNavigation is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MountainNavigation.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @copyright Copyright (c) 2016 Vinzenz Rosenkanz <vinzenz.rosenkranz@gmail.com>
 *
 * @author Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
 */
public class WeatherHandler {
    private static final String API_KEY = "fd4034defae557fd5f2fdaaf73c3402c"; //OpenWeatherMap API Key
    private Context context;
    private WeatherDatabase weatherDb;
    private WeatherClient weatherClient;
    private WeatherConfig weatherConfig;
    private HistoricalWeather histWeather;
    private DayForecast[] forecasts;
    private Weather[] weathers;
    public final static int MIN_HOT_TEMP_DEFAULT = 30;
    public final static int MAX_COLD_TEMP_DEFAULT = 10;
    public final static int MAX_DAYS_FORECAST_DEFAULT = 3;
    public final static int MIN_DAYS_FORECAST = 1;
    private static int MIN_HOT_TEMP;
    private static int MAX_COLD_TEMP;
    private static int MAX_DAYS_FORECAST;
    private static float MIN_WINDY_SPEED;

    private static Activity activity;
    private static FragmentManager fragmentManager;

    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?
        }

        Resources res = MainActivity.getRes();
        MAX_DAYS_FORECAST = SettingsActivity.getDaysForecastFromPrefs(MAX_DAYS_FORECAST_DEFAULT);
        WeatherClient.ClientBuilder weatherBuilder = new WeatherClient.ClientBuilder();
        weatherConfig = new WeatherConfig();
        weatherConfig.unitSystem = WeatherConfig.UNIT_SYSTEM.M;
        weatherConfig.ApiKey = API_KEY;
        weatherConfig.lang = res.getString(R.string.lang_identifier);
        weatherConfig.numDays = MAX_DAYS_FORECAST;

        MIN_HOT_TEMP = SettingsActivity.getMinHotFromPrefs(MIN_HOT_TEMP_DEFAULT);
        MAX_COLD_TEMP = SettingsActivity.getMaxColdFromPrefs(MAX_COLD_TEMP_DEFAULT);

        MIN_WINDY_SPEED = 8; //http://www.wettergefahren-fruehwarnung.de/Artikel/beaufort.html
        if(weatherConfig.unitSystem.equals(WeatherConfig.UNIT_SYSTEM.I)){
            MIN_HOT_TEMP = (int) celsiusToFahrenheit(MIN_HOT_TEMP);
            MAX_COLD_TEMP = (int) celsiusToFahrenheit(MAX_COLD_TEMP);
        }

        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(OnSingleWeatherRetrieved callback) {
        getCurrentWeather(MainActivity.getLocation(), callback);
    }

    public void getCurrentWeather(Location l, OnSingleWeatherRetrieved callback) {
        getCurrentWeather(l.getLatitude(), l.getLongitude(), callback);
    }

    public void getCurrentWeather(double lat, double lon, OnSingleWeatherRetrieved callback) {
        if(MainActivity.hasInternet()) getCurrentWeatherOnline(lat, lon, callback);
        else getCurrentWeatherCached(lat, lon, 0, callback);
    }

    //TODO
    private void getCurrentWeatherOnline(double lat, double lon, final OnSingleWeatherRetrieved callback) {
        weatherClient.getCurrentCondition(new WeatherRequest(lon, lat), new WeatherClient.WeatherEventListener() {
            @Override
            public void onWeatherRetrieved(CurrentWeather weather) {
                MyWeather myWeather = new MyWeather(weather.weather, System.currentTimeMillis());
                callback.postRetrieve(myWeather);
            }
            @Override
            public void onWeatherError(WeatherLibException wle) {
            }

            @Override
            public void onConnectionError(Throwable t) {
            }
        });
    }

    //TODO
    private void getCurrentWeatherCached(double lat, double lon, int offset, OnSingleWeatherRetrieved callback) {
        getFromDatabase(lat, lon, offset, callback);
    }

    public void getForecast(int days, OnWeatherRetrieved callback) {
        getForecast(MainActivity.getLocation(), days, callback);
    }

    public void getForecast(Location l, OnWeatherRetrieved callback) {
        if(l == null) return;
        getForecast(l.getLatitude(), l.getLongitude(), callback);
    }

    public void getForecast(Location l, int days, OnWeatherRetrieved callback) {
        if(l == null) return;
        getForecast(l.getLatitude(), l.getLongitude(), days, callback);
    }

    public void getForecast(double lat, double lon, OnWeatherRetrieved callback) {
        getForecast(lat, lon, MAX_DAYS_FORECAST, callback);
    }

    public void getForecast(double lat, double lon, int days, OnWeatherRetrieved callback) {
        if(MainActivity.hasInternet()) getForecastOnline(lat, lon, days, callback);
        else getForecastCached(lat, lon, days, 0, callback);
    }

    public void getForecastOnline(double lat, double lon, final int days, final OnWeatherRetrieved callback) {
        weatherConfig.numDays = days;
        weatherClient.updateWeatherConfig(weatherConfig);
        weatherClient.getForecastWeather(new WeatherRequest(lon, lat), new WeatherClient.ForecastWeatherEventListener() {
            @Override
            public void onWeatherRetrieved(WeatherForecast forecast) {
                List<MyWeather> weatherList = new ArrayList<>();
                List<DayForecast> foundForecasts = forecast.getForecast();
                int dayCount = Math.min(foundForecasts.size(), days);
                if(dayCount == 0) return;
                forecasts = new DayForecast[dayCount];
                weathers = new Weather[dayCount];
                for(int i=0; i<dayCount; i++) {
                    DayForecast curr = foundForecasts.get(i);
                    forecasts[i] = curr;
                    weathers[i] = curr.weather;
                    MyWeather newWeather = new MyWeather(curr);
                    insertIntoDatabase(newWeather);
                    weatherList.add(newWeather);
                }
                Log.d(MainActivity.TAG, "City [" + weathers[0].location.getCity() + "] Current temp: " + weathers[0].temperature.getTemp());

                String title = String.format("Weather for %s in %s", weathers[0].location.getCity(), weathers[0].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",
                        forecasts[0].forecastTemp.day,
                        forecast.getUnit().tempUnit,
                        forecasts[0].forecastTemp.min,
                        forecast.getUnit().tempUnit,
                        forecasts[0].forecastTemp.max,
                        forecast.getUnit().tempUnit,
                        MainActivity.df_hm.format(new Date(weathers[0].location.getSunrise() * 1000)),
                        MainActivity.df_hm.format(new Date(weathers[0].location.getSunset() * 1000)),
                        weathers[0].currentCondition.getWeatherCode().getCode(),
                        weathers[0].currentCondition.getWeatherId());
                Log.d(MainActivity.TAG, title + ": " + msg);
                callback.postRetrieve(weatherList);
                //displayHints(weatherList.get(0));
                //displaySevereWeather(weatherList.get(0));
            }

            @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();
            }
        });
    }

    //TODO
    public void getForecastCached(double lat, double lon, int days, int offset, OnWeatherRetrieved callback) {
        return;
    }

    public void getHistoricalWeather(long start, long end, OnWeatherRetrieved callback) {
        getHistoricalWeather(MainActivity.getLocation(), start, end, callback);
    }

    public void getHistoricalWeather(Location l, long start, long end, OnWeatherRetrieved callback) {
        if(l == null) return;
        getHistoricalWeather(l.getLatitude(), l.getLongitude(), start, end, callback);
    }

    public void getHistoricalWeather(double lat, double lon, long start, long end, OnWeatherRetrieved callback){
        if(MainActivity.hasInternet()) getHistoricalWeatherOnline(lat, lon, start, end, callback);
        else getHistoricalWeatherCached(lat, lon, start, end, callback);
    }

    public void getHistoricalWeatherOnline(double lat, double lon, long start, long end, OnWeatherRetrieved callback) {
        ArrayList<MyWeather> weathers = new ArrayList<>();
        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();
            }
        });
        for(HistoricalHourWeather hhw : histWeather.getHoistoricalData()) {
            MyWeather curr = new MyWeather(hhw.weather, hhw.timestamp);
            insertIntoDatabase(curr);
            weathers.add(curr);
        }
        callback.postRetrieve(weathers);
    }

    private void getHistoricalWeatherCached(double lat, double lon, long start, long end, OnWeatherRetrieved callback) {
        return;
    }

    private void insertIntoDatabase(Weather weather) {
        insertIntoDatabase(weather, System.currentTimeMillis());
    }

    private void insertIntoDatabase(Weather weather, long timestamp) {
        insertIntoDatabase(new MyWeather(weather, timestamp));
    }

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

    private void getFromDatabase(double lat, double lon, int offset, OnSingleWeatherRetrieved callback) {
        MyWeather weather = weatherDb.getWeather(lat, lon, offset);
        callback.postRetrieve(weather);
    }

    public void displayHints(MyWeather weather) {
        //TODO add more hints
        if (MainActivity.showWeatherHints()) {
            Resources res = MainActivity.getRes();
            List<String> titles = new ArrayList<>();
            List<String> descs = new ArrayList<>();
            List<String> infos = new ArrayList<>();
            if (weather.isSunny()) {
                titles.add(res.getString(R.string.hint_dialog_titles_sunny));
                descs.add(res.getString(R.string.hint_dialog_msgs_sunny));
                infos.add("");
            }
            if (weather.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 (weather.isCold()) {
                titles.add(res.getString(R.string.hint_dialog_titles_cold));
                descs.add(res.getString(R.string.hint_dialog_msgs_cold));
                infos.add("");
            }
            if (weather.isWindy()) {
                titles.add(res.getString(R.string.hint_dialog_titles_windy));
                descs.add(res.getString(R.string.hint_dialog_msgs_windy));
                infos.add("");
            }
            if (weather.isAfterSunset()) {
                titles.add(res.getString(R.string.hint_dialog_titles_sunset));
                descs.add(res.getString(R.string.hint_dialog_msgs_sunset));
                infos.add("");
            }
            if (weather.isFoggy()) {
                titles.add(res.getString(R.string.hint_dialog_titles_foggy));
                descs.add(res.getString(R.string.hint_dialog_msgs_foggy));
                infos.add(res.getString(R.string.hint_dialog_info_foggy));
            }
            if (weather.hasSleet()) {
                titles.add(res.getString(R.string.hint_dialog_titles_sleet));
                descs.add(res.getString(R.string.hint_dialog_msgs_sleet));
                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");
            }
        }
    }

    public void displaySevereWeather(MyWeather weather) {
        if (weather.isSevereWeather(weather.getWeatherCode())) {
            Resources res = context.getResources();
            String warnMsg = String.format(res.getString(R.string.weather_warning_dialog_msg), weather.getCity(), weather.getWeatherCode());
            InformDialog informAlertDialog = new InformDialog(res.getString(R.string.weather_warning_dialog_title), warnMsg);
            informAlertDialog.show(fragmentManager, "Inform Dialog - Weather Warning");
        }
    }

    public static float getMinHotTemp() {
        return MIN_HOT_TEMP;
    }

    public static float getMinWindySpeed() {
        return MIN_WINDY_SPEED;
    }

    public static int getMaxDaysForecast() {
        return MAX_DAYS_FORECAST;
    }

    public static float getMaxColdTemp() {
        return MAX_COLD_TEMP;
    }

    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;
    }
}
