public class ClientRaw {
    public static final String VALID_HEADER_VALUE = "12345";
    public static final int HEADER = 0;
    public static final int AVERAGE_WIND_SPEED_KNOTS = 1;
    public static final int GUST_SPEED_KNOTS = 2;
    public static final int WIND_DIRECTION_COMPASS_DEGREES = 3;
    public static final int OUTDOOR_TEMPERATURE_CELSIUS = 4;
    public static final int OUTDOOR_HUMIDITY_PERCENTAGE = 5;
    public static final int SURFACE_PRESSURE_HECTOPASCALS = 6;
    public static final int DAILY_RAINFALL_MILLIMETRES = 7;
    public static final int RAINFALL_RATE_MILLIMETRES_PER_MINUTE = 10;
    public static final int INDOOR_TEMPERATURE_CELSIUS = 12;
    public static final int INDOOR_HUMIDITY_PERCENTAGE = 13;
    public static final int FORECAST = 15;
    public static final int HOUR = 29;
    public static final int MINUTE = 30;
    public static final int SECONDS = 31;
    public static final int STATION_NAME = 32;
    public static final int SOLAR_PERCENTAGE = 34;
    public static final int DAY = 35;
    public static final int MONTH = 36;
    public static final int WIND_CHILL_CELSIUS = 44;
    public static final int HUMIDEX_CELSIUS = 45;
    public static final int DAILY_MAX_OUTDOOR_TEMPERATURE_CELSIUS = 46;
    public static final int DAILY_MIN_OUTDOOR_TEMPERATURE_CELSIUS = 47;
    public static final int CURRENT_CONDITIONS_DESCRIPTION = 49;
    public static final int SURFACE_PRESSURE_TREND = 50;
    public static final int DEW_POINT_CELSIUS = 72;
    public static final int UV_INDEX = 79;
    public static final int HEAT_INDEX_CELSIUS = 112;
    public static final int SOLAR_RADIATION_WATTS_PER_METRE_SQUARED = 127;
    public static final int APPARENT_TEMPERATURE_CELSIUS = 130;
    public static final int YEAR = 141;
    public static final int OUTDOOR_TEMPERATURE_TREND = 143;
    public static final int OUTDOOR_HUMIDITY_TREND = 144;
    public static final int LATITUDE_DECIMAL_DEGREES = 160;
    public static final int LONGITUDE_DECIMAL_DEGREES = 161;

    private String[] fields;

    private ClientRaw(String[] fields) {
        this.fields = fields;
    }

    public static ClientRaw getInstance(InputStream is) throws IOException {
        LineNumberReader lnr = null;
        String[] fields = {};

        try {
            lnr = new LineNumberReader(new InputStreamReader(is));
            String line = lnr.readLine();

            if (line != null && line.length() > 0) {
                fields = line.split(" ");
            }
        } finally {
            if (lnr != null) {
                lnr.close();
            }
        }

        return new ClientRaw(fields);
    }

    public boolean isEmpty() {
        return fields.length == 0;
    }

    public boolean isValid() {
        boolean isValid = false;

        String header = getHeader();

        if (header != null && header.equals(VALID_HEADER_VALUE)) {
            isValid = true;
        }

        return isValid;
    }

    public String getHeader() {
        return getFieldValueAsString(HEADER);
    }

    public WindSpeed getAverageWindSpeed() {
        return getFieldValueAsWindSpeed(AVERAGE_WIND_SPEED_KNOTS);
    }

    public WindSpeed getGustSpeed() {
        return getFieldValueAsWindSpeed(GUST_SPEED_KNOTS);
    }

    public WindDirection getWindDirection() {
        return getFieldValueAsWindDirection(WIND_DIRECTION_COMPASS_DEGREES);
    }

    public Temperature getOutdoorTemperature() {
        return getFieldValueAsTemperature(OUTDOOR_TEMPERATURE_CELSIUS);
    }

    public Integer getOutdoorHumidityPercentage() {
        return getFieldValueAsInteger(OUTDOOR_HUMIDITY_PERCENTAGE);
    }

    public Pressure getSurfacePressure() {
        return getFieldValueAsPressure(SURFACE_PRESSURE_HECTOPASCALS);
    }

    public Rainfall getDailyRainfall() {
        return getFieldValueAsRainfall(DAILY_RAINFALL_MILLIMETRES);
    }

    public Rainfall getRainfallRatePerMinute() {
        return getFieldValueAsRainfall(RAINFALL_RATE_MILLIMETRES_PER_MINUTE);
    }

    public Temperature getIndoorTemperature() {
        return getFieldValueAsTemperature(INDOOR_TEMPERATURE_CELSIUS);
    }

    public Integer getIndoorHumidityPercentage() {
        return getFieldValueAsInteger(INDOOR_HUMIDITY_PERCENTAGE);
    }

    public Conditions getForecast() {
        Conditions forecast = null;

        Integer forecastIcon = getFieldValueAsInteger(FORECAST);

        if (forecastIcon != null) {
            forecast = Conditions.resolveIcon(forecastIcon);
        }

        return forecast;
    }

    public Integer getHour() {
        return getFieldValueAsInteger(HOUR);
    }

    public Integer getMinute() {
        return getFieldValueAsInteger(MINUTE);
    }

    public Integer getSeconds() {
        return getFieldValueAsInteger(SECONDS);
    }

    public String getStationName() {
        String stationName = getFieldValueAsString(STATION_NAME);

        if (stationName != null) {
            if (stationName.equals("-")) {
                stationName = null;
            } else {
                stationName = removeTrailingTime(stationName);
                stationName = sanitizeString(stationName);
            }
        }

        return stationName;
    }

    public Integer getSolarPercentage() {
        return getFieldValueAsInteger(SOLAR_PERCENTAGE);
    }

    public Integer getDay() {
        return getFieldValueAsInteger(DAY);
    }

    public Integer getMonth() {
        return getFieldValueAsInteger(MONTH);
    }

    public Temperature getWindChill() {
        return getFieldValueAsTemperature(WIND_CHILL_CELSIUS);
    }

    public Temperature getHumidex() {
        return getFieldValueAsTemperature(HUMIDEX_CELSIUS);
    }

    public Temperature getDailyMaxOutdoorTemperature() {
        return getFieldValueAsTemperature(DAILY_MAX_OUTDOOR_TEMPERATURE_CELSIUS);
    }

    public Temperature getDailyMinOutdoorTemperature() {
        return getFieldValueAsTemperature(DAILY_MIN_OUTDOOR_TEMPERATURE_CELSIUS);
    }

    public String getCurrentConditionsDescription() {
        String currentConditions = getFieldValueAsString(CURRENT_CONDITIONS_DESCRIPTION);

        if (currentConditions != null) {
            currentConditions = sanitizeString(currentConditions);
        }

        return currentConditions;
    }

    public Trend getSurfacePressureTrend() {
        return getFieldValueAsTrend(SURFACE_PRESSURE_TREND);
    }

    public Temperature getDewPoint() {
        return getFieldValueAsTemperature(DEW_POINT_CELSIUS);
    }

    public BigDecimal getUvIndex() {
        return getFieldValueAsBigDecimal(UV_INDEX);
    }

    public Temperature getHeatIndex() {
        return getFieldValueAsTemperature(HEAT_INDEX_CELSIUS);
    }

    public Temperature getApparentTemperature() {
        return getFieldValueAsTemperature(APPARENT_TEMPERATURE_CELSIUS);
    }

    public BigDecimal getSolarRadiation() {
        return getFieldValueAsBigDecimal(SOLAR_RADIATION_WATTS_PER_METRE_SQUARED);
    }

    public Integer getYear() {
        return getFieldValueAsInteger(YEAR);
    }

    public Trend getOutdoorTemperatureTrend() {
        return getFieldValueAsTrend(OUTDOOR_TEMPERATURE_TREND);
    }

    public Trend getOutdoorHumidityTrend() {
        return getFieldValueAsTrend(OUTDOOR_HUMIDITY_TREND);
    }

    public BigDecimal getLatitudeDecimalDegrees() {
        return getFieldValueAsBigDecimal(LATITUDE_DECIMAL_DEGREES);
    }

    public BigDecimal getLongitudeDecimalDegrees() {
        return getFieldValueAsBigDecimal(LONGITUDE_DECIMAL_DEGREES);
    }

    private String sanitizeString(String value) {
        value = replaceUnderscoresWithSpaces(value);
        value = value.trim();
        value = minimizeSpaces(value);

        if (value.length() == 0) {
            value = null;
        }

        return value;
    }

    private String minimizeSpaces(String stationName) {
        stationName = stationName.replaceAll(" +", " ");
        return stationName;
    }

    private String replaceUnderscoresWithSpaces(String value) {
        return value.replaceAll("_", " ");
    }

    private String removeTrailingTime(String value) {
        // e.g. -hh:mm:ss
        int i = value.lastIndexOf("-");

        if (i != -1) {
            value = value.substring(0, i);
        }

        return value;
    }

    private WindSpeed getFieldValueAsWindSpeed(int fieldPosition) {
        WindSpeed value = null;

        BigDecimal valueKnots = getFieldValueAsBigDecimal(fieldPosition);

        if (valueKnots != null) {
            value = new WindSpeed(valueKnots);
        }

        return value;
    }

    private WindDirection getFieldValueAsWindDirection(int fieldPosition) {
        WindDirection value = null;

        Integer valueCompassDegrees = getFieldValueAsInteger(fieldPosition);

        if (valueCompassDegrees != null) {
            value = new WindDirection(valueCompassDegrees);
        }

        return value;
    }

    private Temperature getFieldValueAsTemperature(int fieldPosition) {
        Temperature value = null;

        BigDecimal valueCelsius = getFieldValueAsBigDecimal(fieldPosition);

        if (valueCelsius != null) {
            value = new Temperature(valueCelsius);
        }

        return value;
    }

    private Pressure getFieldValueAsPressure(int fieldPosition) {
        Pressure value = null;

        BigDecimal valueHectopascals = getFieldValueAsBigDecimal(fieldPosition);

        if (valueHectopascals != null) {
            value = new Pressure(valueHectopascals);
        }

        return value;
    }

    private Rainfall getFieldValueAsRainfall(int fieldPosition) {
        Rainfall value = null;

        BigDecimal valueMillimetres = getFieldValueAsBigDecimal(fieldPosition);

        if (valueMillimetres != null) {
            value = new Rainfall(valueMillimetres);
        }

        return value;
    }

    private Integer getFieldValueAsInteger(int fieldPosition) {
        Integer value = null;

        if (fields.length - 1 >= fieldPosition) {
            try {
                value = Integer.parseInt(fields[fieldPosition]);
            } catch (NumberFormatException ex) {
                value = null;
            }
        }

        return value;
    }

    private BigDecimal getFieldValueAsBigDecimal(int fieldPosition) {
        BigDecimal value = null;

        if (fields.length - 1 >= fieldPosition) {
            try {
                value = new BigDecimal(fields[fieldPosition]);
            } catch (NumberFormatException ex) {
                value = null;
            }
        }

        return value;
    }

    private String getFieldValueAsString(int fieldPosition) {
        String value = null;

        if (fields.length - 1 >= fieldPosition) {
            value = fields[fieldPosition];

            if (value.equals("-")) {
                value = null;
            }
        }

        return value;
    }

    private Trend getFieldValueAsTrend(int fieldPosition) {
        Trend value = null;

        BigDecimal bigDecimalValue = getFieldValueAsBigDecimal(fieldPosition);

        if (bigDecimalValue != null) {
            if (bigDecimalValue.compareTo(BigDecimal.valueOf(0)) > 0) {
                value = Trend.RISING;
            } else if (bigDecimalValue.compareTo(BigDecimal.valueOf(0)) < 0) {
                value = Trend.FALLING;
            } else {
                value = Trend.STEADY;
            }
        }

        return value;
    }
}
 public class ClientRawCache {
    private static SparseArray<ClientRaw> cache = new SparseArray<ClientRaw>();

    private ClientRawCache() {
    }

    public static void update(int appWidgetId, ClientRaw clientRaw) {
        cache.put(appWidgetId, clientRaw);
    }

    public static ClientRaw fetch(int appWidgetId) {
        return cache.get(appWidgetId);
    }

    public static void remove(int appWidget) {
        cache.remove(appWidget);
    }

    public static boolean isCached(int appWidget) {
        return fetch(appWidget) != null;
    }
} public class ClientRawRequest {
    private int appWidgetId;
    private ClientRawUrl clientRawUrl;

    public ClientRawRequest(int appWidgetId, ClientRawUrl clientRawUrl) {
        this.appWidgetId = appWidgetId;
        this.clientRawUrl = clientRawUrl;
    }

    public int getAppWidgetId() {
        return appWidgetId;
    }

    public ClientRawUrl getClientRawUrl() {
        return clientRawUrl;
    }
} public class ClientRawResponse {
    private int appWidgetId;
    private ClientRaw clientRaw;
    private String errorMessage;

    public ClientRawResponse(int appWidgetId, ClientRaw clientRaw) {
        this.appWidgetId = appWidgetId;
        this.clientRaw = clientRaw;
    }

    public ClientRawResponse(int appWidgetId, String errorMessage) {
        this.appWidgetId = appWidgetId;
        this.errorMessage = errorMessage;
    }

    public int getAppWidgetId() {
        return appWidgetId;
    }

    public ClientRaw getClientRaw() {
        return clientRaw;
    }

    public String getErrorMessage() {
        return errorMessage;
    }
} public class ClientRawUrl {
    private String url;

    public ClientRawUrl(String url) {
        this.url = url;
    }

    public String getUrl() {
        String correctedUrl = url;

        if (!url.endsWith("/clientraw.txt")) {
            if (!url.endsWith("/")) {
                correctedUrl += "/";
            }

            if (!url.endsWith("clientraw.txt")) {
                correctedUrl += "clientraw.txt";
            }
        }

        if (!url.contains("://")) {
            correctedUrl = "http://" + correctedUrl;
        }

        return correctedUrl;
    }

    @Override
    public String toString() {
        return "ClientRawUrl{" +
                "url='" + url + '\'' +
                '}';
    }
} public class RetrieveClientRawTask extends AsyncTask<ClientRawRequest, Void, List<ClientRawResponse>> {
    private static final String TAG = "RetrieveClientRawTask";

    private static final int MAX_FETCH_CLIENT_RAW_ATTEMPTS = 3;
    private static final long WAIT_BETWEEN_FETCH_CLIENT_RAW_ATTEMPTS_MSECS = 1000;
    private static final int MAX_IS_ONLINE_ATTEMPTS = 5;
    private static final long MAX_WAIT_BETWEEN_IS_ONLINE_ATTEMPTS_MSECS = 5000;

    private UpdateWidgetService updateWidgetService;
    private int connectTimeoutMs;
    private int readTimeoutMs;

    public RetrieveClientRawTask(UpdateWidgetService updateWidgetService, int connectTimeoutMs, int readTimeoutMs) {
        this.updateWidgetService = updateWidgetService;
        this.connectTimeoutMs = connectTimeoutMs;
        this.readTimeoutMs = readTimeoutMs;
    }

    protected List<ClientRawResponse> doInBackground(ClientRawRequest... requests) {
        Log.d(TAG, "doInBackground()");

        List<ClientRawResponse> responses = new ArrayList<ClientRawResponse>();

        for (ClientRawRequest request : requests) {
            int appWidgetId = request.getAppWidgetId();
            ClientRawUrl clientRawUrl = request.getClientRawUrl();

            ClientRawResponse response;

            if (!whileNotOnlineRetryCheck(isOnline())) {
                response = new ClientRawResponse(appWidgetId, "Not online");
            } else {
                try {
                    ClientRaw clientRaw = whileClientRawEmptyRetryFetch(clientRawUrl, fetchClientRaw(clientRawUrl));
                    response = handleClientRaw(appWidgetId, clientRaw);
                } catch (IOException ex) {
                    response = handleException(appWidgetId, ex);
                }
            }

            responses.add(response);
        }

        return responses;
    }

    private ClientRaw whileClientRawEmptyRetryFetch(ClientRawUrl clientRawUrl, ClientRaw clientRaw) throws IOException {
        /* clientraw.txt may have been being written to on the server-side when we read.
           This results in an empty file being returned in which case we try fetching again */
        int fetchClientRawAttempts = 1;

        while (clientRaw.isEmpty() && fetchClientRawAttempts <= MAX_FETCH_CLIENT_RAW_ATTEMPTS) {
            waitFor(WAIT_BETWEEN_FETCH_CLIENT_RAW_ATTEMPTS_MSECS);

            Log.d(TAG, String.format("Fetched clientraw.txt is empty, attempting retry %s of %d",
                    fetchClientRawAttempts, MAX_FETCH_CLIENT_RAW_ATTEMPTS));

            clientRaw = fetchClientRaw(clientRawUrl);
            fetchClientRawAttempts++;
        }

        return clientRaw;
    }

    private ClientRaw fetchClientRaw(ClientRawUrl clientRawUrl) throws IOException {
        Log.d(TAG, String.format("Fetching clientraw.txt from %s", clientRawUrl));

        ClientRaw clientRaw;
        URLConnection connection = new URL(clientRawUrl.getUrl()).openConnection();

        connection.setConnectTimeout(connectTimeoutMs);
        connection.setReadTimeout(readTimeoutMs);

        BufferedInputStream reader = null;

        try {
            reader = new BufferedInputStream(connection.getInputStream());
            clientRaw = ClientRaw.getInstance(reader);
        } finally {
            if (reader != null) {
                reader.close();
            }
        }
        return clientRaw;
    }

    private ClientRawResponse handleClientRaw(int appWidgetId, ClientRaw clientRaw) throws IOException {
        ClientRawResponse response;

        if (clientRaw.isEmpty()) {
            String errorMessage = "Empty clientraw.txt received";
            Log.d(TAG, errorMessage);
            response = new ClientRawResponse(appWidgetId, errorMessage);
        } else if (!clientRaw.isValid()) {
            String errorMessage = "Invalid clientraw.txt received";
            Log.d(TAG, errorMessage);
            response = new ClientRawResponse(appWidgetId, errorMessage);
        } else {
            Log.d(TAG, "Valid clientraw.txt received");
            response = new ClientRawResponse(appWidgetId, clientRaw);
        }

        return response;
    }

    private void waitFor(long waitTimeMsecs) {
        try {
            Thread.sleep(waitTimeMsecs);
        } catch (InterruptedException ex) {
            // Ignore
        }
    }

    private ClientRawResponse handleException(int appWidgetId, IOException exception) {
        Log.e(TAG, "Exception occurred when fetching clientraw.txt", exception);

        ClientRawResponse response;

        if (exception instanceof SocketTimeoutException) {
            response = new ClientRawResponse(appWidgetId, "Connection timed out");
        } else if (exception instanceof FileNotFoundException) {
            response = new ClientRawResponse(appWidgetId, "clientraw.txt not found");
        } else if (exception instanceof UnknownHostException) {
            response = new ClientRawResponse(appWidgetId, "Unknown host");
        } else {
            response = new ClientRawResponse(appWidgetId, "Connection issue");
        }

        return response;
    }

    private boolean whileNotOnlineRetryCheck(boolean isOnline) {
        int isOnlineAttempts = 1;

        while (!isOnline && isOnlineAttempts <= MAX_IS_ONLINE_ATTEMPTS) {
            waitFor(MAX_WAIT_BETWEEN_IS_ONLINE_ATTEMPTS_MSECS);

            Log.d(TAG, String.format("Not online, attempting retry of online check %s of %d",
                    isOnlineAttempts, MAX_IS_ONLINE_ATTEMPTS));

            isOnline = isOnline();
            isOnlineAttempts++;
        }

        return isOnline;
    }

    private boolean isOnline() {
        Log.d(TAG, "Checking if device is online");

        ConnectivityManager connectivityManager =
                (ConnectivityManager) updateWidgetService.getApplicationContext().getSystemService(CONNECTIVITY_SERVICE);

        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();

        Log.d(TAG, "networkInfo=" + networkInfo);

        boolean isOnline = (networkInfo != null && networkInfo.isConnected());

        Log.d(TAG, String.format("Device is online=%b", isOnline));

        return isOnline;
    }

    protected void onPostExecute(List<ClientRawResponse> responses) {
        Log.d(TAG, "onPostExecute()");

        updateWidgetService.handleClientRawResponses(responses);
    }
} public class Temperature {
    private BigDecimal celsius;
    private BigDecimal fahrenheit;

    public Temperature(BigDecimal celsius) {
        this.celsius = celsius;
        this.fahrenheit = (celsius.multiply(new BigDecimal("1.8"))).add(new BigDecimal("32"));
    }

    public BigDecimal getValue(TemperatureUnit unit) {
        BigDecimal value = null;

        switch (unit) {
            case CELSIUS:
                value = celsius;
                break;
            case FAHRENHEIT:
                value = fahrenheit;
                break;
        }

        return value;
    }
}
 public enum Trend {
    RISING,
    STEADY,
    FALLING
}
 public enum WeatherItem {
    APPARENT_TEMPERATURE("Apparent Temperature"),
    AVERAGE_WIND("Average Wind"),
    BLANK("Blank"),
    DAILY_RAINFALL("Daily Rainfall"),
    DEW_POINT("Dew Point"),
    FORECAST("Forecast"),
    GUST("Gust"),
    HEAT_INDEX("Heat Index"),
    HUMIDEX("Humidex"),
    HUMIDITY("Humidity"),
    INDOOR_CONDITIONS("Indoor Conditions"),
    RAINFALL_RATE("Rainfall Rate"),
    SOLAR("Solar"),
    SURFACE_PRESSURE("Surface Pressure"),
    UV_INDEX("UV Index"),
    WIND_CHILL("Wind Chill");

    private String description;

    WeatherItem(String description) {
        this.description = description;
    }

    public String toString() {
        return description;
    }
} public class Preferences {
    private SharedPreferences preferences;
    private Editor editor;

    private static final String CONFIGURED = "configured";
    private static final String CLIENT_RAW_URL = "client-raw-url";
    private static final String STATION_NAME = "station_name";
    private static final String WEATHER_ITEM_1 = "weather-item-1";
    private static final String WEATHER_ITEM_2 = "weather-item-2";
    private static final String WEATHER_ITEM_3 = "weather-item-3";
    private static final String WEATHER_ITEM_4 = "weather-item-4";
    private static final String WEATHER_ITEM_5 = "weather-item-5";
    private static final String TEMPERATURE_UNIT = "temperature-unit";
    private static final String PRESSURE_UNIT = "pressure-unit";
    private static final String WIND_SPEED_UNIT = "wind-speed-unit";
    private static final String WIND_DIRECTION_UNIT = "wind-direction-unit";
    private static final String RAINFALL_UNIT = "rainfall-unit";
    private static final String DATE_FORMAT = "date-format";
    private static final String TIME_FORMAT = "time-format";
    private static final String TRANSPARENT = "transparent";
    private static final String CONNECTION_TIMEOUT = "connection-timeout";
    private static final String READ_TIMEOUT = "read-timeout";

    public Preferences(Context context) {
        preferences = PreferenceManager.getDefaultSharedPreferences(context);
    }

    public boolean isConfigured(int appWidgetId) {
        return Boolean.valueOf(getPreference(CONFIGURED, appWidgetId, Boolean.FALSE.toString()));
    }

    public void setConfigured(int appWidgetId, boolean value) {
        setPreference(CONFIGURED, appWidgetId, Boolean.toString(value));
    }

    public String getClientRawUrl(int appWidgetId) {
        return getPreference(CLIENT_RAW_URL, appWidgetId, null);
    }

    public void setClientRawUrl(int appWidgetId, String value) {
        setPreference(CLIENT_RAW_URL, appWidgetId, value);
    }

    public String getStationName(int appWidgetId) {
        return getPreference(STATION_NAME, appWidgetId, null);
    }

    public void setStationName(int appWidgetId, String value) {
        setPreference(STATION_NAME, appWidgetId, value);
    }

    public void removeStationName(int appWidgetId) {
        removePreference(STATION_NAME, appWidgetId);
    }

    public WeatherItem getWeatherItem1(int appWidgetId) {
        return WeatherItem.valueOf(getPreference(WEATHER_ITEM_1, appWidgetId, WeatherItem.SURFACE_PRESSURE.name()));
    }

    public void setWeatherItem1(int appWidgetId, WeatherItem value) {
        setPreference(WEATHER_ITEM_1, appWidgetId, value.name());
    }

    public WeatherItem getWeatherItem2(int appWidgetId) {
        return WeatherItem.valueOf(getPreference(WEATHER_ITEM_2, appWidgetId, WeatherItem.HUMIDITY.name()));
    }

    public void setWeatherItem2(int appWidgetId, WeatherItem value) {
        setPreference(WEATHER_ITEM_2, appWidgetId, value.name());
    }

    public WeatherItem getWeatherItem3(int appWidgetId) {
        return WeatherItem.valueOf(getPreference(WEATHER_ITEM_3, appWidgetId, WeatherItem.DEW_POINT.name()));
    }

    public void setWeatherItem3(int appWidgetId, WeatherItem value) {
        setPreference(WEATHER_ITEM_3, appWidgetId, value.name());
    }

    public WeatherItem getWeatherItem4(int appWidgetId) {
        return WeatherItem.valueOf(getPreference(WEATHER_ITEM_4, appWidgetId, WeatherItem.AVERAGE_WIND.name()));
    }

    public void setWeatherItem4(int appWidgetId, WeatherItem value) {
        setPreference(WEATHER_ITEM_4, appWidgetId, value.name());
    }

    public WeatherItem getWeatherItem5(int appWidgetId) {
        return WeatherItem.valueOf(getPreference(WEATHER_ITEM_5, appWidgetId, WeatherItem.DAILY_RAINFALL.name()));
    }

    public void setWeatherItem5(int appWidgetId, WeatherItem value) {
        setPreference(WEATHER_ITEM_5, appWidgetId, value.name());
    }

    public TemperatureUnit getTemperatureUnit(int appWidgetId) {
        return TemperatureUnit.valueOf(getPreference(TEMPERATURE_UNIT, appWidgetId, TemperatureUnit.CELSIUS.name()));
    }

    public void setTemperatureUnit(int appWidgetId, TemperatureUnit value) {
        setPreference(TEMPERATURE_UNIT, appWidgetId, value.name());
    }

    public PressureUnit getPressureUnit(int appWidgetId) {
        return PressureUnit.valueOf(getPreference(PRESSURE_UNIT, appWidgetId, PressureUnit.HECTOPASCALS.name()));
    }

    public void setPressureUnit(int appWidgetId, PressureUnit value) {
        setPreference(PRESSURE_UNIT, appWidgetId, value.name());
    }

    public WindSpeedUnit getWindSpeedUnit(int appWidgetId) {
        return WindSpeedUnit.valueOf(getPreference(WIND_SPEED_UNIT, appWidgetId, WindSpeedUnit.KILOMETRES_PER_HOUR.name()));
    }

    public void setWindSpeedUnit(int appWidgetId, WindSpeedUnit value) {
        setPreference(WIND_SPEED_UNIT, appWidgetId, value.name());
    }

    public WindDirectionUnit getWindDirectionUnit(int appWidgetId) {
        return WindDirectionUnit.valueOf(getPreference(WIND_DIRECTION_UNIT, appWidgetId, WindDirectionUnit.CARDINAL_DIRECTION.name()));
    }

    public void setWindDirectionUnit(int appWidgetId, WindDirectionUnit value) {
        setPreference(WIND_DIRECTION_UNIT, appWidgetId, value.name());
    }

    public RainfallUnit getRainfallUnit(int appWidgetId) {
        return RainfallUnit.valueOf(getPreference(RAINFALL_UNIT, appWidgetId, RainfallUnit.MILLIMETRES.name()));
    }

    public void setRainfallUnit(int appWidgetId, RainfallUnit value) {
        setPreference(RAINFALL_UNIT, appWidgetId, value.name());
    }

    public DateFormat getDateFormat(int appWidgetId) {
        String dateFormatStr = getPreference(DATE_FORMAT, appWidgetId, null);
        DateFormat dateFormat = null;

        if (dateFormatStr != null) {
            dateFormat = DateFormat.valueOf(dateFormatStr);
        }

        return dateFormat;
    }

    public void setDateFormat(int appWidgetId, DateFormat value) {
        setPreference(DATE_FORMAT, appWidgetId, value.name());
    }

    public void removeDateFormat(int appWidgetId) {
        removePreference(DATE_FORMAT, appWidgetId);
    }

    public TimeFormat getTimeFormat(int appWidgetId) {
        return TimeFormat.valueOf(getPreference(TIME_FORMAT, appWidgetId, TimeFormat.HOUR_24.name()));
    }

    public void setTimeFormat(int appWidgetId, TimeFormat value) {
        setPreference(TIME_FORMAT, appWidgetId, value.name());
    }

    public boolean isTransparent(int appWidgetId) {
        return Boolean.valueOf(getPreference(TRANSPARENT, appWidgetId, Boolean.FALSE.toString()));
    }

    public void setTransparent(int appWidgetId, boolean value) {
        setPreference(TRANSPARENT, appWidgetId, Boolean.toString(value));
    }

    public Timeout getConnectionTimeout(int appWidgetId) {
        return Timeout.valueOf(getPreference(CONNECTION_TIMEOUT, appWidgetId, Timeout.TEN_SECONDS.name()));
    }

    public void setConnectionTimeout(int appWidgetId, Timeout value) {
        setPreference(CONNECTION_TIMEOUT, appWidgetId, value.name());
    }

    public Timeout getReadTimeout(int appWidgetId) {
        return Timeout.valueOf(getPreference(READ_TIMEOUT, appWidgetId, Timeout.TEN_SECONDS.name()));
    }

    public void setReadTimeout(int appWidgetId, Timeout value) {
        setPreference(READ_TIMEOUT, appWidgetId, value.name());
    }

    public void removePreferences(int appWidgetId) {
        removePreference(CONFIGURED, appWidgetId);
        removePreference(CLIENT_RAW_URL, appWidgetId);
        removePreference(STATION_NAME, appWidgetId);
        removePreference(WEATHER_ITEM_1, appWidgetId);
        removePreference(WEATHER_ITEM_2, appWidgetId);
        removePreference(WEATHER_ITEM_3, appWidgetId);
        removePreference(WEATHER_ITEM_4, appWidgetId);
        removePreference(WEATHER_ITEM_5, appWidgetId);
        removePreference(TEMPERATURE_UNIT, appWidgetId);
        removePreference(PRESSURE_UNIT, appWidgetId);
        removePreference(WIND_SPEED_UNIT, appWidgetId);
        removePreference(WIND_DIRECTION_UNIT, appWidgetId);
        removePreference(RAINFALL_UNIT, appWidgetId);
        removePreference(DATE_FORMAT, appWidgetId);
        removePreference(TIME_FORMAT, appWidgetId);
        removePreference(TRANSPARENT, appWidgetId);
        removePreference(CONNECTION_TIMEOUT, appWidgetId);
        removePreference(READ_TIMEOUT, appWidgetId);
    }

    public void commit() {
        if (editor != null) {
            editor.commit();
            editor = null;
        }
    }

    private String getPreference(String key, int appWidgetId, String defaultValue) {
        return preferences.getString(getKeyForAppWidgetId(key, appWidgetId), defaultValue);
    }

    @SuppressLint("CommitPrefEdits")
    private void setPreference(String key, int appWidgetId, String value) {
        if (editor == null) {
            editor = preferences.edit();
        }

        editor.putString(getKeyForAppWidgetId(key, appWidgetId), value);
    }

    @SuppressLint("CommitPrefEdits")
    private void removePreference(String key, int appWidgetId) {
        if (editor == null) {
            editor = preferences.edit();
        }

        editor.remove(getKeyForAppWidgetId(key, appWidgetId));
    }

    private String getKeyForAppWidgetId(String key, int appWidgetId) {
        return key + "-" + appWidgetId;
    }
} public class TemperatureColorizer {
    public static final int WHITE = 0xffffffff;
    public static final int LIGHT_BLUE = 0xff69c3ff;
    public static final int DARK_GREEN = 0xffaaffaa;
    public static final int MEDIUM_GREEN = 0xffc3ff5d;
    public static final int LIGHT_GREEN = 0xffdefc4e;
    public static final int LIGHT_YELLOW = 0xfffff83b;
    public static final int MEDIUM_YELLOW = 0xffffdc36;
    public static final int DARK_YELLOW = 0xffffcd30;
    public static final int LIGHT_ORANGE = 0xffffb230;
    public static final int MEDIUM_ORANGE = 0xffff9b25;
    public static final int DARK_ORANGE = 0xffff8700;

    private Temperature temperature;

    public TemperatureColorizer(Temperature temperature) {
        this.temperature = temperature;
    }

    public int colorize() {
        int colorCode = WHITE;

        if (temperature != null) {
            BigDecimal temperatureCelsius = temperature.getValue(TemperatureUnit.CELSIUS);
            int temperatureCelsiusRounded = temperatureCelsius.setScale(0, RoundingMode.HALF_DOWN).intValue();

            if (temperatureCelsiusRounded < 1) {
                colorCode = LIGHT_BLUE;
            } else if (temperatureCelsiusRounded > 24) {
                colorCode = DARK_ORANGE;
            } else {
                switch (temperatureCelsiusRounded) {
                    case 1:
                    case 2:
                    case 3:
                        colorCode = DARK_GREEN;
                        break;

                    case 4:
                    case 5:
                    case 6:
                        colorCode = MEDIUM_GREEN;
                        break;

                    case 7:
                    case 8:
                    case 9:
                        colorCode = LIGHT_GREEN;
                        break;

                    case 10:
                    case 11:
                    case 12:
                        colorCode = LIGHT_YELLOW;
                        break;

                    case 13:
                    case 14:
                    case 15:
                        colorCode = MEDIUM_YELLOW;
                        break;

                    case 16:
                    case 17:
                    case 18:
                        colorCode = DARK_YELLOW;
                        break;

                    case 19:
                    case 20:
                    case 21:
                        colorCode = LIGHT_ORANGE;
                        break;

                    case 22:
                    case 23:
                    case 24:
                        colorCode = MEDIUM_ORANGE;
                        break;
                }
            }
        }

        return colorCode;
    }
} public class WeatherItemColorizer {
    static final int WHITE = 0xffffffff;

    private ClientRaw clientRaw;

    public WeatherItemColorizer(ClientRaw clientRaw) {
        this.clientRaw = clientRaw;
    }

    public int colorize(WeatherItem weatherItem) {
        int colorCode;

        switch (weatherItem) {
            case APPARENT_TEMPERATURE:
                colorCode = new TemperatureColorizer(clientRaw.getApparentTemperature()).colorize();
                break;

            case DEW_POINT:
                colorCode = new TemperatureColorizer(clientRaw.getDewPoint()).colorize();
                break;

            case HEAT_INDEX:
                colorCode = new TemperatureColorizer(clientRaw.getHeatIndex()).colorize();
                break;

            case HUMIDEX:
                colorCode = new TemperatureColorizer(clientRaw.getHumidex()).colorize();
                break;

            case UV_INDEX:
                colorCode = new UvIndexColorizer(clientRaw.getUvIndex()).colorize();
                break;

            case WIND_CHILL:
                colorCode = new TemperatureColorizer(clientRaw.getWindChill()).colorize();
                break;

            default:
                colorCode = WHITE;
        }

        return colorCode;
    }
} public enum DateFormat {
    YYYY_MM_DD("YYYY/MM/DD"),
    DD_MM_YYYY("DD/MM/YYYY"),
    MM_DD_YYYY("MM/DD/YYYY");

    private String description;

    DateFormat(String description) {
        this.description = description;
    }

    public String toString() {
        return description;
    }
} public class DateFormatter {
    private Integer year;
    private Integer month;
    private Integer day;

    public DateFormatter(Integer year, Integer month, Integer day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public String format(DateFormat dateFormat) {
        String formatted;

        if (year != null && month != null && day != null) {
            if (dateFormat == YYYY_MM_DD) {
                formatted = String.format(Locale.US, "%1$04d/%2$02d/%3$02d", year, month, day);
            } else if (dateFormat == DateFormat.DD_MM_YYYY) {
                formatted = String.format(Locale.US, "%1$02d/%2$02d/%3$04d", day, month, year);
            } else {
                formatted = String.format(Locale.US, "%1$02d/%2$02d/%3$04d", month, day, year);
            }
        } else {
            if (dateFormat == YYYY_MM_DD) {
                formatted = "----/--/--";
            } else {
                formatted = "--/--/----";
            }
        }

        return formatted;
    }
} public class FormattedWeatherItem {
    private String label;
    private String value;

    public FormattedWeatherItem(String label, String value) {
        this.label = label;
        this.value = value;
    }

    public String getLabel() {
        return label;
    }

    public String getValue() {
        return value;
    }
} public class StringFormatter {
    private String string;

    public StringFormatter(String string) {
        this.string = string;
    }

    public String format() {
        String formatted = "-----";

        if (string != null) {
            formatted = string;
        }

        return formatted;
    }
} public class TemperatureFormatter {
    private Temperature temperature;

    public TemperatureFormatter(Temperature temperature) {
        this.temperature = temperature;
    }

    public String format(TemperatureUnit unit) {
        String formatted;

        if (temperature != null) {
            String formatString;

            if (unit == CELSIUS) {
                formatString = "%1.1f°C";
            } else {
                formatString = "%1.1f°F";
            }

            formatted = String.format(Locale.US, formatString, temperature.getValue(unit).setScale(1, RoundingMode.HALF_DOWN));
        } else {
            if (unit == CELSIUS) {
                formatted = "--.-°C";
            } else {
                formatted = "--.-°F";
            }
        }

        return formatted;
    }
} public enum TimeFormat {
    HOUR_24("24 Hour"),
    HOUR_12("12 Hour");

    private String description;

    TimeFormat(String description) {
        this.description = description;
    }

    public String toString() {
        return description;
    }
} public class TimeFormatter {
    private Integer hour;
    private Integer minute;

    public TimeFormatter(Integer hour, Integer minute) {
        this.hour = hour;
        this.minute = minute;
    }

    public String format(TimeFormat timeFormat) {
        String formatted;

        if (hour != null && minute != null) {
            if (timeFormat == HOUR_24) {
                formatted = String.format(Locale.US, "%1$02d:%2$02d", hour, minute);
            } else {
                formatted = String.format(Locale.US, "%1$01d:%2$02d %3$2s", getTwelveHour(hour), minute, getTwelveHourPeriod(hour));
            }
        } else {
            if (timeFormat == HOUR_24) {
                formatted = "--:--";
            } else {
                formatted = "--:-- --";
            }
        }

        return formatted;
    }

    private int getTwelveHour(int hour24) {
        int twelveHour = hour24;

        if (twelveHour == 0) {
            twelveHour = 12;
        } else if (twelveHour >= 12) {
            if (twelveHour > 12) {
                twelveHour -= 12;
            }
        }

        return twelveHour;
    }

    private String getTwelveHourPeriod(int hour24) {
        String period;

        if (hour24 >= 12) {
            period = "PM";
        } else {
            period = "AM";
        }

        return period;
    }
} public class TrendFormatter {
    private Trend trend;

    public TrendFormatter(Trend trend) {
        this.trend = trend;
    }

    public String format() {
        String formatted = "-";

        if (trend == Trend.RISING) {
            formatted = "\u279A";
        } else if (trend == Trend.STEADY) {
            formatted = "\u279E";
        } else if (trend == Trend.FALLING) {
            formatted = "\u2798";
        }

        return formatted;
    }
} public class WeatherItemFormatter {
    private ClientRaw clientRaw;
    private TemperatureUnit temperatureUnit;
    private PressureUnit pressureUnit;
    private WindSpeedUnit windSpeedUnit;
    private WindDirectionUnit windDirectionUnit;
    private RainfallUnit rainfallUnit;

    public WeatherItemFormatter(ClientRaw clientRaw, TemperatureUnit temperatureUnit, PressureUnit pressureUnit, WindSpeedUnit windSpeedUnit, WindDirectionUnit windDirectionUnit, RainfallUnit rainfallUnit) {
        this.clientRaw = clientRaw;
        this.temperatureUnit = temperatureUnit;
        this.pressureUnit = pressureUnit;
        this.windSpeedUnit = windSpeedUnit;
        this.windDirectionUnit = windDirectionUnit;
        this.rainfallUnit = rainfallUnit;
    }

    public FormattedWeatherItem format(WeatherItem weatherItem) {
        FormattedWeatherItem formattedWeatherItem = null;

        switch (weatherItem) {
            case APPARENT_TEMPERATURE:
                formattedWeatherItem = new FormattedWeatherItem("apparent",
                        new TemperatureFormatter(clientRaw.getApparentTemperature()).format(temperatureUnit));
                break;

            case AVERAGE_WIND:
                formattedWeatherItem = new FormattedWeatherItem("wind",
                        new WindSpeedFormatter(clientRaw.getAverageWindSpeed()).format(windSpeedUnit) + " " +
                                new WindDirectionFormatter(clientRaw.getWindDirection()).format(windDirectionUnit));
                break;

            case BLANK:
                formattedWeatherItem = new FormattedWeatherItem("", "");
                break;

            case DAILY_RAINFALL:
                formattedWeatherItem = new FormattedWeatherItem("daily rain",
                        new RainfallFormatter(clientRaw.getDailyRainfall()).format(rainfallUnit));
                break;

            case DEW_POINT:
                formattedWeatherItem = new FormattedWeatherItem("dew point",
                        new TemperatureFormatter(clientRaw.getDewPoint()).format(temperatureUnit));
                break;

            case FORECAST:
                formattedWeatherItem = new FormattedWeatherItem("fcst",
                        new ConditionsFormatter(clientRaw.getForecast()).format());
                break;

            case GUST:
                formattedWeatherItem = new FormattedWeatherItem("gust",
                        new WindSpeedFormatter(clientRaw.getGustSpeed()).format(windSpeedUnit));
                break;

            case HEAT_INDEX:
                formattedWeatherItem = new FormattedWeatherItem("heat index",
                        new TemperatureFormatter(clientRaw.getHeatIndex()).format(temperatureUnit));
                break;

            case HUMIDEX:
                formattedWeatherItem = new FormattedWeatherItem("humidex",
                        new TemperatureFormatter(clientRaw.getHumidex()).format(temperatureUnit));
                break;

            case HUMIDITY:
                formattedWeatherItem = new FormattedWeatherItem("humidity",
                        new HumidityFormatter(clientRaw.getOutdoorHumidityPercentage()).format() + " " +
                                new TrendFormatter(clientRaw.getOutdoorHumidityTrend()).format());
                break;

            case INDOOR_CONDITIONS:
                formattedWeatherItem = new FormattedWeatherItem("indoor",
                        new TemperatureFormatter(clientRaw.getIndoorTemperature()).format(temperatureUnit) + ", " +
                                new HumidityFormatter(clientRaw.getIndoorHumidityPercentage()).format());
                break;

            case RAINFALL_RATE:
                formattedWeatherItem = new FormattedWeatherItem("rain rate",
                        new RainfallRateFormatter(clientRaw.getRainfallRatePerMinute()).format(rainfallUnit));
                break;

            case SOLAR:
                formattedWeatherItem = new FormattedWeatherItem("solar",
                        new SolarRadiationFormatter(clientRaw.getSolarRadiation()).format() + ", " +
                                new SolarPercentageFormatter(clientRaw.getSolarPercentage()).format());
                break;

            case SURFACE_PRESSURE:
                formattedWeatherItem = new FormattedWeatherItem("press",
                        new PressureFormatter(clientRaw.getSurfacePressure()).format(pressureUnit) + " " +
                                new TrendFormatter(clientRaw.getSurfacePressureTrend()).format());
                break;

            case UV_INDEX:
                formattedWeatherItem = new FormattedWeatherItem("uv index",
                        new UvIndexFormatter(clientRaw.getUvIndex()).format());
                break;

            case WIND_CHILL:
                formattedWeatherItem = new FormattedWeatherItem("wind chill",
                        new TemperatureFormatter(clientRaw.getWindChill()).format(temperatureUnit));
                break;
        }

        return formattedWeatherItem;
    }
} public enum PressureUnit {
    HECTOPASCALS("Hectopascals (hPa)"),
    INCHES_OF_MERCURY("Inches of Mercury (inHg)"),
    KILIOPASCALS("Kilopascals (kPa)"),
    MILLIBARS("Millibars (mb)"),
    MILLIMETRES_OF_MERCURY("Millimetres of Mercury (mmHg)");

    private String description;

    PressureUnit(String description) {
        this.description = description;
    }

    public String toString() {
        return description;
    }
} public enum RainfallUnit {
    INCHES("Inches (in)"),
    MILLIMETRES("Millimetres (mm)");

    private String description;

    RainfallUnit(String description) {
        this.description = description;
    }

    public String toString() {
        return description;
    }
} public enum TemperatureUnit {
    CELSIUS("Celsius (°C)"),
    FAHRENHEIT("Fahrenheit (°F)");

    private String description;

    TemperatureUnit(String description) {
        this.description = description;
    }

    public String toString() {
        return description;
    }
} public enum WindDirectionUnit {
    CARDINAL_DIRECTION("Cardinal Direction"),
    COMPASS_DEGREES("Compass Degrees");

    private String description;

    WindDirectionUnit(String description) {
        this.description = description;
    }

    public String toString() {
        return description;
    }
} public enum WindSpeedUnit {
    BEAUFORT_SCALE("Beaufort Scale (Bft)"),
    KILOMETRES_PER_HOUR("Kilometres per Hour (km/h)"),
    KNOTS("Knots (kts)"),
    METRES_PER_SECOND("Metres per Second (m/s)"),
    MILES_PER_HOUR("Miles per Hour (mph)");

    private String description;

    WindSpeedUnit(String description) {
        this.description = description;
    }

    public String toString() {
        return description;
    }
} public static final String ERROR_ACTION = "com.waynedgrant.cirrus.error"; public static final String ERROR_MESSAGE_EXTRA = "com.waynedgrant.cirrus.errorMessage"; public static final String FETCH_FRESH_CLIENT_RAW = "com.waynedgrant.cirrus.fetchFreshClientRaw";
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import android.util.Log;
import android.widget.RemoteViews;
import com.waynedgrant.cirrus.clientraw.ClientRaw;
import com.waynedgrant.cirrus.clientraw.ClientRawCache;
import com.waynedgrant.cirrus.clientraw.ClientRawRequest;
import com.waynedgrant.cirrus.clientraw.ClientRawResponse;
import com.waynedgrant.cirrus.clientraw.ClientRawUrl;
import com.waynedgrant.cirrus.clientraw.RetrieveClientRawTask;
import com.waynedgrant.cirrus.measures.Temperature;
import com.waynedgrant.cirrus.measures.Trend;
import com.waynedgrant.cirrus.measures.WeatherItem;
import com.waynedgrant.cirrus.preferences.Preferences;
import com.waynedgrant.cirrus.presentation.colorizers.TemperatureColorizer;
import com.waynedgrant.cirrus.presentation.colorizers.WeatherItemColorizer;
import com.waynedgrant.cirrus.presentation.formatters.DateFormat;
import com.waynedgrant.cirrus.presentation.formatters.DateFormatter;
import com.waynedgrant.cirrus.presentation.formatters.FormattedWeatherItem;
import com.waynedgrant.cirrus.presentation.formatters.StringFormatter;
import com.waynedgrant.cirrus.presentation.formatters.TemperatureFormatter;
import com.waynedgrant.cirrus.presentation.formatters.TimeFormat;
import com.waynedgrant.cirrus.presentation.formatters.TimeFormatter;
import com.waynedgrant.cirrus.presentation.formatters.TrendFormatter;
import com.waynedgrant.cirrus.presentation.formatters.WeatherItemFormatter;
import com.waynedgrant.cirrus.units.PressureUnit;
import com.waynedgrant.cirrus.units.RainfallUnit;
import com.waynedgrant.cirrus.units.TemperatureUnit;
import com.waynedgrant.cirrus.units.WindDirectionUnit;
import com.waynedgrant.cirrus.units.WindSpeedUnit;
import java.util.List;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE;
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static com.waynedgrant.cirrus.R.drawable.error;
import static com.waynedgrant.cirrus.R.drawable.refreshing;
import static com.waynedgrant.cirrus.R.drawable.widget_shape_opaque;
import static com.waynedgrant.cirrus.R.drawable.widget_shape_transparent;
import static com.waynedgrant.cirrus.R.id.configButton;
import static com.waynedgrant.cirrus.R.id.dailyMaxOutdoorTemperature;
import static com.waynedgrant.cirrus.R.id.dailyMinOutdoorTemperature;
import static com.waynedgrant.cirrus.R.id.mainWidgetLayout;
import static com.waynedgrant.cirrus.R.id.outdoorTemperature;
import static com.waynedgrant.cirrus.R.id.outdoorTemperatureTrend;
import static com.waynedgrant.cirrus.R.id.stationName;
import static com.waynedgrant.cirrus.R.id.statusImage;
import static com.waynedgrant.cirrus.R.id.weatherItemLabel1;
import static com.waynedgrant.cirrus.R.id.weatherItemLabel2;
import static com.waynedgrant.cirrus.R.id.weatherItemLabel3;
import static com.waynedgrant.cirrus.R.id.weatherItemLabel4;
import static com.waynedgrant.cirrus.R.id.weatherItemLabel5;
import static com.waynedgrant.cirrus.R.id.weatherItemValue1;
import static com.waynedgrant.cirrus.R.id.weatherItemValue2;
import static com.waynedgrant.cirrus.R.id.weatherItemValue3;
import static com.waynedgrant.cirrus.R.id.weatherItemValue4;
import static com.waynedgrant.cirrus.R.id.weatherItemValue5;
import static com.waynedgrant.cirrus.R.id.whenRefreshed;
import static com.waynedgrant.cirrus.R.layout.widget_layout;
import static com.waynedgrant.cirrus.WidgetProvider.ERROR_ACTION;
import static com.waynedgrant.cirrus.WidgetProvider.ERROR_MESSAGE_EXTRA;
import static com.waynedgrant.cirrus.WidgetProvider.FETCH_FRESH_CLIENT_RAW;
            updateStatusToDisplayError(appWidgetId, remoteViews, errorMessage);
        } else {
            ClientRawCache.update(appWidgetId, clientRaw);
            updateDisplay(appWidgetId, preferences, remoteViews, clientRaw);
        }

        updateBackground(appWidgetId, remoteViews, preferences);
        updateIntents(appWidgetId, remoteViews);

        pushUpdatesToWidget(appWidgetId, remoteViews);
    }

    private RemoteViews getRemoteViews() {
        return new RemoteViews(getApplicationContext().getPackageName(), widget_layout);
    }

    private void updateBackground(int appWidgetId, RemoteViews remoteViews, Preferences preferences) {
        int drawable = widget_shape_opaque;

        if (preferences.isTransparent(appWidgetId)) {
            drawable = widget_shape_transparent;
        }

        remoteViews.setInt(mainWidgetLayout, "setBackgroundResource", drawable);
    }

    private void updateStatusToDisplayRefreshing(RemoteViews remoteViews) {
        remoteViews.setImageViewResource(statusImage, refreshing);
        remoteViews.setViewVisibility(statusImage, VISIBLE);

        remoteViews.setOnClickPendingIntent(statusImage, null);
    }

    private void updateStatusToDisplayError(int appWidgetId, RemoteViews remoteViews, String errorMessage) {
        remoteViews.setImageViewResource(statusImage, error);
        remoteViews.setViewVisibility(statusImage, VISIBLE);

        Context context = getApplicationContext();
        Intent clickErrorIntent = new Intent(context, WidgetProvider.class);
        clickErrorIntent.setAction(ERROR_ACTION);
        clickErrorIntent.putExtra(EXTRA_APPWIDGET_IDS, new int[]{appWidgetId});
        clickErrorIntent.putExtra(ERROR_MESSAGE_EXTRA, errorMessage);
        clickErrorIntent.setData(Uri.withAppendedPath(Uri.parse("customuri://widget/id/"), String.valueOf(appWidgetId)));
        PendingIntent errorPendingIntent = PendingIntent.getBroadcast(context, appWidgetId, clickErrorIntent, FLAG_UPDATE_CURRENT);

        remoteViews.setOnClickPendingIntent(statusImage, errorPendingIntent);
    }

    private void updateClickRefreshIntent(int appWidgetId, RemoteViews remoteViews) {
        Context context = getApplicationContext();
        Intent clickRefreshIntent = new Intent(context, WidgetProvider.class);
        clickRefreshIntent.setAction(ACTION_APPWIDGET_UPDATE);
        clickRefreshIntent.putExtra(EXTRA_APPWIDGET_IDS, new int[]{appWidgetId});
        clickRefreshIntent.setData(Uri.withAppendedPath(Uri.parse("customuri://widget/id/"), String.valueOf(appWidgetId)));
        PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(context, appWidgetId, clickRefreshIntent, FLAG_UPDATE_CURRENT);

        remoteViews.setOnClickPendingIntent(mainWidgetLayout, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(stationName, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(dailyMaxOutdoorTemperature, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(dailyMinOutdoorTemperature, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(outdoorTemperature, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(outdoorTemperatureTrend, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(weatherItemLabel1, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(weatherItemValue1, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(weatherItemLabel2, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(weatherItemValue2, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(weatherItemLabel3, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(weatherItemValue3, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(weatherItemLabel4, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(weatherItemValue4, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(weatherItemLabel5, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(weatherItemValue5, refreshPendingIntent);
        remoteViews.setOnClickPendingIntent(whenRefreshed, refreshPendingIntent);
    }

    private void updateClickConfigIntent(int appWidgetId, RemoteViews remoteViews) {
        Context context = getApplicationContext();
        Intent clickConfigIntent = new Intent(context, WidgetConfigActivity.class);
        clickConfigIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId);
        clickConfigIntent.setData(Uri.withAppendedPath(Uri.parse("customuri://widget/id/"), String.valueOf(appWidgetId)));
        PendingIntent configPendingIntent = PendingIntent.getActivity(context, appWidgetId, clickConfigIntent, FLAG_UPDATE_CURRENT);

        remoteViews.setOnClickPendingIntent(configButton, configPendingIntent);
    }

    private void updateStationNameDisplay(int appWidgetId, RemoteViews remoteViews, Preferences preferences, ClientRaw clientRaw) {
        String stationNameValue = preferences.getStationName(appWidgetId);

        if (stationNameValue == null) {
            stationNameValue = clientRaw.getStationName();
        }

        stationNameValue = new StringFormatter(stationNameValue).format();

        remoteViews.setTextViewText(stationName, stationNameValue);
    }

    private void updateTemperatureDisplay(int appWidgetId, RemoteViews remoteViews, Preferences preferences, ClientRaw clientRaw) {
        TemperatureUnit temperatureUnit = preferences.getTemperatureUnit(appWidgetId);

        Temperature dailyMaxOutdoorTemperatureValue = clientRaw.getDailyMaxOutdoorTemperature();
        Temperature dailyMinOutdoorTemperatureValue = clientRaw.getDailyMinOutdoorTemperature();
        Temperature outdoorTemperatureValue = clientRaw.getOutdoorTemperature();
        Trend outdoorTemperatureTrendValue = clientRaw.getOutdoorTemperatureTrend();

        remoteViews.setTextViewText(dailyMaxOutdoorTemperature, new TemperatureFormatter(dailyMaxOutdoorTemperatureValue).format(temperatureUnit));
        remoteViews.setTextColor(dailyMaxOutdoorTemperature, new TemperatureColorizer(dailyMaxOutdoorTemperatureValue).colorize());

        remoteViews.setTextViewText(dailyMinOutdoorTemperature, new TemperatureFormatter(dailyMinOutdoorTemperatureValue).format(temperatureUnit));
        remoteViews.setTextColor(dailyMinOutdoorTemperature, new TemperatureColorizer(clientRaw.getDailyMinOutdoorTemperature()).colorize());

        remoteViews.setTextViewText(outdoorTemperature, new TemperatureFormatter(outdoorTemperatureValue).format(temperatureUnit));
        remoteViews.setTextColor(outdoorTemperature, new TemperatureColorizer(clientRaw.getOutdoorTemperature()).colorize());

        remoteViews.setTextViewText(outdoorTemperatureTrend, new TrendFormatter(outdoorTemperatureTrendValue).format());
    }

    private void updateWeatherItemsDisplay(int appWidgetId, RemoteViews remoteViews, Preferences preferences, ClientRaw clientRaw) {
        TemperatureUnit temperatureUnit = preferences.getTemperatureUnit(appWidgetId);
        PressureUnit pressureUnit = preferences.getPressureUnit(appWidgetId);
        WindSpeedUnit windSpeedUnit = preferences.getWindSpeedUnit(appWidgetId);