package me.despawningbone.discordbot.command.info;

import java.awt.Color;
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.TimeZone;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import me.despawningbone.discordbot.command.Command;
import me.despawningbone.discordbot.command.CommandResult;
import me.despawningbone.discordbot.command.CommandResult.CommandResultType;
import me.despawningbone.discordbot.utils.MiscUtils;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;

public class CityInfo extends Command {
	public CityInfo() {
		this.alias = Arrays.asList("ci", "weather");
		this.desc = "Search for info about a city!"; //"Search for info about the city the address is in!";
		this.usage = "<address/search words>";
		for (String country : Locale.getISOCountries()) {
            Locale locale = new Locale("en", country);
            countryCodes.put(locale.getDisplayCountry(Locale.ENGLISH), locale.getCountry());
		}
		this.examples = Arrays.asList("hong kong", "tokyo");  //"HK", "akihabara");
	} 
	
	HashMap<String, String> countryCodes = new HashMap<>();
	
	NumberFormat formatter = new DecimalFormat("#0.00");
	
	//private final String flickrAPI = DiscordBot.tokens.getProperty("flickr");
	
	@Override
	public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) {
		if(args.length < 1) {
			return new CommandResult(CommandResultType.INVALIDARGS, "Please input a city name."); //or a address.");
		 } else {
			channel.sendTyping().queue();
			String sword = String.join(" ", args);
			try {
				/*JSONTokener georesult = null;
				InputStream geostream = null;
				String search = URLEncoder.encode(sword, "UTF-8");
				URL geocode = null;
				try {
					geocode = new URL("https://maps.googleapis.com/maps/api/geocode/json?address=" + search + "&key=" + AudioPlayer.GAPI + "&language=en");
					geostream = geocode.openStream();
				} catch (IOException e) {
					e.printStackTrace();
				}
				georesult = new JSONTokener(geostream);
				JSONObject geomain = new JSONObject(georesult);
				JSONArray resultList = geomain.getJSONArray("results");
				if(resultList.isNull(0)) {
					channel.sendMessage("Unfortunately there is no results :cry:").queue();
					return new CommandResult(CommandResultType.NORESULT);
				}
				JSONObject firstResult = resultList.getJSONObject(0);
				JSONObject loc = firstResult.getJSONObject("geometry").getJSONObject("location"); 
				//String formattedAddr = firstResult.getString("formatted_address");
				JSONArray addrComponents = firstResult.getJSONArray("address_components"); 
				String formattedAddr = "", addr = firstResult.getString("formatted_address"); 
				String countryShort = null; String region = null; String locality = null; String country = null; String colarea = null;
				boolean stop = false;
				for(int i = 0; i < addrComponents.length(); i++) {
					JSONObject component = addrComponents.getJSONObject(i);
					String compname = component.getString("long_name");
					if(!stop) {
						if(i == addrComponents.length() - 1) {
							formattedAddr += compname;
						} else {
							formattedAddr += compname + ", ";	
						}	
					}
					List<Object> types = component.getJSONArray("types").toList();
					if(types.contains("country")) {
						countryShort = component.getString("short_name");
						country = compname;
						if(i == 0) {
							channel.sendMessage("You need to specify which part of the country you want to get the info from.").queue();
							return new CommandResult(CommandResultType.FAILURE, "Address is a country");
						}
					} else if(types.contains("continent")) {
						if(i == 0) {
							channel.sendMessage("You need to specify which part of the continent you want to get the info from.").queue();
							return new CommandResult(CommandResultType.FAILURE, "Address is a continent");
						}
					} else if(types.contains("postal_code")) {
						if(i == 0) {
							formattedAddr = addr;
							stop = true;
						}
					} else if(types.contains("administrative_area_level_1")) {
						region = compname; 
					} else if(types.contains("locality")) {
						locality = compname;
					} else if(types.contains("colloquial_area")) {
						colarea = compname;
					} else if(types.contains("natural_feature") && addrComponents.length() == 1) {
						channel.sendMessage("Search civilized locations please :joy:").queue();
						return new CommandResult(CommandResultType.FAILURE, "Address is natural");
					}
				}
				if(region == null) {
					if(stop) {
						region = country;
					} else {
						if(locality.equals("Singapore")) {
							formattedAddr = addr;	
						}
						region = colarea;
						if(locality != null) region = locality;
					}
				}
				double lat = loc.getDouble("lat");
				double lng = loc.getDouble("lng");
				JSONTokener timeresult = null;
				InputStream timestream = null;
				URL timezone = null;
		        Timestamp timestamp = new Timestamp(System.currentTimeMillis());
		        long sec = Math.round(timestamp.getTime() / 1000.0);
				try {  //can deprecate this since the new weather scrape has local time, but there wont be a name for the timezone anymore
					timezone = new URL("https://maps.googleapis.com/maps/api/timezone/json?location=" + lat + "," + lng + "&timestamp=" + sec + "&key=" + AudioPlayer.GAPI + "&language=en");
					timestream = timezone.openStream();
				} catch (IOException e) {
					e.printStackTrace();
				}
				timeresult = new JSONTokener(timestream);
				JSONObject timemain = new JSONObject(timeresult);
				String timeZoneName = timemain.getString("timeZoneName");
				int rawOffset = timemain.getInt("rawOffset");
				int dstOffset = timemain.getInt("dstOffset");
				ZonedDateTime zone = ZonedDateTime.now(ZoneOffset.ofTotalSeconds(rawOffset + dstOffset));
				int hours = (rawOffset + dstOffset) / 60 / 60;*/
		        
		        boolean hasWeather = true;
		        JSONObject info = null;
		        String wQualifiedName = "",  woeid = "", lng = "", lat = "", region = "", countryShort = ""; TimeZone timezone = null;
		        /*try {
		        	URLConnection con = new URL("https://api.flickr.com/services/rest/?method=flickr.places.find&api_key=" + flickrAPI + "&query=" + URLEncoder.encode(sword, "UTF-8") + "&format=json&nojsoncallback=1").openConnection();
		        	con.setRequestProperty("Accept-Language", "en");
		        	JSONArray warray = new JSONObject(new JSONTokener(con.getInputStream())).getJSONObject("places").getJSONArray("place");
		        	int index; 
		        	for(index = 0; index < warray.length(); index++) {
		        		if(warray.getJSONObject(index).has("timezone")) {
		        			break;
		        		}
		        	}
		        	JSONObject wsearch = warray.getJSONObject(index);
		        	woeid = wsearch.getString("woeid");  //flickr api, using generated api key (will it expire?)  //highest accuracy so far
		        	//wQualifiedName = wsearch.getString("_content");  //too short
		        	ArrayList<String> pSplit = new ArrayList<>(Arrays.asList(URLDecoder.decode(wsearch.getString("place_url"), "UTF-8").substring(1).split("/")));
		        	if(!pSplit.get(pSplit.size() - 1).equals(wsearch.getString("woe_name"))) {
		        		pSplit.add(wsearch.getString("woe_name"));
		        	}
		        	Collections.reverse(pSplit);
		        	wQualifiedName = String.join(", ", pSplit);
		        	timezone = TimeZone.getTimeZone(wsearch.getString("timezone"));
		        	lat = wsearch.getString("latitude");
		        	lng = wsearch.getString("longitude");
		        	String[] rSplit = wQualifiedName.split(", ");
		        	region = rSplit.length > 1 ? rSplit[rSplit.length - 2] : rSplit[0];
		        	countryShort = countryCodes.get(rSplit[rSplit.length - 1].trim());
		        } catch(IOException e) {
		        	return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
		        } catch (JSONException e) {
		        	//e.printStackTrace();
					return new CommandResult(CommandResultType.NORESULT);
		        }*/  //FLICKR DED
		        try {
		        	JSONObject wsearch = new JSONObject(new JSONTokener(new URL("https://www.yahoo.com/news/_tdnews/api/resource/WeatherSearch;text=" + URLEncoder.encode(sword, "UTF-8") + "?returnMeta=true").openStream())).getJSONArray("data").getJSONObject(0);
		        	woeid = String.valueOf(wsearch.getInt("woeid"));  //yahoo scrape
		        	lat = String.valueOf(wsearch.getDouble("lat"));
		        	lng = String.valueOf(wsearch.getDouble("lon"));
		        	wQualifiedName = wsearch.getString("qualifiedName");
		        	countryShort = countryCodes.get(wsearch.getString("country"));
		        	region = wsearch.getString("city");
		        	timezone = TimeZone.getTimeZone(new JSONObject(new JSONTokener(new URL("https://api.teleport.org/api/locations/" + lat + "," + lng + "/?embed=location:nearest-cities/location:nearest-city/city:timezone").openStream())).getJSONObject("_embedded").getJSONArray("location:nearest-cities").getJSONObject(0).getJSONObject("_embedded").getJSONObject("location:nearest-city").getJSONObject("_embedded").getJSONObject("city:timezone").getString("iana_name"));
		        	//can use metaweather, but not accurate enough
		        	//can also broaden the scope for yahoo scrape for it to work better
		        	//JSONObject wsearch = new JSONObject(new JSONTokener(new URL("https://api.flickr.com/services/rest/?method=flickr.places.findByLatLon&api_key=bdaafeafab62267931d920dda27a4f90&lat=" + lat + "&lon=" + lng + "&format=json&nojsoncallback=1").openStream())).getJSONObject("places").getJSONArray("place").getJSONObject(0);  //gonna use flickr find instead
		        	info = new JSONObject(new JSONTokener(new URL("https://www.yahoo.com/news/_tdnews/api/resource/WeatherService;woeids=[" + woeid + "]?lang=en-US&returnMeta=true").openStream())).getJSONObject("data").getJSONArray("weathers").getJSONObject(0);
		        } catch(IOException e) {
		        	e.printStackTrace();
		        } catch(JSONException e) {
		        	e.printStackTrace();
		        	return new CommandResult(CommandResultType.NORESULT);
		        	//hasWeather = false;
		        }
		        /*String ftimezone = timeZoneName + " (UTC" + (Math.signum(hours) == 1 || Math.signum(hours) == 0 ? "+" + hours : hours) + ")";
		        if(ftimezone.length() < 34) {
		        	ftimezone += String.join("", Collections.nCopies(34 - ftimezone.length(), " "));
		        }*/
		        StringBuffer unibuff = new StringBuffer();
		        if(countryShort != null) {
		        	char[] ch  = countryShort.toLowerCase().toCharArray();
			        for(char c : ch) {
			        	int temp = (int)c;
			        	int temp_integer = 96; //for lower case
			        	if(temp<=122 & temp>=97) unibuff.append(Character.toChars(127461 + (temp-temp_integer)));
			        }	
		        } else {
		        	unibuff.append("N/A");
		        }
		        Date date = new Date();
		        //System.out.println(info);
				EmbedBuilder embedmsg = new EmbedBuilder();
		        embedmsg.setAuthor("Info for " + wQualifiedName, null, null);
		        embedmsg.setColor(new Color(100, 0, 255));
		        //embedmsg.setFooter("Weather info last updated: " + info.getString("lastBuildDate") , null);
		        embedmsg.addField("Country", unibuff.toString(), true);
		        embedmsg.addField("Region", region, true);
		        embedmsg.addField("Current time", OffsetDateTime.now(timezone.toZoneId()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ssa").withLocale(Locale.ENGLISH)).trim(), true);
		        long hours = (timezone.getOffset(date.getTime())/1000/60/60);
		        embedmsg.addField("Timezone" , timezone.getDisplayName(timezone.inDaylightTime(date), TimeZone.LONG, Locale.ENGLISH) + " (UTC" + (Math.signum(hours) == 1 || Math.signum(hours) == 0 ? "+" + hours : hours) + ")" + "\u1160", true);   //FLICKR DED
		        String footer = "Weather info not available";
		        if (hasWeather) {   //use another api if weather info not available?
		        	JSONObject obs = info.getJSONObject("observation"); JSONObject temp = obs.getJSONObject("temperature");
		        	if(temp.has("now")) {
			        	embedmsg.addField("Temperature", fToC(temp.getInt("now")) + "°C (↑" + fToC(temp.getInt("high")) + "°C | ↓" + fToC(temp.getInt("low")) + "°C)", true);
				        embedmsg.addField("Humidity", obs.getInt("humidity") + "% (Chance of rain: " + obs.getInt("precipitationProbability") + "%)", true);
				        embedmsg.addField("Visibility", miToKm(obs.getDouble("visibility")) + "km (" + obs.getString("conditionDescription") + ")", true);
				        embedmsg.addField("Atmospheric pressure", formatter.format(obs.getDouble("barometricPressure") / 0.029530) + "millibars", true);
				        embedmsg.addField("Wind speed", miToKm(obs.getDouble("windSpeed")) + "km/h", true);
				        embedmsg.addField("Wind direction", obs.getInt("windDirection") + "° (" + obs.getString("windDirectionCode") + ")", true);
				        embedmsg.addField("Feels Like", fToC(temp.getInt("feelsLike")) + "°C", true);
				        embedmsg.addField("UV index", obs.getInt("uvIndex") + " (" + obs.getString("uvDescription") + ")", true);
				        embedmsg.addField("Sunrise", MiscUtils.convertMillis(info.getJSONObject("sunAndMoon").getLong("sunrise") * 1000).substring(0, 5), true);
				        embedmsg.addField("Sunset", MiscUtils.convertMillis(info.getJSONObject("sunAndMoon").getLong("sunset") * 1000).substring(0, 5), true);
				        String imgUrl = info.getJSONArray("photos").getJSONObject(0).getJSONArray("resolutions").getJSONObject(0).getString("url");  //seems to have dead urls, how fix
				        embedmsg.setThumbnail(imgUrl.split(":\\/\\/").length > 2 ? "https://" + imgUrl.split(":\\/\\/")[2] : imgUrl);
				        footer = "Weather info last updated: " + OffsetDateTime.parse(obs.getJSONObject("observationTime").getString("timestamp")).format(DateTimeFormatter.RFC_1123_DATE_TIME)
				        		.replace("GMT", timezone.getDisplayName(timezone.inDaylightTime(date), TimeZone.SHORT, Locale.ENGLISH)); //+ " | " + wQualifiedName;
				        //add weather provider to footer?
		        	}
		        }
		        embedmsg.addField("Latitude", lat, true);
		        embedmsg.addField("Longitude", lng, true);
		        embedmsg.setFooter(footer, null);
		        try {
		        	MessageEmbed fmsg = embedmsg.build();
		        	channel.sendMessage(fmsg).queue();
		        } catch(InsufficientPermissionException e2) {
		        	return new CommandResult(CommandResultType.FAILURE, "Unfortunately, the bot is missing the permission `MESSAGE_EMBED_LINKS` which is required for this command to work.");
		        }
		        return new CommandResult(CommandResultType.SUCCESS);
			} catch (Exception e) {
				return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
			}
		 }
	}
	
	private String fToC(int f) {  //fucking no metric ree
		return formatter.format((f-32)*5.0/9);
	}
	
	private String miToKm(double mile) {
		return formatter.format(mile*1.609344);
	}
}
