package vandy.mooc.tests;

import android.test.AndroidTestCase;
import junit.framework.Assert;
import vandy.mooc.model.aidl.WeatherData;
import vandy.mooc.model.aidl.WeatherDataJsonParser;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;

/**
 * Test case for testing the WeatherDataJsonParser class methods.
 * The data member validation is only performed on the subset of
 * data members that the skeleton application actually uses. For
 * clarity, all data members required by the skeleton are marked
 * with a "required" comment.
 * <p/>
 * These tests have broken the data into strings that could be
 * used to test each of the individual methods in the parser.
 * However, student implementations may vary in terms of the
 * state of the JsonReader that is passed to each of these
 * functions which will likely make function specific testing
 * fail. For example, some students may decide to call
 * reader.beginArray() and reader.endArray() outside
 * the scope of an array parsing function, while others may
 * call these functions inside the function.
 * <p/>
 * NOTE: If any student implementation uses more than this the
 * minimum number of data members required by the skeleton, these
 * values will not be tested by this test case.
 */
public class TestWeatherDataJsonParser extends AndroidTestCase {

    /**
     * Sys data
     */
    public static final String mSysData =
            "\"sys\": {"
                    + "\"message\": 0.0138,"
                    + "\"country\": \"United States of America\"," /* required */
                    + "\"sunrise\": 1431427373," /* required */
                    + "\"sunset\": 1431477841" /* required */
                    + "}";

    /**
     * Weathers data (only 1 list entry).
     */
    public static final String mWeathersData =
            "\"weather\": [{"
                    + "\"id\": 802," /* required */
                    + "\"main\": \"Clouds\"," /* parcelized but not used */
                    + "\"description\": \"scattered clouds\"," /* required */
                    + "\"icon\": \"03d\"" /* parcelized but not used */
                    + "}]";

    /**
     * Main data.
     */
    public static final String mMainData =
            "\"main\": {"
                    + "\"temp\": 289.847," /* required */
                    + "\"temp_min\": 289.847,"
                    + "\"temp_max\": 289.847,"
                    + "\"pressure\": 1010.71," /* parcelized but not used */
                    + "\"sea_level\": 1035.76,"
                    + "\"grnd_level\": 1010.71,"
                    + "\"humidity\": 76" + "}"; /* required */

    /**
     * Wind data.
     */
    public static final String mWindData =
            "\"wind\": {"
                    + "\"speed\": 2.42," /* required */
                    + "\"deg\": 310.002" /* required */
                    + "}";

    /*
     * Weather Data (with string replacement stubs for subclasses).
     */
    public static final String mData = "{"
            + "\"coord\": { \"lon\": -86.78, \"lat\": 36.17 },"
            + "_SYS_DATA" + ","
            + "_WEATHER_DATA" + ","
            + "\"base\": \"stations\","
            + "_MAIN_DATA" + ","
            + "_WIND_DATA" + ","
            + "\"clouds\": {" + "\"all\": 36" + "},"
            + "\"dt\": 1431435983," /* parcelized but not used */
            + "\"id\": 4644585,"
            + "\"name\": \"Nashville\"," /* required */
            + "\"cod\": 200" /* parcelized but not used */
            + "}";

    /*
     * Weather Data error response.
     */
    public static final String mErrorData = "{"
            + "\"cod\": 404," /* required */
            + "\"message\": \"Error: Not found city\"" /* required */
            + "}";

    /**
     * Test case runner for testing the
     * WeatherDataJsonParser class methods.
     *
     * @throws Throwable
     */
    public void testParseJSONStream() throws Throwable {
        // Build the input string to mock the JSON output
        // from openweathermap.org.
        String data = buildJSONData();

        // Convert JSON string to an InputStream.
        InputStream inputStream = new ByteArrayInputStream(data.getBytes());

        // Test the WeatherDataJsonParser main entry point.
        WeatherDataJsonParser parser = new WeatherDataJsonParser();

        try {
            // Test the parser.
            List<WeatherData> weatherDataList = parser.parseJsonStream(inputStream);

            // Now verify the results from the parser.
            validateWeatherDataList(weatherDataList);
        } catch (Exception e) {
            fail("Unable to parse weather data: " + e);
        }
    }

    public void testParseJsonErrorStream() throws Throwable {
        // Build the input string to mock the Json output
        // from openweathermap.org.
        String data = buildJsonErrorData();

        // Convert Json string to an InputStream.
        InputStream inputStream = new ByteArrayInputStream(data.getBytes());

        // Test the WeatherDataJsonParser main entry point.
        WeatherDataJsonParser parser = new WeatherDataJsonParser();

        try {
            // Test the parser.
            List<WeatherData> weatherDataList = parser.parseJsonStream(inputStream);

            // Now verify the results from the parser.
            validateErrorData(weatherDataList);
        } catch (Exception e) {
            fail("Unable to parse weather data: " + e);
        }
    }

    public void validateWeatherDataList(List<WeatherData> weatherDataList) throws Throwable {
        Assert.assertNotNull(weatherDataList != null);
        Assert.assertNotNull(weatherDataList.size() == 1);

        WeatherData weatherData = weatherDataList.get(0);
        Assert.assertNotNull(weatherData); // Probably not necessary.

        // Validate weatherData members.
        validateData(weatherData);

        // Validate sys data values.
        validateSys(weatherData);

        // Validate weathers data values.
        validateWeathers(weatherData);

        // Validate main data values.
        validateMain(weatherData);

        // Validate wind data values.
        validateWind(weatherData);
    }

    public void validateErrorData(List<WeatherData> weatherDataList) {
        Assert.assertNotNull(weatherDataList != null);
        Assert.assertNotNull(weatherDataList.size() == 1);

        WeatherData weatherData = weatherDataList.get(0);
        Assert.assertNotNull(weatherData);

        Assert.assertEquals("getCod() is invalid",
                weatherData.getCod(), 404);
        Assert.assertEquals("getMessage() is invalid",
                weatherData.getMessage(), "Error: Not found city");
    }

    public void validateData(WeatherData weatherData) {
        Assert.assertNotNull(weatherData);
        Assert.assertEquals("getName() is invalid",
                weatherData.getName(), "Nashville");
    }

    public void validateWeathers(WeatherData weatherData) {
        Assert.assertNotNull(weatherData.getWeathers());
        Assert.assertEquals("Expected 1 weathers array object",
                1, weatherData.getWeathers().size());

        Assert.assertEquals("getWeathers().get(0).getDescription() value is invalid",
                weatherData.getWeathers().get(0).getDescription(), "scattered clouds");
        Assert.assertEquals("getWeathers().get(0).getId() value is invalid",
                weatherData.getWeathers().get(0).getId(), 802);
    }

    public void validateSys(WeatherData weatherData) {
        Assert.assertNotNull(weatherData.getSys());
        Assert.assertEquals("getSys().getCountry() is invalid",
                weatherData.getSys().getCountry(), "United States of America");
        Assert.assertEquals("getSys().getSunrise() is invalid",
                weatherData.getSys().getSunrise(), 1431427373);
        Assert.assertEquals("getSys().getSunset() is invalid",
                weatherData.getSys().getSunset(), 1431477841);
    }

    public void validateMain(WeatherData weatherData) {
        Assert.assertNotNull(weatherData.getMain());

        Assert.assertEquals("getSys().getHumidity() is invalid",
                weatherData.getMain().getHumidity(), 76);
        Assert.assertEquals("getSys().getTemp() is invalid",
                weatherData.getMain().getTemp(), 289.847);
    }

    public void validateWind(WeatherData weatherData) {
        Assert.assertNotNull(weatherData.getWind());

        Assert.assertEquals("getWind().getDeg() is invalid",
                weatherData.getWind().getDeg(), 310.002);
        Assert.assertEquals("getWind().getSpeed() is invalid",
                weatherData.getWind().getSpeed(), 2.42);
    }

    private String buildJSONData() {
        String data = new String(mData);

        data = data.replace("_SYS_DATA", mSysData);
        data = data.replace("_MAIN_DATA", mMainData);
        data = data.replace("_WEATHER_DATA", mWeathersData);
        data = data.replace("_WIND_DATA", mWindData);

        return data;
    }

    private String buildJsonErrorData() {
        return mErrorData;
    }
}
