public class CreateKeyActivity extends AppCompatActivity {

    public static boolean active = false;

    RelativeLayout layout;
    EditText personalKeyField;
    Button keyButton;
    Button abortButton;
    Boolean fragmentShown = false;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_create_key);
        layout = findViewById(R.id.add_key_layout);
        personalKeyField = findViewById(R.id.owm_key_field);
        keyButton = findViewById(R.id.set_owm_key_button);
        abortButton = findViewById(R.id.abort_button);
        TextView introText = findViewById(R.id.explanation_text_owm_key);
        boolean limitReached = getIntent().getBooleanExtra("429", false);
        if (!limitReached) {
            introText.setText(R.string.explanation_owm_key_on_start);
        }

        keyButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String currentValue = personalKeyField.getText().toString().replaceAll("[\\s\\u0085\\p{Z}]", "");
                Log.d("debugtag", "currentfalue3: " + currentValue);
                if (currentValue != null && currentValue.length() == 32) {

                    SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
                    SharedPreferences.Editor editor = settings.edit();
                    editor.putString("API_key_value", currentValue);
                    editor.commit();
                    leave();
                } else {
                    Toast.makeText(getApplicationContext(), R.string.insert_correct_owm_key, Toast.LENGTH_LONG).show();
                }
            }
        });

        abortButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onBackPressed();
            }
        });


        Toolbar toolbar = findViewById(R.id.toolbar);
        if (getSupportActionBar() == null) {
            setSupportActionBar(toolbar);
        }
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setDisplayShowHomeEnabled(true);

    }

    @Override
    public boolean onSupportNavigateUp() {
        onBackPressed();
        return true;
    }

    private void areYouSure() {
        fragmentShown = true;
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.are_you_sure_fragment, new AreYouSureFragment());
        ft.commit();
    }

    @Override
    public void onBackPressed() {
        if (fragmentShown) {
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.remove(getSupportFragmentManager().findFragmentById(R.id.are_you_sure_fragment));
            ft.commit();
            fragmentShown = false;
        } else {
            areYouSure();
        }
    }

    public void leave() {
        AppPreferencesManager prefManager = new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(this));
        prefManager.setAskedForOwmKey(true);
        ForecastCityActivity.stopTurning = true;
        if (isTaskRoot()) {
            Intent mainIntent = new Intent(getApplicationContext(), ForecastCityActivity.class);
            startActivity(mainIntent);
        } else {
            super.onBackPressed();

        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        active = true;
    }

    @Override
    protected void onStop() {
        super.onStop();
        active = false;
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
    }
} @Database(entities = {City.class, CityToWatch.class, CurrentWeatherData.class, Forecast.class, WeekForecast.class}, version = AppDatabase.VERSION)
public abstract class AppDatabase extends RoomDatabase {
    public static final String DB_NAME = "PF_WEATHER_DB.db";
    static final int VERSION = 7;
    static final String TAG = AppDatabase.class.getSimpleName();

    // DAOs
    public abstract CityDao cityDao();
    public abstract CityToWatchDao cityToWatchDao();
    public abstract CurrentWeatherDao currentWeatherDao();

    public abstract ForecastDao forecastDao();

    public abstract WeekForecastDao weekForecastDao();

    // INSTANCE
    private static final Object databaseLock = new Object();
    private static volatile AppDatabase INSTANCE;

    /**
     * Get list of all migrations. If they are ContextAwareMigrations, then inject the Context.
     * @param context to be injected into the ContextAwareMigrations
     * @return an array of Migrations, this can not be empty
     */
    public static Migration[] getMigrations(Context context) {
        Migration[] MIGRATIONS = new Migration[]{
                new Migration_1_2(),
                new Migration_2_3(),
                new Migration_3_4(),
                new Migration_4_5(),
                new Migration_5_6(),
                new Migration_6_7()
                // Add new migrations here
        };

        for(Migration m : MIGRATIONS) {
            if(m instanceof ContextAwareMigration) {
                ((ContextAwareMigration) m).injectContext(context);
            }
        }
        return MIGRATIONS;
    }

    public static AppDatabase getInstance(Context context) {
        if(INSTANCE == null) {
            synchronized (databaseLock) {
                if(INSTANCE == null) {
                    INSTANCE = buildDatabase(context);
                }
            }
        }
        return INSTANCE;
    }

    private static AppDatabase buildDatabase(final Context context) {
        Migration[] MIGRATIONS = getMigrations(context);

        return Room.databaseBuilder(context, AppDatabase.class, DB_NAME)
                .addMigrations(MIGRATIONS)
                .addCallback(new Callback() {
                    @Override
                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
                        super.onCreate(db);

                        // disable WAL because of the upcoming big transaction
                        db.setTransactionSuccessful();
                        db.endTransaction();
                        db.disableWriteAheadLogging();
                        db.beginTransaction();

                        int cityCount = 0;
                        Cursor c = db.query("SELECT count(*) FROM CITIES");
                        if(c != null) {
                            if(c.moveToFirst()) {
                                cityCount = c.getInt(c.getColumnIndex("count(*)"));
                            }
                            c.close();
                        }

                        Log.d(TAG, "City count: " + cityCount);
                        if(cityCount == 0) {
                            fillCityDatabase(context, db);
                        }

                        // TODO: DEBUG ONLY - REMOVE WHEN DONE
                        c = db.query("SELECT count(*) FROM CITIES");
                        if(c != null) {
                            if(c.moveToFirst()) {
                                cityCount = c.getInt(c.getColumnIndex("count(*)"));
                            }
                            c.close();
                        }
                        Log.d(TAG, "City count: " + cityCount);
                    }
                })
                .allowMainThreadQueries()
                .fallbackToDestructiveMigration()
                .build();
    }

    public static void fillCityDatabase(final Context context, SupportSQLiteDatabase database) {
        long startInsertTime = System.currentTimeMillis();

        InputStream inputStream = context.getResources().openRawResource(R.raw.city_list);
        try {
            FileReader fileReader = new FileReader();
            final List<City> cities = fileReader.readCitiesFromFile(inputStream);

            if (cities.size() > 0) {
                for (City c : cities) {
                    ContentValues values = new ContentValues();
                    values.put("cities_id", c.getCityId());
                    values.put("city_name", c.getCityName());
                    values.put("country_code", c.getCountryCode());
                    values.put("longitude", c.getLongitude());
                    values.put("latitude", c.getLatitude());
                    database.insert("CITIES", SQLiteDatabase.CONFLICT_REPLACE, values);
                }
            }

            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        long endInsertTime = System.currentTimeMillis();
        Log.d("debug_info", "Time for insert:" + (endInsertTime - startInsertTime));
    }
} @Entity(tableName = "CITIES", indices = {@Index(value = {"city_name", "cities_id"})})
public class City {

    private static final String UNKNOWN_POSTAL_CODE_VALUE = "-";

    @PrimaryKey @ColumnInfo(name = "cities_id") private int cityId;
    @ColumnInfo(name = "city_name") @NonNull private String cityName = "";
    @ColumnInfo(name = "country_code") @NonNull private String countryCode = "";
    @ColumnInfo(name = "longitude") private float longitude;
    @ColumnInfo(name = "latitude") private float latitude;

    public City() { }

    @Ignore public City(int cityId, @NonNull String cityName, @NonNull String countryCode, float lon, float lat) {
        this.cityId = cityId;
        this.cityName = cityName;
        this.countryCode = countryCode;
        this.longitude = lon;
        this.latitude = lat;
    }

    public int getCityId() {
        return cityId;
    }

    public void setCityId(int cityId) {
        this.cityId = cityId;
    }

    public @NonNull String getCityName() {
        return cityName;
    }

    public void setCityName(@NonNull String cityName) {
        this.cityName = cityName;
    }

    public @NonNull String getCountryCode() {
        return countryCode;
    }

    public void setCountryCode(@NonNull String countryCode) {
        this.countryCode = countryCode;
    }

    @Override
    public @NonNull String toString() {
        return String.format(Locale.ENGLISH, "%s, %s (%f - %f)", cityName, countryCode, longitude, latitude);
    }

    public void setLatitude(float latitude) {
        this.latitude = latitude;
    }

    public float getLatitude() {
        return latitude;
    }

    public float getLongitude() {
        return longitude;
    }

    public void setLongitude(float lon) {
        this.longitude = lon;
    }
} @SuppressWarnings({RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED,
        RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, RoomWarnings.INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED})

/**
 * This class is the database model for the cities to watch. 'Cities to watch' means the locations
 * for which a user would like to see the weather for. This includes those locations that will be
 * deleted after app close (non-persistent locations).
 */
@Entity(tableName = "CITIES_TO_WATCH", foreignKeys = {
        @ForeignKey(entity = City.class,
                parentColumns = {"cities_id"},
                childColumns = {"city_id"},
                onDelete = ForeignKey.CASCADE)})
public class CityToWatch {

    @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "cities_to_watch_id") private int id = 0;
    @ColumnInfo(name = "city_id") private int cityId;
    @ColumnInfo(name = "rank") private int rank;
    @Embedded private City city;

    public CityToWatch() {
        this.city = new City();
    }

    @Ignore
    public CityToWatch(int rank, String countryCode, int id, int cityId, String cityName, float longitude, float latitude) {
        this.rank = rank;
        this.id = id;
        this.cityId = cityId;
        this.city = new City();
        this.city.setCityName(cityName);
        this.city.setCityId(cityId);
        this.city.setCountryCode(countryCode);
        this.city.setLongitude(longitude);
        this.city.setLatitude(latitude);
    }

    public City getCity() {
        return city;
    }

    public void setCity(City city) {
        this.city = city;
    }

    public int getId() {
        return id;
    }

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

    public int getCityId() {
        return cityId;
    }

    public void setCityId(int cityId) {
        this.cityId = cityId;
    }

    public String getCityName() {
        return city.getCityName();
    }

    public void setCityName(String cityName) {
        this.city.setCityName(cityName);
    }

    public String getCountryCode() {
        return city.getCountryCode();
    }

    public void setCountryCode(String countryCode) {
        this.city.setCountryCode(countryCode);
    }

    public int getRank() {
        return rank;
    }

    public void setRank(int rank) {
        this.rank = rank;
    }

    public float getLongitude() {
        return this.city.getLongitude();
    }

    public float getLatitude() {
        return this.city.getLatitude();
    }

    public void setLongitude(float lon) {
        this.city.setLongitude(lon);
    }

    public void setLatitude(float lat) {
        this.city.setLatitude(lat);
    }

} @SuppressWarnings({RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED,
        RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD})

/**
 * This class represents the database model for current weather data of cities.
 */
@Entity(tableName = "CURRENT_WEATHER", foreignKeys = {
        @ForeignKey(entity = City.class,
                parentColumns = {"cities_id"},
                childColumns = {"city_id"},
                onDelete = ForeignKey.CASCADE)})
public class CurrentWeatherData {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "current_weather_id")
    private int id;
    @ColumnInfo(name = "city_id")
    private int city_id;
    @ColumnInfo(name = "time_of_measurement")
    private long timestamp;
    @ColumnInfo(name = "weather_id")
    private int weatherID;
    @ColumnInfo(name = "temperature_current")
    private float temperatureCurrent;
    @ColumnInfo(name = "temperature_min")
    private float temperatureMin;//TODO: Remove, not available in one call api
    @ColumnInfo(name = "temperature_max")
    private float temperatureMax;//TODO: Remove, not available in one call api
    @ColumnInfo(name = "humidity")
    private float humidity;
    @ColumnInfo(name = "pressure")
    private float pressure;
    @ColumnInfo(name = "wind_speed")
    private float windSpeed;
    @ColumnInfo(name = "wind_direction")
    private float windDirection;
    @ColumnInfo(name = "cloudiness")
    private float cloudiness;
    @ColumnInfo(name = "time_sunrise")
    private long timeSunrise;
    @ColumnInfo(name = "time_sunset")
    private long timeSunset;
    @ColumnInfo(name = "timezone_seconds")
    private int timeZoneSeconds;
    @ColumnInfo(name = "rain60min")
    private String rain60min;

    @Ignore
    private String city_name;

    public CurrentWeatherData() {
        this.city_id = Integer.MIN_VALUE;
    }

    @Ignore
    public CurrentWeatherData(int id, int city_id, long timestamp, int weatherID, float temperatureCurrent, float temperatureMin, float temperatureMax, float humidity, float pressure, float windSpeed, float windDirection, float cloudiness, long timeSunrise, long timeSunset, int timeZoneSeconds) {
        this.id = id;
        this.city_id = city_id;
        this.timestamp = timestamp;
        this.weatherID = weatherID;
        this.temperatureCurrent = temperatureCurrent;
        this.temperatureMin = temperatureMin;
        this.temperatureMax = temperatureMax;
        this.humidity = humidity;
        this.pressure = pressure;
        this.windSpeed = windSpeed;
        this.windDirection = windDirection;
        this.cloudiness = cloudiness;
        this.timeSunrise = timeSunrise;
        this.timeSunset = timeSunset;
        this.timeZoneSeconds = timeZoneSeconds;
        this.rain60min = rain60min;
    }

    public int getId() {
        return id;
    }

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

    public int getCity_id() {
        return city_id;
    }

    public void setCity_id(int city_id) {
        this.city_id = city_id;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public int getWeatherID() {
        return weatherID;
    }

    public void setWeatherID(int weatherID) {
        this.weatherID = weatherID;
    }

    public float getTemperatureCurrent() {
        return temperatureCurrent;
    }

    public void setTemperatureCurrent(float temperatureCurrent) {
        this.temperatureCurrent = temperatureCurrent;
    }

    public float getTemperatureMin() {
        return temperatureMin;
    }

    public void setTemperatureMin(float temperatureMin) {
        this.temperatureMin = temperatureMin;
    }

    public float getTemperatureMax() {
        return temperatureMax;
    }

    public void setTemperatureMax(float temperatureMax) {
        this.temperatureMax = temperatureMax;
    }

    public float getHumidity() {
        return humidity;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    public float getPressure() {
        return pressure;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    public float getWindSpeed() {
        return windSpeed;
    }

    public void setWindSpeed(float windSpeed) {
        this.windSpeed = windSpeed;
    }

    public float getWindDirection() {
        return windDirection;
    }

    public void setWindDirection(float windDirection) {
        this.windDirection = windDirection;
    }

    public float getCloudiness() {
        return cloudiness;
    }

    public void setCloudiness(float cloudiness) {
        this.cloudiness = cloudiness;
    }

    public long getTimeSunrise() {
        return timeSunrise;
    }

    public void setTimeSunrise(long timeSunrise) {
        this.timeSunrise = timeSunrise;
    }

    public long getTimeSunset() {
        return timeSunset;
    }

    public void setTimeSunset(long timeSunset) {
        this.timeSunset = timeSunset;
    }

    public String getCity_name() {
        return city_name;
    }

    public void setCity_name(String city_name) {
        this.city_name = city_name;
    }

    public int getTimeZoneSeconds() {
        return timeZoneSeconds;
    }

    public void setTimeZoneSeconds(int timeZoneSeconds) {
        this.timeZoneSeconds = timeZoneSeconds;
    }

    public String getRain60min() {
        return rain60min;
    }

    public void setRain60min(String rain60min) {
        this.rain60min = rain60min;
    }
} @SuppressWarnings({RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED,
        RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, RoomWarnings.INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED})

/**
 * This class is the database model for the forecasts table.
 */
@Entity(tableName = "FORECASTS", foreignKeys = {
        @ForeignKey(entity = City.class,
                parentColumns = {"cities_id"},
                childColumns = {"city_id"},
                onDelete = ForeignKey.CASCADE)})
public class Forecast {

    public static final float NO_RAIN_VALUE = 0;

    @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "forecast_id") private int id;
    @ColumnInfo(name = "city_id") private int city_id;
    @ColumnInfo(name = "time_of_measurement") private long timestamp;
    @ColumnInfo(name = "forecast_for") private long forecastTime;
    @ColumnInfo(name = "weather_id") private int weatherID;
    @ColumnInfo(name = "temperature_current") private float temperature;
    @ColumnInfo(name = "humidity") private float humidity;
    @ColumnInfo(name = "pressure") private float pressure;
    @ColumnInfo(name = "wind_speed") private float windSpeed;
    @ColumnInfo(name = "wind_direction") private float windDirection;
    @ColumnInfo(name = "precipitation")
    private float rainValue;
    @ColumnInfo(name = "rain_probability")
    private float rainProbability;
    @Embedded
    private City city;

    public Forecast() { }

    @Ignore
    public Forecast(int id, int city_id, long timestamp, long forecastFor, int weatherID, float temperature, float humidity,
                    float pressure, float windSpeed, float windDirection, float rainValue, float rainProbability) {
        this.id = id;
        this.city_id = city_id;
        this.timestamp = timestamp;
        this.forecastTime = forecastFor;
        this.weatherID = weatherID;
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        this.windSpeed = windSpeed;
        this.windDirection = windDirection;
        this.rainValue = rainValue;
        this.rainProbability = rainProbability;
    }

    public float getWindDirection() {
        return windDirection;
    }

    public void setWindDirection(float windDirection) {
        this.windDirection = windDirection;
    }

    public float getWindSpeed() {
        return windSpeed;
    }

    public void setWindSpeed(float speed) {
        windSpeed = speed;
    }

    /**
     * @return Returns the ID of the record (which uniquely identifies the record).
     */
    public int getId() {
        return id;
    }

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

    /**
     * @return Returns the date and time for the forecast.
     */
    public long getForecastTime() {
        return forecastTime;
    }

    /**
     * @return Returns the local time for the forecast in UTC epoch
     */
    public long getLocalForecastTime(Context context) {
        AppDatabase dbhelper = AppDatabase.getInstance(context);
        CurrentWeatherData data = dbhelper.currentWeatherDao().getCurrentWeatherByCityId(city_id);
        int timezoneseconds = 0;
        if (data != null) {
            timezoneseconds = data.getTimeZoneSeconds();
        } else {
            Toast.makeText(context, R.string.no_forecast_warning, Toast.LENGTH_SHORT).show();
        }
        return forecastTime + timezoneseconds * 1000L;
    }

    /**
     * @param forecastFor The point of time for the forecast.
     */
    public void setForecastTime(long forecastFor) {
        this.forecastTime = forecastFor;
    }

    /**
     * @return Returns the point of time when the data was inserted into the database in Unix, UTC.
     */
    public long getTimestamp() {
        return timestamp;
    }

    /**
     * @param timestamp The point of time to set when the data was inserted into the database in
     *                  Unix, UTC.
     */
    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public int getCity_id() {
        return city_id;
    }

    public void setCity_id(int city_id) {
        this.city_id = city_id;
    }

    /**
     * @return Returns the weather condition ID.
     */
    public int getWeatherID() {
        return weatherID;
    }

    /**
     * @param weatherID The weather condition ID to set.
     */
    public void setWeatherID(int weatherID) {
        this.weatherID = weatherID;
    }

    /**
     * @return Returns the current temperature in Celsius.
     */
    public float getTemperature() {
        return temperature;
    }

    /**
     * @param temperature The current temperature to set in Celsius.
     */
    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    /**
     * @return Returns the humidity value in percent.
     */
    public float getHumidity() {
        return humidity;
    }

    /**
     * @param humidity The humidity value in percent to set.
     */
    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    /**
     * @return Returns the air pressure value in hectopascal (hPa).
     */
    public float getPressure() {
        return pressure;
    }

    /**
     * @param pressure The air pressure value in hectopascal (hPa) to set.
     */
    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    public String getCity_name() {
        return city.getCityName();
    }

    public void setCity_name(String city_name) {
        this.city.setCityName(city_name);
    }

    public float getRainValue() {
        return rainValue;
    }

    public void setRainValue(float RainValue) {
        rainValue = RainValue;
    }

    public float getRainProbability() {
        return rainProbability;
    }

    public void setRainProbability(float rainProbability) {
        this.rainProbability = rainProbability;
    }

    public City getCity() {
        return city;
    }

    public void setCity(City city) {
        this.city = city;
    }
} @SuppressWarnings({RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED,
        RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, RoomWarnings.INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED})

/**
 * This class is the database model for the Weekforecasts table.
 */
@Entity(tableName = "WEEKFORECASTS", foreignKeys = {
        @ForeignKey(entity = City.class,
                parentColumns = {"cities_id"},
                childColumns = {"city_id"},
                onDelete = ForeignKey.CASCADE)})
public class WeekForecast {


    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "forecast_id")
    private int id;
    @ColumnInfo(name = "city_id")
    private int city_id;
    @ColumnInfo(name = "time_of_measurement")
    private long timestamp;
    @ColumnInfo(name = "forecastTime")
    private long forecastTime;
    @ColumnInfo(name = "weather_id")
    private int weatherID;
    @ColumnInfo(name = "temperature_current")
    private float temperature;
    @ColumnInfo(name = "temperature_min")
    private float temperature_min;
    @ColumnInfo(name = "temperature_max")
    private float temperature_max;
    @ColumnInfo(name = "humidity")
    private float humidity;
    @ColumnInfo(name = "pressure")
    private float pressure;
    @ColumnInfo(name = "precipitation")
    private float precipitation;
    @ColumnInfo(name = "rain_probability")
    private float rain_probability;
    @ColumnInfo(name = "wind_speed")
    private float wind_speed;
    @ColumnInfo(name = "wind_direction")
    private float wind_direction;
    @ColumnInfo(name = "uv_index")
    private float uv_index;
    @Embedded
    private City city;

    public WeekForecast() {
    }

    @Ignore
    public WeekForecast(int id, int city_id, long timestamp, long forecastTime, int weatherID, float temperature, float temperature_min, float temperature_max, float humidity, float pressure, float precipitation, float wind_speed, float wind_direction, float uv_index, float rain_probability) {
        this.id = id;
        this.city_id = city_id;
        this.timestamp = timestamp;
        this.forecastTime = forecastTime;
        this.weatherID = weatherID;
        this.temperature = temperature;
        this.temperature_min = temperature_min;
        this.temperature_max = temperature_max;
        this.humidity = humidity;
        this.pressure = pressure;
        this.precipitation = precipitation;
        this.wind_speed = wind_speed;
        this.wind_direction = wind_direction;
        this.uv_index = uv_index;
        this.rain_probability = rain_probability;
    }


    /**
     * @return Returns the ID of the record (which uniquely identifies the record).
     */
    public int getId() {
        return id;
    }

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

    /**
     * @return Returns the date and time for the forecast.
     */
    public long getForecastTime() {
        return forecastTime;
    }

    /**
     * @return Returns the local time for the forecast in UTC epoch
     */
    public long getLocalForecastTime(Context context) {
        AppDatabase dbhelper = AppDatabase.getInstance(context);
        int timezoneseconds = dbhelper.currentWeatherDao().getCurrentWeatherByCityId(city_id).getTimeZoneSeconds();
        return forecastTime + timezoneseconds * 1000L;
    }

    /**
     * @param forecastFor The point of time for the forecast.
     */
    public void setForecastTime(long forecastFor) {
        this.forecastTime = forecastFor;
    }

    /**
     * @return Returns the point of time when the data was inserted into the database in Unix, UTC.
     */
    public long getTimestamp() {
        return timestamp;
    }

    /**
     * @param timestamp The point of time to set when the data was inserted into the database in
     *                  Unix, UTC.
     */
    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public int getCity_id() {
        return city_id;
    }

    public void setCity_id(int city_id) {
        this.city_id = city_id;
    }

    /**
     * @return Returns the weather condition ID.
     */
    public int getWeatherID() {
        return weatherID;
    }

    /**
     * @param weatherID The weather condition ID to set.
     */
    public void setWeatherID(int weatherID) {
        this.weatherID = weatherID;
    }

    /**
     * @return Returns the current temperature in Celsius.
     */
    public float getTemperature() {
        return temperature;
    }

    /**
     * @param temperature The current temperature to set in Celsius.
     */
    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    /**
     * @return Returns the min temperature in Celsius.
     */
    public float getMinTemperature() {
        return temperature_min;
    }

    /**
     * @param temperature_min The min temperature to set in Celsius.
     */
    public void setMinTemperature(float temperature_min) {
        this.temperature_min = temperature_min;
    }

    /**
     * @return Returns the max temperature in Celsius.
     */
    public float getMaxTemperature() {
        return temperature_max;
    }

    /**
     * @param temperature_max The max temperature to set in Celsius.
     */
    public void setMaxTemperature(float temperature_max) {
        this.temperature_max = temperature_max;
    }


    /**
     * @return Returns the humidity value in percent.
     */
    public float getHumidity() {
        return humidity;
    }

    /**
     * @param humidity The humidity value in percent to set.
     */
    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    public float getPressure() {
        return pressure;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    public float getPrecipitation() {
        return precipitation;
    }

    public void setPrecipitation(float precipitation) {
        this.precipitation = precipitation;
    }

    public float getWind_speed() {
        return wind_speed;
    }

    public void setWind_speed(float wind_speed) {
        this.wind_speed = wind_speed;
    }

    public float getWind_direction() {
        return wind_direction;
    }

    public void setWind_direction(float wind_direction) {
        this.wind_direction = wind_direction;
    }

    public float getUv_index() {
        return uv_index;
    }

    public void setUv_index(float uv_index) {
        this.uv_index = uv_index;
    }

    public float getTemperature_min() {
        return temperature_min;
    }

    public void setTemperature_min(float temperature_min) {
        this.temperature_min = temperature_min;
    }

    public float getTemperature_max() {
        return temperature_max;
    }

    public void setTemperature_max(float temperature_max) {
        this.temperature_max = temperature_max;
    }

    public City getCity() {
        return city;
    }

    public void setCity(City city) {
        this.city = city;
    }

    public float getRain_probability() {
        return rain_probability;
    }

    public void setRain_probability(float rain_probability) {
        this.rain_probability = rain_probability;
    }
} public class AppPreferencesManager {

    /**
     * Constants
     */
    public static final String PREFERENCES_NAME = "org.secuso.privacyfriendlyweather.preferences";
    public static final String OLD_PREF_NAME = "weather-Preference";

    private static final String IS_FIRST_TIME_LAUNCH = "IsFirstTimeLaunch";
    private static final String ASKED_FOR_OWM_KEY = "AskedForOWMKey";

    /**
     * Member variables
     */
    SharedPreferences preferences;

    /**
     * Constructor.
     *
     * @param preferences Source for the preferences to use.
     */
    public AppPreferencesManager(SharedPreferences preferences) {
        this.preferences = preferences;
    }


    public void setFirstTimeLaunch(boolean isFirstTime) {
        SharedPreferences.Editor editor = preferences.edit();
        editor.putBoolean(IS_FIRST_TIME_LAUNCH, isFirstTime);
        editor.commit();
    }

    public void setAskedForOwmKey(boolean asked) {
        SharedPreferences.Editor editor = preferences.edit();
        editor.putBoolean(ASKED_FOR_OWM_KEY, asked);
        editor.commit();
    }

    public boolean isFirstTimeLaunch() {
        return preferences.getBoolean(IS_FIRST_TIME_LAUNCH, true);
    }

    public boolean askedForOWMKey() {
        return preferences.getBoolean(ASKED_FOR_OWM_KEY, false);
    }

    /**
     * This method converts a given temperature value into the unit that was set in the preferences.
     *
     * @param temperature The temperature to convert into the unit that is set in the preferences.
     *                    Make sure to pass a value in celsius.
     * @return Returns the converted temperature.
     */
    public float convertTemperatureFromCelsius(float temperature) {
        // 1 = Celsius (fallback), 2 = Fahrenheit
        int prefValue = Integer.parseInt(preferences.getString("temperatureUnit", "1"));
        if (prefValue == 1) {
            return temperature;
        } else {
            return (((temperature * 9) / 5) + 32);
        }
    }

    public boolean is3hourForecastSet() {
        return 2 == Integer.parseInt(preferences.getString("forecastChoice", "1"));
    }

    /**
     * This method converts a given distance value into the unit that was set in the preferences.
     *
     * @param kilometers The kilometers to convert into the unit that is set in the preferences.
     *                   Make sure to pass a value in kilometers.
     * @return Returns the converted distance.
     */
    public float convertDistanceFromKilometers(float kilometers) {
        // 1 = kilometers, 2 = miles
        int prefValue = Integer.parseInt(preferences.getString("distanceUnit", "1"));
        if (prefValue == 1) {
            return kilometers;
        } else {
            return convertKmInMiles(kilometers);
        }
    }

    /**
     * @return Returns true if kilometers was set as distance unit in the preferences else false.
     */
    public boolean isDistanceUnitKilometers() {
        int prefValue = Integer.parseInt(preferences.getString("distanceUnit", "0"));
        return (prefValue == 1);
    }

    /**
     * @return Returns true if miles was set as distance unit in the preferences else false.
     */
    public boolean isDistanceUnitMiles() {
        int prefValue = Integer.parseInt(preferences.getString("distanceUnit", "0"));
        return (prefValue == 2);
    }

    /**
     * Converts a kilometer value in miles.
     *
     * @param km The value to convert to miles.
     * @return Returns the converted value.
     */
    public float convertKmInMiles(float km) {
        // TODO: Is this the right class for the function???
        return (float) (km / 1.609344);
    }

    /**
     * Converts a miles value in kilometers.
     *
     * @param miles The value to convert to kilometers.
     * @return Returns the converted value.
     */
    public float convertMilesInKm(float miles) {
        // TODO: Is this the right class for the function???
        return (float) (miles * 1.609344);
    }

    /**
     * @return Returns "°C" in case Celsius is set and "°F" if Fahrenheit was selected.
     */
    public String getWeatherUnit() {
        int prefValue = Integer.parseInt(preferences.getString("temperatureUnit", "1"));
        if (prefValue == 1) {
            return "°C";
        } else {
            return "°F";
        }
    }

    /**
     * @return Returns "km" in case kilometer is set and "mi" if miles was selected.
     */
    public String getDistanceUnit() {
        int prefValue = Integer.parseInt(preferences.getString("distanceUnit", "1"));
        if (prefValue == 1) {
            return "km";
        } else {
            return "mi";
        }
    }

    public void setThemeChoice(int i) {
        int choice;
        if (i == 0) {
            choice = Integer.parseInt(preferences.getString("themeChoice", "1"));
        } else {
            choice = i;
        }

        switch (choice) {
            case 1:
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
                break;
            case 2:
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
                break;
            case 3:
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
                break;
        }
    }

    /**
     * @return Returns speed unit abbreviations
     */
    public String getSpeedUnit() {
        int prefValue = Integer.parseInt(preferences.getString("speedUnit", "6"));
        if (prefValue == 1) {
            return "km/h";
        } else if (prefValue == 2) {
            return "m/s";
        } else if (prefValue == 3) {
            return "mph";
        } else if (prefValue == 4) {
            return "ft/s";
        } else if (prefValue == 5) {
            return "kn";
        } else {
            return "Bft";
        }
    }

    public String convertToCurrentSpeedUnit(float speedInMetersPerSecond) {
        int prefValue = Integer.parseInt(preferences.getString("speedUnit", "6"));
        if (prefValue == 1) {
            return StringFormatUtils.formatDecimal(speedInMetersPerSecond * 3.6f, "km/h");
        } else if (prefValue == 2) {
            return StringFormatUtils.formatDecimal(speedInMetersPerSecond, "m/s");
        } else if (prefValue == 3) {
            return StringFormatUtils.formatDecimal(speedInMetersPerSecond * 2.23694f, "mph");
        } else if (prefValue == 4) {
            return StringFormatUtils.formatDecimal(speedInMetersPerSecond * 3.28084f, "ft/s");
        } else if (prefValue == 5) {
            return StringFormatUtils.formatDecimal(speedInMetersPerSecond * 1.94384f, "kn");
        } else {
            return StringFormatUtils.formatWindToBeaufort(speedInMetersPerSecond);
        }
    }

    public int get1dayWidgetInfo() {
        return Integer.parseInt(preferences.getString("widgetChoice4", "1"));
    }

    public int get5dayWidgetInfo() {
        return Integer.parseInt(preferences.getString("widgetChoice1", "1"));
    }

    public int get3dayWidgetInfo1() {
        return Integer.parseInt(preferences.getString("widgetChoice2", "1"));
    }

    public int get3dayWidgetInfo2() {
        return Integer.parseInt(preferences.getString("widgetChoice3", "2"));
    }

    public boolean usingPersonalKey(Context context) {
        String prefValue = preferences.getString("API_key_value", context.getString(R.string.settings_API_key_default));
        return !prefValue.equals(context.getString(R.string.settings_API_key_default));
    }

    public String getOWMApiKey(Context context) {
        String noKeyString = context.getString(R.string.settings_API_key_default);
        String prefValue = preferences.getString("API_key_value", noKeyString);
        if (!prefValue.equals(noKeyString)) {
            return prefValue;
        } else {
            int keyIndex = preferences.getInt("last_used_key", 1);
            SharedPreferences.Editor editor = preferences.edit();
            switch (keyIndex) {
                case 1:
                    editor.putInt("last_used_key", 2);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY2;
                case 2:
                    editor.putInt("last_used_key", 3);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY3;
                case 3:
                    editor.putInt("last_used_key", 4);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY4;
                case 4:
                    editor.putInt("last_used_key", 5);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY5;
                case 5:
                    editor.putInt("last_used_key", 6);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY6;
                case 6:
                    editor.putInt("last_used_key", 7);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY7;
                case 7:
                    editor.putInt("last_used_key", 8);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY8;
                case 8:
                    editor.putInt("last_used_key", 9);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY9;
                case 9:
                    editor.putInt("last_used_key", 10);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY10;
                case 10:
                    editor.putInt("last_used_key", 11);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY11;
                case 11:
                    editor.putInt("last_used_key", 12);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY12;
                case 12:
                    editor.putInt("last_used_key", 13);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY13;
                case 13:
                    editor.putInt("last_used_key", 14);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY14;
                case 14:
                    editor.putInt("last_used_key", 15);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY15;
                case 15:
                    editor.putInt("last_used_key", 16);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY16;
                case 16:
                    editor.putInt("last_used_key", 17);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY17;
                case 17:
                    editor.putInt("last_used_key", 18);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY18;
                case 18:
                    editor.putInt("last_used_key", 19);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY19;
                case 19:
                    editor.putInt("last_used_key", 20);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY20;
                default:
                    editor.putInt("last_used_key", 1);
                    editor.commit();
                    return BuildConfig.DEFAULT_API_KEY1;
            }
        }

    }


} public class ViewUpdater {
    private static List<IUpdateableCityUI> subscribers = new ArrayList<>();

    public static void addSubscriber(IUpdateableCityUI sub) {
        if (!subscribers.contains(sub)) {
            subscribers.add(sub);
        }
    }

    public static void removeSubscriber(IUpdateableCityUI sub) {
        subscribers.remove(sub);
    }

    public static void updateCurrentWeatherData(CurrentWeatherData data) {
        ArrayList<IUpdateableCityUI> subcopy = new ArrayList<>(subscribers);
        for (IUpdateableCityUI sub : subcopy) {
            sub.processNewWeatherData(data);
        }
    }

    public static void updateWeekForecasts(List<WeekForecast> forecasts) {
        for (IUpdateableCityUI sub : subscribers) {
            sub.updateWeekForecasts(forecasts);
        }
    }

    public static void updateForecasts(List<Forecast> forecasts) {
        for (IUpdateableCityUI sub : subscribers) {
            sub.updateForecasts(forecasts);
        }
    }

    public static void abortUpdate() {
        for (IUpdateableCityUI sub : subscribers) {
            sub.abortUpdate();
        }
    }
} public interface IDataExtractor {

    /**
     * Takes the response from the (web) server and checks whether the requested city was found.
     *
     * @param data The textual response from the server.
     * @return Returns true if the city was found or false otherwise (i. e. in case of 404).
     */
    boolean wasCityFound(String data);

    /**
     * @param data The data that contains the information to instantiate a CurrentWeatherData
     *             object. In the easiest case this is the (HTTP) response of the API.
     * @return Returns the extracted information as a CurrentWeatherData instance.
     */
    CurrentWeatherData extractCurrentWeatherData(String data);

    /**
     * @param data The data that contains the information to instantiate a CurrentWeatherData
     *             object. In the easiest case this is the (HTTP) response of the One Call API.
     * @return Returns the extracted information as a CurrentWeatherData instance.
     */
    CurrentWeatherData extractCurrentWeatherDataOneCall(String data);

    /**
     * Note that data shall contain information for instantiating <b>one</b> RadiusSearchItem
     * instance.
     *
     * @param data The data that provides the information that are necessary to create
     *             RadiusSearchItem instances.
     * @return Returns an RadiusSearchItem object that was created using the provided information.
     */
    RadiusSearchItem extractRadiusSearchItemData(String data);

    /**
     * @param data The data that contains the information to instantiate a Forecast object.
     * @return Returns the extracted weather forecast information. In case some error occurs, null
     * will be returned.
     */
    WeekForecast extractWeekForecast(String data);

    /**
     * @param data The data that contains the information to instantiate a Forecast object.
     * @return Returns the extracted weather forecast information. In case some error occurs, null
     * will be returned.
     */

    Forecast extractHourlyForecast(String data);

    /**
     * @param data The data that contains the information to instantiate a WeekForecast object.
     * @return Returns the extracted weather forecast information. In case some error occurs, null
     * will be returned.
     */
    Forecast extractForecast(String data);

    /**
     * @param data The data that contains the information to retrieve the ID of the city.
     * @return Returns the ID of the city or Integer#MIN_VALUE in case the data is not well-formed
     * and the information could not be extracted.
     */
    int extractCityID(String data);

    /**
     * @param data0, data1, data2, data3, data4 contain the information to retrieve the rain for a minute within the next 60min.
     * @return Returns a string with a rain drop in case of rain or a - in case of no rain
     */
    String extractRain60min(String data0, String data1, String data2, String data3, String data4);

    /**
     * @param data The data that contains the longitude and latitude to extract.
     * @return Returns an array where the first element is the latitude and the second the
     * longitude. In case an error occurs, an empty array will be returned.
     */
    double[] extractLatitudeLongitude(String data);

} public interface IProcessHttpRequest {

    /**
     * The method that will be executed in case of a successful HTTP request.
     *
     * @param response The response of the HTTP request.
     */
    void processSuccessScenario(String response);

    /**
     * This method will be executed in case any error arose while executing the HTTP request.
     *
     * @param error The error that occurred while executing the HTTP request.
     */
    void processFailScenario(VolleyError error);

} public class WeatherWidget extends AppWidgetProvider {
    public static final String PREFS_NAME = "org.secuso.privacyfriendlyweather.widget.WeatherWidget";
    public static final String PREF_PREFIX_KEY = "appwidget_";

    public void updateAppWidget(Context context, final int appWidgetId) {

        int cityID = context.getSharedPreferences(WeatherWidget.PREFS_NAME, 0).
                getInt(WeatherWidget.PREF_PREFIX_KEY + appWidgetId, -1);
        Intent intent = new Intent(context, UpdateDataService.class);
        Log.d("debugtag", "1day widget calls single update: " + cityID + " with widgetID " + appWidgetId);

        intent.setAction(UpdateDataService.UPDATE_SINGLE_ACTION);
        intent.putExtra("cityId", cityID);
        intent.putExtra(SKIP_UPDATE_INTERVAL, true);
        enqueueWork(context, UpdateDataService.class, 0, intent);

    }

    public static void updateView(Context context, AppWidgetManager appWidgetManager, RemoteViews views, int appWidgetId, City city, CurrentWeatherData weatherData) {
        AppPreferencesManager prefManager =
                new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()));
        DecimalFormat decimalFormat = new DecimalFormat("0.0");
        String temperature = String.format(
                "%s%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(weatherData.getTemperatureCurrent())),
                prefManager.getWeatherUnit()
        );
        SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm");
        timeFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

        //correct for timezone differences
        int zoneseconds = weatherData.getTimeZoneSeconds();

        Date riseTime = new Date((weatherData.getTimeSunrise() + zoneseconds) * 1000L);
        String sunRise = timeFormat.format(riseTime);
        Date setTime = new Date((weatherData.getTimeSunset() + zoneseconds) * 1000L);
        String sunSet = timeFormat.format(setTime);

        String windSpeed = prefManager.convertToCurrentSpeedUnit(weatherData.getWindSpeed());

        views.setTextViewText(R.id.widget_city_weather_temperature, temperature);
        views.setTextViewText(R.id.widget_city_weather_humidity, String.format("%s %%", (int) weatherData.getHumidity()));
        views.setTextViewText(R.id.widget_city_name, city.getCityName());
        views.setTextViewText(R.id.widget_city_weather_rise, sunRise);
        views.setTextViewText(R.id.widget_city_weather_set, sunSet);
        views.setTextViewText(R.id.widget_city_weather_wind, windSpeed);


        boolean isDay = weatherData.getTimestamp()  > weatherData.getTimeSunrise() && weatherData.getTimestamp() < weatherData.getTimeSunset();

        views.setImageViewResource(R.id.widget_city_weather_image_view, UiResourceProvider.getIconResourceForWeatherCategory(weatherData.getWeatherID(), isDay));

        Intent intent = new Intent(context, ForecastCityActivity.class);
        intent.putExtra("cityId", city.getCityId());
        PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, 0);
        views.setOnClickPendingIntent(R.id.widget1day_layout, pendingIntent);

        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetId);
        }
    }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        // When the user deletes the widget, delete the preference associated with it.
        for (int appWidgetId : appWidgetIds) {
            WeatherWidgetConfigureActivity.deleteTitlePref(context, appWidgetId);
        }
    }

    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }

    public static void forceWidgetUpdate(Context context) {
        forceWidgetUpdate(null, context);
    }

    public static void forceWidgetUpdate(Integer widgetId, Context context) {
        Intent intent = new Intent(context, WeatherWidget.class);
        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
        int[] ids;
        if (widgetId == null) {
            ids = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, WeatherWidget.class));
        } else {
            ids = new int[]{widgetId};
        }
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
        context.sendBroadcast(intent);
    }
} public class WeatherWidgetFiveDayForecast extends AppWidgetProvider {
    public static final String PREFS_NAME = "org.secuso.privacyfriendlyweather.widget.WeatherWidget5Day";

    public void updateAppWidget(Context context, int appWidgetId) {

        int cityID = context.getSharedPreferences(WeatherWidgetFiveDayForecast.PREFS_NAME, 0).
                getInt(WeatherWidget.PREF_PREFIX_KEY + appWidgetId, -1);
        Intent intent = new Intent(context, UpdateDataService.class);
        intent.setAction(UpdateDataService.UPDATE_SINGLE_ACTION);

        intent.putExtra("cityId", cityID);
        intent.putExtra(SKIP_UPDATE_INTERVAL, true);
        enqueueWork(context, UpdateDataService.class, 0, intent);

    }

    public static void updateView(Context context, AppWidgetManager appWidgetManager, RemoteViews views, int appWidgetId, float[][] data, City city) {
        AppPreferencesManager prefManager =
                new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()));
        DecimalFormat decimalFormat = new DecimalFormat("#");
        SimpleDateFormat dayFormat = new SimpleDateFormat("EEE");
        dayFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        DecimalFormat decimal1Format = new DecimalFormat("0.0");

        //forecastList = DayForecastFilter.filter(forecastList, 5);
        if (data.length < 5) return;

        String day1 = dayFormat.format((long) data[0][8]);
        String day2 = dayFormat.format((long) data[1][8]);
        String day3 = dayFormat.format((long) data[2][8]);
        String day4 = dayFormat.format((long) data[3][8]);
        String day5 = dayFormat.format((long) data[4][8]);

        String temperature1 = String.format(
                "%s\u200a|\u200a%s%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[0][0])),
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[0][1])),
                prefManager.getWeatherUnit()
        );
        String temperature2 = String.format(
                "%s\u200a|\u200a%s%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[1][0])),
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[1][1])),
                prefManager.getWeatherUnit()
        );
        String temperature3 = String.format(
                "%s\u200a|\u200a%s%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[2][0])),
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[2][1])),
                prefManager.getWeatherUnit()
        );
        String temperature4 = String.format(
                "%s\u200a|\u200a%s%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[3][0])),
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[3][1])),
                prefManager.getWeatherUnit()
        );
        String temperature5 = String.format(
                "%s\u200a|\u200a%s%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[4][0])),
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[4][1])),
                prefManager.getWeatherUnit()
        );

        String extra1 = "";
        String extra2 = "";
        String extra3 = "";
        String extra4 = "";
        String extra5 = "";
        //select extra information to display from settings
        int extraInfo = prefManager.get5dayWidgetInfo();
        if (extraInfo==1){
            extra1 = String.format("%s\u200amm", decimal1Format.format(data[0][7]));
            extra2 = String.format("%s\u200amm", decimal1Format.format(data[1][7]));
            extra3 = String.format("%s\u200amm", decimal1Format.format( data[2][7]));
            extra4 = String.format("%s\u200amm", decimal1Format.format(data[3][7]));
            extra5 = String.format("%s\u200amm", decimal1Format.format(data[4][7]));
        } else if (extraInfo==2) {
            //wind max & min
            extra1 = prefManager.convertToCurrentSpeedUnit(data[0][5]);
            extra2 = prefManager.convertToCurrentSpeedUnit(data[1][5]);
            extra3 = prefManager.convertToCurrentSpeedUnit(data[2][5]);
            extra4 = prefManager.convertToCurrentSpeedUnit(data[3][5]);
            extra5 = prefManager.convertToCurrentSpeedUnit(data[4][5]);
        } else {
            extra1 = String.format("%s\u200a%%rh", (int) data[0][2]);
            extra2 = String.format("%s\u200a%%rh", (int) data[1][2]);
            extra3 = String.format("%s\u200a%%rh", (int) data[2][2]);
            extra4 = String.format("%s\u200a%%rh", (int) data[3][2]);
            extra5 = String.format("%s\u200a%%rh", (int) data[4][2]);
        }


        views.setTextViewText(R.id.widget_city_name, city.getCityName());

        views.setTextViewText(R.id.widget_city_weather_5day_day1, day1);
        views.setTextViewText(R.id.widget_city_weather_5day_day2, day2);
        views.setTextViewText(R.id.widget_city_weather_5day_day3, day3);
        views.setTextViewText(R.id.widget_city_weather_5day_day4, day4);
        views.setTextViewText(R.id.widget_city_weather_5day_day5, day5);
        views.setTextViewText(R.id.widget_city_weather_5day_temp1, temperature1);
        views.setTextViewText(R.id.widget_city_weather_5day_temp2, temperature2);
        views.setTextViewText(R.id.widget_city_weather_5day_temp3, temperature3);
        views.setTextViewText(R.id.widget_city_weather_5day_temp4, temperature4);
        views.setTextViewText(R.id.widget_city_weather_5day_temp5, temperature5);
        views.setTextViewText(R.id.widget_city_weather_5day_hum1, extra1);
        views.setTextViewText(R.id.widget_city_weather_5day_hum2, extra2);
        views.setTextViewText(R.id.widget_city_weather_5day_hum3, extra3);
        views.setTextViewText(R.id.widget_city_weather_5day_hum4, extra4);
        views.setTextViewText(R.id.widget_city_weather_5day_hum5, extra5);

        views.setImageViewResource(R.id.widget_city_weather_5day_image1, UiResourceProvider.getIconResourceForWeatherCategory((int) data[0][9], true));
        views.setImageViewResource(R.id.widget_city_weather_5day_image2, UiResourceProvider.getIconResourceForWeatherCategory((int) data[1][9], true));
        views.setImageViewResource(R.id.widget_city_weather_5day_image3, UiResourceProvider.getIconResourceForWeatherCategory((int) data[2][9], true));
        views.setImageViewResource(R.id.widget_city_weather_5day_image4, UiResourceProvider.getIconResourceForWeatherCategory((int) data[3][9], true));
        views.setImageViewResource(R.id.widget_city_weather_5day_image5, UiResourceProvider.getIconResourceForWeatherCategory((int) data[4][9], true));

        Intent intent = new Intent(context, ForecastCityActivity.class);
        intent.putExtra("cityId", city.getCityId());
        PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, 0);

        views.setOnClickPendingIntent(R.id.widget5day_layout, pendingIntent);
        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetId);
        }
    }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        // When the user deletes the widget, delete the preference associated with it.
        for (int appWidgetId : appWidgetIds) {
            WeatherWidgetFiveDayForecastConfigureActivity.deleteTitlePref(context, appWidgetId);
        }
    }

    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }

    public static void forceWidgetUpdate(Context context) {
        forceWidgetUpdate(null, context);
    }

    public static void forceWidgetUpdate(Integer widgetId, Context context) {
        Intent intent = new Intent(context, WeatherWidgetFiveDayForecast.class);
        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
        int[] ids;
        if (widgetId == null) {
            ids = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, WeatherWidgetFiveDayForecast.class));
        } else {
            ids = new int[]{widgetId};
        }
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
        context.sendBroadcast(intent);
    }
} public class WeatherWidgetOneDayForecast extends AppWidgetProvider {
    public static final String PREFS_NAME = "org.secuso.privacyfriendlyweather.widget.WeatherWidget1Day";

    public void updateAppWidget(Context context, int appWidgetId) {

        int cityID = context.getSharedPreferences(WeatherWidgetOneDayForecast.PREFS_NAME, 0).
                getInt(WeatherWidget.PREF_PREFIX_KEY + appWidgetId, -1);
        Intent intent = new Intent(context, UpdateDataService.class);
        intent.setAction(UpdateDataService.UPDATE_SINGLE_ACTION);

        intent.putExtra("cityId", cityID);
        intent.putExtra(SKIP_UPDATE_INTERVAL, true);
        enqueueWork(context, UpdateDataService.class, 0, intent);

    }

    public static void updateView(Context context, AppWidgetManager appWidgetManager, RemoteViews views, int appWidgetId, float[][] data, City city, long timestamp) {
        AppPreferencesManager prefManager =
                new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()));
        DecimalFormat decimalFormat = new DecimalFormat("#");
        DecimalFormat decimal1Format = new DecimalFormat("0.0");


        //forecastList = DayForecastFilter.filter(forecastList, 5);
        if (data.length < 5) return;

        String temperature1 = String.format(
                "%s\u200a%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[0][1])),
                prefManager.getWeatherUnit()
        );
        String temperature2 = String.format(
                "%s\u200a%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[1][1])),
                prefManager.getWeatherUnit()
        );
        String temperature3 = String.format(
                "%s\u200a%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[2][1])),
                prefManager.getWeatherUnit()
        );
        String temperature4 = String.format(
                "%s\u200a%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[3][1])),
                prefManager.getWeatherUnit()
        );
        String temperature5 = String.format(
                "%s\u200a%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[4][1])),
                prefManager.getWeatherUnit()
        );

        String extra1 = "";
        String extra2 = "";
        String extra3 = "";
        String extra4 = "";
        String extra5 = "";
        //select extra information to display from settings
        int extraInfo = prefManager.get1dayWidgetInfo();
        if (extraInfo==1){
            extra1 = String.format("%smm",decimal1Format.format(data[0][4]));
            extra2 = String.format("%smm", decimal1Format.format(data[1][4]));
            extra3 = String.format("%smm", decimal1Format.format(data[2][4]));
            extra4 = String.format("%smm", decimal1Format.format(data[3][4]));
            extra5 = String.format("%smm", decimal1Format.format(data[4][4]));
        } else if (extraInfo==2) {
            //wind max & min
            extra1 = prefManager.convertToCurrentSpeedUnit(data[0][3]);
            extra2 = prefManager.convertToCurrentSpeedUnit(data[1][3]);
            extra3 = prefManager.convertToCurrentSpeedUnit(data[2][3]);
            extra4 = prefManager.convertToCurrentSpeedUnit(data[3][3]);
            extra5 = prefManager.convertToCurrentSpeedUnit(data[4][3]);
        } else {
            extra1 = String.format("%s%%rh", (int) data[0][2]);
            extra2 = String.format("%s%%rh", (int) data[1][2]);
            extra3 = String.format("%s%%rh", (int) data[2][2]);
            extra4 = String.format("%s%%rh", (int) data[3][2]);
            extra5 = String.format("%s%%rh", (int) data[4][2]);
        }


        views.setTextViewText(R.id.widget_city_name, city.getCityName()+" "+ StringFormatUtils.formatTimeWithoutZone(timestamp));
        Log.d("widgetOne timestamp",timestamp+"");

        views.setTextViewText(R.id.widget_city_weather_1day_temp1, temperature1);
        views.setTextViewText(R.id.widget_city_weather_1day_temp2, temperature2);
        views.setTextViewText(R.id.widget_city_weather_1day_temp3, temperature3);
        views.setTextViewText(R.id.widget_city_weather_1day_temp4, temperature4);
        views.setTextViewText(R.id.widget_city_weather_1day_temp5, temperature5);
        views.setTextViewText(R.id.widget_city_weather_1day_hum1, extra1);
        views.setTextViewText(R.id.widget_city_weather_1day_hum2, extra2);
        views.setTextViewText(R.id.widget_city_weather_1day_hum3, extra3);
        views.setTextViewText(R.id.widget_city_weather_1day_hum4, extra4);
        views.setTextViewText(R.id.widget_city_weather_1day_hum5, extra5);

        views.setImageViewResource(R.id.widget_city_weather_1day_image1, UiResourceProvider.getIconResourceForWeatherCategory((int) data[0][5], true));
        views.setImageViewResource(R.id.widget_city_weather_1day_image2, UiResourceProvider.getIconResourceForWeatherCategory((int) data[1][5], true));
        views.setImageViewResource(R.id.widget_city_weather_1day_image3, UiResourceProvider.getIconResourceForWeatherCategory((int) data[2][5], true));
        views.setImageViewResource(R.id.widget_city_weather_1day_image4, UiResourceProvider.getIconResourceForWeatherCategory((int) data[3][5], true));
        views.setImageViewResource(R.id.widget_city_weather_1day_image5, UiResourceProvider.getIconResourceForWeatherCategory((int) data[4][5], true));

        Intent intent = new Intent(context, ForecastCityActivity.class);
        intent.putExtra("cityId", city.getCityId());
        PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, 0);

        views.setOnClickPendingIntent(R.id.widget1day_layout, pendingIntent);
        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetId);
        }
    }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        // When the user deletes the widget, delete the preference associated with it.
        for (int appWidgetId : appWidgetIds) {
            WeatherWidgetOneDayForecastConfigureActivity.deleteTitlePref(context, appWidgetId);
        }
    }

    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }

    public static void forceWidgetUpdate(Context context) {
        forceWidgetUpdate(null, context);
    }

    public static void forceWidgetUpdate(Integer widgetId, Context context) {
        Intent intent = new Intent(context, WeatherWidgetOneDayForecast.class);
        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
        int[] ids;
        if (widgetId == null) {
            ids = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, WeatherWidgetOneDayForecast.class));
        } else {
            ids = new int[]{widgetId};
        }
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
        context.sendBroadcast(intent);
    }
} public class WeatherWidgetThreeDayForecast extends AppWidgetProvider {
    public static final String PREFS_NAME = "org.secuso.privacyfriendlyweather.widget.WeatherWidget3Day";

    public static void updateAppWidget(final Context context, final int appWidgetId) {

        int cityID = context.getSharedPreferences(WeatherWidgetThreeDayForecast.PREFS_NAME, 0).
                getInt(WeatherWidget.PREF_PREFIX_KEY + appWidgetId, -1);

        Intent intent = new Intent(context, UpdateDataService.class);
        intent.setAction(UpdateDataService.UPDATE_SINGLE_ACTION);

        intent.putExtra("cityId", cityID);
        intent.putExtra(SKIP_UPDATE_INTERVAL, true);
        enqueueWork(context, UpdateDataService.class, 0, intent);

    }

    public static void updateView(Context context, AppWidgetManager appWidgetManager, RemoteViews views, int appWidgetId, float[][] data, City city) {
        AppPreferencesManager prefManager =
                new AppPreferencesManager(PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()));
        DecimalFormat decimalFormat = new DecimalFormat("0.0");
        SimpleDateFormat dayFormat = new SimpleDateFormat("EEEE");
        dayFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

        //forecastList = DayForecastFilter.filter(forecastList, 3);
        if (data.length < 3) return;

        String day1 = dayFormat.format(data[0][8]);
        String day2 = dayFormat.format(data[1][8]);
        String day3 = dayFormat.format(data[2][8]);

        String temperature1 = String.format(
                "%s | %s%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[0][0])),
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[0][1])),
                prefManager.getWeatherUnit()
        );
        String temperature2 = String.format(
                "%s | %s%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[1][0])),
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[1][1])),
                prefManager.getWeatherUnit()
        );
        String temperature3 = String.format(
                "%s | %s%s",
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[2][0])),
                decimalFormat.format(prefManager.convertTemperatureFromCelsius(data[2][1])),
                prefManager.getWeatherUnit()
        );

        String extra11 = "";
        String extra12 = "";
        String extra13 = "";
        //select extra information to display from settings
        int extraInfo = prefManager.get3dayWidgetInfo1();
        if (extraInfo == 1) {
            extra11 = String.format("%s mm  ", (int) data[0][7]);
            extra12 = String.format("%s mm  ", (int) data[1][7]);
            extra13 = String.format("%s mm  ", (int) data[2][7]);
        } else if (extraInfo == 2) {
            //wind max & min
            extra11 = prefManager.convertToCurrentSpeedUnit(data[0][5]);
            extra12 = prefManager.convertToCurrentSpeedUnit(data[1][5]);
            extra13 = prefManager.convertToCurrentSpeedUnit(data[2][5]);
        } else {
            extra11 = String.format("%s%%rh ", (int) data[0][2]);
            extra12 = String.format("%s%%rh ", (int) data[1][2]);
            extra13 = String.format("%s%%rh ", (int) data[2][2]);
        }

        String extra21 = "";
        String extra22 = "";
        String extra23 = "";
        //select extra information to display from settings
        int extra2Info = prefManager.get3dayWidgetInfo2();
        if (extra2Info == 1) {
            extra21 = String.format("%s mm", (int) data[0][7]);
            extra22 = String.format("%s mm", (int) data[1][7]);
            extra23 = String.format("%s mm", (int) data[2][7]);
        } else if (extra2Info == 2) {
            //wind max & min
            extra21 = String.format("%sm/s", (int) data[0][5]);
            extra22 = String.format("%sm/s", (int) data[1][5]);
            extra23 = String.format("%sm/s", (int) data[2][5]);
        } else {
            extra21 = String.format("%s%%rh", (int) data[0][2]);
            extra22 = String.format("%s%%rh", (int) data[1][2]);
            extra23 = String.format("%s%%rh", (int) data[2][2]);
        }


        views.setTextViewText(R.id.widget_city_name, city.getCityName());

        views.setTextViewText(R.id.widget_city_weather_3day_day1, day1);
        views.setTextViewText(R.id.widget_city_weather_3day_day2, day2);
        views.setTextViewText(R.id.widget_city_weather_3day_day3, day3);
        views.setTextViewText(R.id.widget_city_weather_3day_temp1, temperature1);
        views.setTextViewText(R.id.widget_city_weather_3day_temp2, temperature2);
        views.setTextViewText(R.id.widget_city_weather_3day_temp3, temperature3);
        views.setTextViewText(R.id.widget_city_weather_3day_hum1, extra11 + extra21);
        views.setTextViewText(R.id.widget_city_weather_3day_hum2, extra12 + extra22);
        views.setTextViewText(R.id.widget_city_weather_3day_hum3, extra13 + extra23);

        views.setImageViewResource(R.id.widget_city_weather_3day_image1, UiResourceProvider.getIconResourceForWeatherCategory((int) data[0][9], true));
        views.setImageViewResource(R.id.widget_city_weather_3day_image2, UiResourceProvider.getIconResourceForWeatherCategory((int) data[1][9], true));
        views.setImageViewResource(R.id.widget_city_weather_3day_image3, UiResourceProvider.getIconResourceForWeatherCategory((int) data[2][9], true));

        Intent intent = new Intent(context, ForecastCityActivity.class);
        intent.putExtra("cityId", city.getCityId());
        PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, 0);
        views.setOnClickPendingIntent(R.id.widget3day_layout, pendingIntent);

        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetId);
        }
    }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        // When the user deletes the widget, delete the preference associated with it.
        for (int appWidgetId : appWidgetIds) {
            WeatherWidgetThreeDayForecastConfigureActivity.deleteTitlePref(context, appWidgetId);
        }
    }

    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }

    public static void forceWidgetUpdate(Context context) {
        forceWidgetUpdate(null, context);
    }

    public static void forceWidgetUpdate(Integer widgetId, Context context) {
        Intent intent = new Intent(context, WeatherWidgetThreeDayForecast.class);
        intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
        int[] ids;
        if (widgetId == null) {
            ids = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, WeatherWidgetThreeDayForecast.class));
        } else {
            ids = new int[]{widgetId};
        }
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
        context.sendBroadcast(intent);
    }
}
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;
import com.android.volley.VolleyError;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.secuso.privacyfriendlyweather.R;
import org.secuso.privacyfriendlyweather.activities.CreateKeyActivity;
import org.secuso.privacyfriendlyweather.database.AppDatabase;
import org.secuso.privacyfriendlyweather.database.data.City;
import org.secuso.privacyfriendlyweather.database.data.CityToWatch;
import org.secuso.privacyfriendlyweather.database.data.CurrentWeatherData;
import org.secuso.privacyfriendlyweather.database.data.Forecast;
import org.secuso.privacyfriendlyweather.database.data.WeekForecast;
import org.secuso.privacyfriendlyweather.preferences.AppPreferencesManager;
import org.secuso.privacyfriendlyweather.ui.updater.ViewUpdater;
import org.secuso.privacyfriendlyweather.weather_api.IDataExtractor;
import org.secuso.privacyfriendlyweather.weather_api.IProcessHttpRequest;
import org.secuso.privacyfriendlyweather.widget.WeatherWidget;
import org.secuso.privacyfriendlyweather.widget.WeatherWidgetFiveDayForecast;
import org.secuso.privacyfriendlyweather.widget.WeatherWidgetOneDayForecast;
import org.secuso.privacyfriendlyweather.widget.WeatherWidgetThreeDayForecast;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
package org.secuso.privacyfriendlyweather.weather_api.open_weather_map;





/**
 * This class processes the HTTP requests that are made to the OpenWeatherMap API requesting the
 * current weather for all stored cities.
 */
public class ProcessOwmForecastOneCallAPIRequest implements IProcessHttpRequest {

    /**
     * Constants
     */
    private final String DEBUG_TAG = "process_forecast";

    /**
     * Member variables
     */
    private Context context;
    private AppDatabase dbHelper;

    /**
     * Constructor.
     *
     * @param context The context of the HTTP request.
     */
    public ProcessOwmForecastOneCallAPIRequest(Context context) {
        this.context = context;
        this.dbHelper = AppDatabase.getInstance(context);
    }

    /**
     * Converts the response to JSON and updates the database. Note that for this method no
     * UI-related operations are performed.
     *
     * @param response The response of the HTTP request.
     */
    @Override
    public void processSuccessScenario(String response) {
        IDataExtractor extractor = new OwmDataExtractor();
        try {
            JSONObject json = new JSONObject(response);
            float lat = (float) json.getDouble("lat");
            float lon = (float) json.getDouble("lon");
            //           Log.d("URL JSON",Float.toString(lat));
            //           Log.d("URL JSON",Float.toString(lon));
            int cityId = 0;
            //get CityID from lat/lon
            //Maybe a risk of rounding problems. Alternative: search closest citytowatch
            List<CityToWatch> citiesToWatch = dbHelper.cityToWatchDao().getAll();
            for (int i = 0; i < citiesToWatch.size(); i++) {
                CityToWatch city = citiesToWatch.get(i);
                //if lat/lon of json response very close to lat/lon in citytowatch
                if ((Math.abs(city.getLatitude() - lat) < 0.01) && (Math.abs(city.getLongitude() - lon) < 0.01)) {
                    cityId = city.getCityId();
                    //                   Log.d("URL CITYID", Integer.toString(cityId));
                    break;
                }
            }

            String rain60min = "no data";
            if (json.has("minutely")) {
                rain60min = "";
                JSONArray listrain = json.getJSONArray("minutely");
                for (int i = 0; i < listrain.length() / 5; i++) {   //evaluate in 5min intervals
                    String currentItem0 = listrain.get(i * 5).toString();
                    String currentItem1 = listrain.get(i * 5 + 1).toString();
                    String currentItem2 = listrain.get(i * 5 + 2).toString();
                    String currentItem3 = listrain.get(i * 5 + 3).toString();
                    String currentItem4 = listrain.get(i * 5 + 4).toString();
                    rain60min += extractor.extractRain60min(currentItem0, currentItem1, currentItem2, currentItem3, currentItem4);
                }
            }

            CurrentWeatherData weatherData = extractor.extractCurrentWeatherDataOneCall(json.getString("current"));

            if (weatherData == null) {
                final String ERROR_MSG = context.getResources().getString(R.string.convert_to_json_error);
                Toast.makeText(context, ERROR_MSG, Toast.LENGTH_LONG).show();
            } else {
                weatherData.setCity_id(cityId);
                weatherData.setRain60min(rain60min);
                weatherData.setTimeZoneSeconds(json.getInt("timezone_offset"));

                dbHelper.currentWeatherDao().deleteCurrentWeatherByCityId(cityId);

                dbHelper.currentWeatherDao().addCurrentWeather(weatherData);

                ViewUpdater.updateCurrentWeatherData(weatherData);
            }


            JSONArray listdaily = json.getJSONArray("daily");

            dbHelper.weekForecastDao().deleteWeekForecastsByCityId(cityId);
            List<WeekForecast> weekforecasts = new ArrayList<>();

            for (int i = 0; i < listdaily.length(); i++) {
                String currentItem = listdaily.get(i).toString();
                WeekForecast forecast = extractor.extractWeekForecast(currentItem);
                // Data were not well-formed, abort
                if (forecast == null) {
                    final String ERROR_MSG = context.getResources().getString(R.string.convert_to_json_error);
                    Toast.makeText(context, ERROR_MSG, Toast.LENGTH_LONG).show();
                    return;
                }
                // Could retrieve all data, so proceed
                else {
                    forecast.setCity_id(cityId);
                    // add it to the database
                    dbHelper.weekForecastDao().addWeekForecast(forecast);
                    weekforecasts.add(forecast);
                }
            }

            ViewUpdater.updateWeekForecasts(weekforecasts);



            JSONArray listhourly = json.getJSONArray("hourly");

            dbHelper.forecastDao().deleteForecastsByCityId(cityId);
            List<Forecast> hourlyforecasts = new ArrayList<>();