/** Copyright (c) 2012 Sundog Software LLC. All rights reserved worldwide. */

package com.sundogsoftware.silverlining;

import android.content.res.AssetManager;

/** A simplified Java interface for the SilverLining Sky, Cloud, and Weather SDK.
 * Allows for creation of the various cloud types with the most common settings,
 * and basic configuration of the simulated time, place, and wind conditions.
 * The full SilverLining API is exposed via JNI if you need to expand upon this
 * class.
 */
public class SilverLining {

	Atmosphere mAtmosphere;

	static {
		System.loadLibrary("silverlining");
	}

	/** 
	 * Initializes SilverLining and associates it with an Android Asset Manager.
	 * This method assumes "up" is along the positive Y axis, and "right" is 
	 * along the positive X axis - modify this method if your convention is
	 * different, or if you are in a geocentric / ECEF coordinate system.
	 * @param username The registration name associated with your license, or pass
	 * any string if you have not yet purchased a license. If novalid license 
	 * info is provided, SilverLining will terminate after five minutes.
	 * @param licenseKey The registration code associated with your license, or any
	 * string if you don't yet have a license. If a valid key is not provided,
	 * SilverLining will terminate after five minutes.
	 * @param assetManager The AssetManager instance from your Activity class, 
	 * used for loading SilverLining's resources. Ensure the Resources folder
	 * from the SilverLining SDK for Android is included in your app's assets
	 * directory.
	 * @return true if SilverLining initialized successfully.
	 */
	public boolean Initialize(String username, String licenseKey, AssetManager assetManager) {
		mAtmosphere = new Atmosphere(username, licenseKey);
		SetAssetManager(assetManager, (long)Atmosphere.getCPtr(mAtmosphere));

		boolean ok = (mAtmosphere.Initialize(0, "Resources", true, null) == Atmosphere.InitializeErrors.E_NOERROR.swigValue());
		if (ok) {
			mAtmosphere.SetUpVector(0.0, 1.0, 0.0);
			mAtmosphere.SetRightVector(1.0, 0.0, 0.0);
		}

		return ok;
	}

	/** 
	 * Sets the projection and view matrices for your scene; must be called
	 * once per frame (or whenever your view changes.) These matrices are used
	 * to draw the sky and clouds consistently with the rest of your scene.
	 * @param modelView An array of 16 floats describing the 4x4 view matrix.
	 * @param projection An array of 16 floats describing the 4x4 projection
	 * matrix.
	 */
	public void SetMatrices(float[] modelView, float[] projection) {
		if (mAtmosphere == null) return;

		SWIGTYPE_p_double dProjection = SilverLiningAtmosphere.new_double_array(16);
		SWIGTYPE_p_double dModelview = SilverLiningAtmosphere.new_double_array(16);
		for (int i = 0; i < 16; i++)
		{
			SilverLiningAtmosphere.double_array_setitem(dProjection, i, projection[i]);
			SilverLiningAtmosphere.double_array_setitem(dModelview, i, modelView[i]);
		}
		mAtmosphere.SetProjectionMatrix(dProjection);
		mAtmosphere.SetCameraMatrix(dModelview);
		SilverLiningAtmosphere.delete_double_array(dProjection);
		SilverLiningAtmosphere.delete_double_array(dModelview);
	}

	/** 
	 * Sets the simulated date, which influences the positions of the sun and
	 * moon and the phase of the moon.
	 * @param year The year in YYYY format
	 * @param month The month (1-12)
	 * @param day The day of the month (1-31)
	 */
	public void SetDate(int year, int month, int day) {
		if (mAtmosphere == null) return;
		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		LocalTime time = conditions.GetTime();
		time.SetYear(year);
		time.SetMonth(month);
		time.SetDay(day);
		conditions.SetTime(time);
	}

	/** 
	 * Sets the simulated time of day. For local time, pass in the GMT offset of
	 * your time zone and ensure the location specified via SetLocation is 
	 * consistent with the time zone you've specified. Or, pass in GMT time
	 * and a GMT offset of zero to avoid the complexities of figuring out the
	 * time zone for a given location.
	 * @param hour The simulated hour of the day (0-23)
	 * @param minute The simulated minute of the hour (0-59)
	 * @param second The simulated second of the minute (0-59)
	 * @param offsetGMT The GMT offset of the local time zone. For example, 
	 * Pacific Time (Los Angeles) would be specified as -8.0.
	 * @param daylightSavingsTime If local time is specified, whether daylight
	 * savings time is being observed.
	 */
	public void SetTime(int hour, int minute, int second, float offsetGMT, boolean daylightSavingsTime) {
		if (mAtmosphere == null) return;
		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		LocalTime time = conditions.GetTime();
		time.SetHour(hour);
		time.SetMinutes(minute);
		time.SetSeconds(second);
		time.SetTimeZone(offsetGMT);
		time.SetObservingDaylightSavingsTime(daylightSavingsTime);
		conditions.SetTime(time);
	}
	
	/** 
	 * Retrieves the date and time currently being simulated.
	 * @see SetTime
	 * @see SetDate
	 * @return The LocalTime object containing the date and time.
	 */
	public LocalTime GetDateTime() {
		if (mAtmosphere == null) return null;
		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		if (conditions == null) return null;
		return conditions.GetTime();
	}

	/**
	 * Sets the simulated location, which influences the position of the 
	 * sun and moon. The altitude will influence the appearance of the sky.
	 * @param latitude The simulated latitude (-90.0 - 90.0, positive is North.)
	 * @param longitude The simualted longitude (-180.0 - 180.0, positive is East.)
	 * @param altitude The simulated altitude above mean sea level, in meters.
	 */
	public void SetLocation(float latitude, float longitude, float altitude) {
		if (mAtmosphere == null) return;
		AtmosphericConditions conditions = mAtmosphere.GetConditions();

		Location loc = new Location();
		loc.SetLatitude(latitude);
		loc.SetLongitude(longitude);
		loc.SetAltitude(altitude);
		
		conditions.SetLocation(loc);		
	}

	/** Retrieves the location currently being simulated.
	 * @see SetLocation
	 */
	public Location GetLocation() {
		if (mAtmosphere == null) return null;
		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		if (conditions == null) return null;
		return conditions.GetLocation();
	}
	
	/** 
	 * Sets the simulated wind speed and direction, will will cause the 
	 * clouds to move.
	 * @param windSpeed The simulated wind speed in meters per second.
	 * @param windDirection The direction of the wind, in degrees. 0 = North,
	 * 90 = East, 180 = South, 270 = West.
	 */
	public void SetWind(float windSpeed, float windDirection) {
		if (mAtmosphere == null) return;
		AtmosphericConditions conditions = mAtmosphere.GetConditions();

		WindVolume wind = new WindVolume();
		wind.SetWindSpeed(windSpeed);
		wind.SetDirection(windDirection);

		conditions.ClearWindVolumes();
		conditions.SetWind(wind);
	}

	/**
	 * Adds a layer of cumulus congestus clouds (large volumetric, puffy clouds) to
	 * the scene. Must be called after Initialize().
	 * @param altitude The altitude in MSL of the bottom of the cloud layer, in meters.
	 * @param size The width and length of the cloud layer, in meters.
	 * @param coverage The cloud coverage of the layer, from 0 to 1.0.
	 * @return A CloudLayer object used to represent this layer.
	 */
	public CloudLayer AddCumulusCongestusLayer(float altitude, float size, float coverage) {
		if (mAtmosphere == null) return null;

		CloudLayer layer = CloudLayerFactory.Create(CloudTypes.CUMULUS_CONGESTUS);
		if (layer == null) return null;

		layer.SetDensity(coverage);
		layer.SetBaseWidth(size);
		layer.SetBaseLength(size);
		layer.SetIsInfinite(true);
		layer.SetBaseAltitude(altitude);
		layer.SetFadeTowardEdges(true);
		layer.SetAlpha(0.8);
		layer.SeedClouds(mAtmosphere);

		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		if (conditions == null) return null;

		conditions.AddCloudLayer(layer);
		
		return layer;
	}

	/**
	 * Adds a layer of cumulus mediocris clouds (small volumetric, puffy clouds) to
	 * the scene. Must be called after Initialize().
	 * @param altitude The altitude in MSL of the bottom of the cloud layer, in meters.
	 * @param size The width and length of the cloud layer, in meters.
	 * @param coverage The cloud coverage of the layer, from 0 to 1.0.
	 * @return A CloudLayer object used to represent this layer.
	 */
	public CloudLayer AddCumulusMediocrisLayer(float altitude, float size, float coverage) {
		if (mAtmosphere == null) return null;

		CloudLayer layer = CloudLayerFactory.Create(CloudTypes.CUMULUS_MEDIOCRIS);
		if (layer == null) return null;

		layer.SetDensity(coverage);
		layer.SetBaseWidth(size);
		layer.SetBaseLength(size);
		layer.SetIsInfinite(true);
		layer.SetBaseAltitude(altitude);
		layer.SetFadeTowardEdges(true);
		layer.SeedClouds(mAtmosphere);

		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		if (conditions == null) return null;

		conditions.AddCloudLayer(layer);
		
		return layer;
	}

	/** Adds a cirrus cloud layer to the scene (high, wispy, planar clouds.)
	 * Must be called after Initialize().
	 * @param altitude The altitude of the cloud layer in meters.
	 * @param size The width and length of the cloud layer, in meters.
	 * @return A CloudLayer object representing this cloud layer.
	 */
	public CloudLayer AddCirrusLayer(float altitude, float size) {
		if (mAtmosphere == null) return null;

		CloudLayer layer = CloudLayerFactory.Create(CloudTypes.CIRRUS_FIBRATUS);
		if (layer == null) return null;

		layer.SetBaseWidth(size);
		layer.SetBaseLength(size);
		layer.SetIsInfinite(true);
		layer.SetThickness(0);
		layer.SetLayerPosition(0, 0);
		layer.SetBaseAltitude(altitude);
		layer.SeedClouds(mAtmosphere);
		layer.SetAlpha(0.5);

		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		if (conditions == null) return null;

		conditions.AddCloudLayer(layer);
		
		return layer;
	}

	/** Adds a cirrus cloud layer to the scene (high, lumpy planar clouds.)
	 * Must be called after Initialize().
	 * @param altitude The altitude of the cloud layer in meters.
	 * @param size The width and length of the cloud layer, in meters.
	 * @return A CloudLayer object representing this cloud layer.
	 */
	public CloudLayer AddCirroCumulusLayer(float altitude, float size) {
		if (mAtmosphere == null) return null;

		CloudLayer layer = CloudLayerFactory.Create(CloudTypes.CIRROCUMULUS);
		if (layer == null) return null;

		layer.SetBaseWidth(size);
		layer.SetBaseLength(size);
		layer.SetIsInfinite(true);
		layer.SetBaseAltitude(altitude);
		layer.SetThickness(0);
		layer.SetLayerPosition(0, 0);
		layer.SeedClouds(mAtmosphere);

		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		if (conditions == null) return null;

		conditions.AddCloudLayer(layer);
		
		return layer;
	}

	/**
	 * Adds a cumulonimbus thunderhead to the scene. Must be called after
	 * Initialize().
	 * @param altitude The height of the base of the cloud, in meters.
	 * @param xPos The east/west position of the cloud center.
	 * @param zPos The north/south position of the cloud center.
	 * @param size The length and width of the cumulonimbus cloud, in meters.
	 */
	public CloudLayer AddCumulonimbusCloud(float altitude, float xPos, float zPos, float size) {
		if (mAtmosphere == null) return null;

		CloudLayer layer = CloudLayerFactory.Create(CloudTypes.CUMULONIMBUS_CAPPILATUS);
		if (layer == null) return null;

		layer.SetBaseWidth(size);
		layer.SetBaseLength(size);
		layer.SetIsInfinite(false);
		layer.SetBaseAltitude(altitude);
		layer.SetLayerPosition(xPos, zPos);
		layer.SeedClouds(mAtmosphere);

		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		if (conditions == null) return null;

		conditions.AddCloudLayer(layer);
		
		return layer;
	}

	/**
	 * Adds a stratus cloud layer (low, thick clouds) to the scene. Must be called
	 * after Initialize().
	 * @param altitude The altitude of the base of the cloud, in meters.
	 * @param coverage The percent of the sky covered by the cloud (0.0 - 1.0.)
	 * Specify 1.0 for dark, rainy conditions.
	 * @param thickness The thickness of the cloud layer, in meters.
	 * @return A CloudLayer object describing this cloud.
	 */
	public CloudLayer AddStratusLayer(float altitude, float coverage, float thickness) {
		if (mAtmosphere == null) return null;

		CloudLayer layer = CloudLayerFactory.Create(CloudTypes.STRATUS);
		if (layer == null) return null;

		layer.SetBaseWidth(100000.0);
		layer.SetBaseLength(100000.0);
		layer.SetIsInfinite(true);
		layer.SetBaseAltitude(altitude);
		layer.SetLayerPosition(0, 0);
		layer.SetThickness(thickness);
		layer.SetDensity(coverage);
		layer.SeedClouds(mAtmosphere);

		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		if (conditions == null) return null;

		conditions.AddCloudLayer(layer);
		
		return layer;
	}

	/** Adds rain to the scene.
	 * @param mmPerHour The rate of precipitation in millimeters per hour. (0.0 - 20.0)
	 */
	public void SetRain(float mmPerHour) {
		if (mAtmosphere == null) return;

		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		conditions.SetPrecipitation(CloudLayer.PrecipitationTypes.RAIN.swigValue(), mmPerHour);
	}
	
	/** Adds sleet to the scene.
	 * @param mmPerHour The rate of precipitation in millimeters per hour. (0.0 - 20.0)
	 */
	public void SetSleet(float mmPerHour) {
		if (mAtmosphere == null) return;

		AtmosphericConditions conditions = mAtmosphere.GetConditions();
		conditions.SetPrecipitation(CloudLayer.PrecipitationTypes.SLEET.swigValue(), mmPerHour);
	}

	/** Adds snow to the scene.
	 * @param mmPerHour The rate of precipitation in millimeters per hour. (0.0 - 20.0)
	 * @param wetSnow Whether the snow is 'wet', which influences the rate at which
	 * it falls.
	 */
	public void SetSnow(float mmPerHour, boolean wetSnow) {
		if (mAtmosphere == null) return;

		AtmosphericConditions conditions = mAtmosphere.GetConditions();

		if (wetSnow) {
			conditions.SetPrecipitation(CloudLayer.PrecipitationTypes.WET_SNOW.swigValue(), mmPerHour);
		} else {
			conditions.SetPrecipitation(CloudLayer.PrecipitationTypes.DRY_SNOW.swigValue(), mmPerHour);
		}
	}
	
	/**
	 * Draws the sky box and clears the depth buffer. Should be called once
	 * per frame. Requires Initialize() was called successfully.
	 */
	public void DrawSky() {
		if (mAtmosphere != null) {
			mAtmosphere.DrawSky(true);
		}
	}

	/**
	 * Draws the clouds and effects. Should be called once per frame, after
	 * your scene's objects have been drawn. Requires Initialize() was called
	 * successfully.
	 */
	public void DrawClouds() {
		if (mAtmosphere != null) {
			mAtmosphere.DrawObjects(true);
		}
	}

	public static native boolean SetAssetManager(AssetManager manager, long atmosphere);
}
