package main

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math"
	"os"
	"runtime"
	"strconv"
	"strings"
	"time"

	//	"io/ioutil"
	"net/http"
)

type canonicalNameJson struct {
	CanonicalName float64  `json:"canonicalName"`
}


//struct for holding the species array in country
type arraySpeciesJSON struct {
	ArryJson []speciesJSON2 `json:"results"`
}

type arraySpeciesKeyJSON struct {
	ArryJson []speciskey `json:"results"`
}
type speciskey struct {

	SpecKey				float64 `json:"speciesKey"`
}


//just a small struct used to make it easier to get year
type specisYearJson struct {

	Key float64  `json:"key"`
}

//struct for json respnce in country method
type countryJSON struct {
	Code 				string `json:"code"`
	Countryname 		string `json:"countryname"`
	CountryFlag 		string `json:"countryflag"`
	Species 			[]speciesJSON2 `json:"species"`
	SpeciesKey 			[]speciskey `json:"speciesKey"`

}

//due to stupid implementation by me i had to make an identical struct with an float responce in year, hey if it works it works
// the initial json was made when my understanding of json was poor, something that imporved over time, but i am afraid that trying to improve the json
//to the other type might break it

type speciesJSON2 struct {
	Key					float64 `json:"key"`
	Kingdom				string `json:"kingdom"`
	Phylum				string `json:"phylum"`
	Order				string `json:"order"`
	Family				string `json:"family"`
	Genus				string `json:"genus"`
	ScientificName		string `json:"scientificName"`
	CanonicalName		string `json:"canonicalName"`
	Year				float64 `json:"year"`
	SpecKey				float64 `json:"speciesKey,omitempty"` 	//used to get the specieskey to check for duplicates, will not be printed out
																//due to omiteempty
}

type speciesJSON struct {
	Key					float64 `json:"key"`
	Kingdom				string `json:"kingdom"`
	Phylum				string `json:"phylum"`
	Order				string `json:"order"`
	Family				string `json:"family"`
	Genus				string `json:"genus"`
	ScientificName		string `json:"scientificName"`
	CanonicalName		string `json:"canonicalName"`
	Year				string `json:"year"`
}

//json struct for diagnose
type diagJSON struct {
	Gbif 				int `json:"gbif"`
	Rescountries		int `json:"restcountries"`
	Version 			string `json:"version"`
	Uptime 				float64 `json:"uptime"`

}

//global variables
var(
	StartTime = time.Time{} //for uptime

	//filter for url methods
	countryFilter 	= "/conservation/v1/country/"
	specisFilter 	= "/conservation/v1/species/"
	diagFilter 		= "/conservation/v1/diag/"
)


func main() {


	//starts the uptime counter
	StartTime = time.Now()


	//to handle random / and undefined behavior
	http.HandleFunc("/", undefined)

	//handels the functions based on user input, the 2. parameter is the global string used for filtering
	http.HandleFunc(countryFilter, country)
	http.HandleFunc(specisFilter, species)
	http.HandleFunc(diagFilter, diag)

	//tries to get the port from the port number from the env file
	port := os.Getenv("PORT")

	//if it is unable to find the port variable it forces one
	if port == "" {
		port = "8080"
	}

	//opens and continus listens on said port
	log.Fatal(http.ListenAndServe(":"+port, nil))


}

func undefined(w http.ResponseWriter, r *http.Request){


	io.WriteString(w, "Hello and welcome to Benjamin Skinstad project\n" +
		"the valid url calls are  /conservation/v1/country/ /conservation/v1/species/ /conservation/v1/diag/\n" +
		"some example urls are:\n" +
		"https://sheltered-sea-90657.herokuapp.com/conservation/v1/species/6969\n" +
		"https://sheltered-sea-90657.herokuapp.com/conservation/v1/country/us&limit=100\n" +
		"https://sheltered-sea-90657.herokuapp.com/conservation/v1/diag/\n\n\n" +
		"these links will only work the heroku deployed site, but they wiil still work if you change the heroku part with localhost")
}


func country(w http.ResponseWriter, r *http.Request){

	var input = r.URL.String() //takes in an raw url string to keep the question mark


	filtered := strings.Replace(input,countryFilter, "", -1) //removes the unessesary parts of the url


	if filtered == ""{
		io.WriteString(w,"404")
	}else {

		countryCode := string(filtered[0:2]) //saves away country parameter, its always 2 letters

		filterNumber := strings.Replace(filtered, countryCode+"&limit=", "", -1) //removes more of the url to only get the filter number

		//makes it possible to not send in an limit, sets the value to 10 if no limit has been given
		//it also now doesnt accept negative numbers or letters , default is size is 10 if not proper limit has been given
		{
			testInt, _ := strconv.Atoi(filterNumber)
			if testInt <= 0 {
				filterNumber = "10"
			}
		}

		resp, err := http.Get("https://restcountries.eu/rest/v2/alpha/" + countryCode) //get full cpountry json responce
		println(resp.StatusCode)
		if resp.StatusCode == 404 {
			io.WriteString(w, "404")

		}else{
			if err != nil {
				print("error\n")
				log.Fatalln(err)

			}

			{ //
				tempLimit, _ := strconv.Atoi(filterNumber) //checks that the input number is not over 300, the api does not support 300+ in limit
				//sets it to 300 if its to big
				if tempLimit > 300 {
					filterNumber = "300"
				}
			}

			//gets the species part of the json

			respSpecis, err := http.Get("http://api.gbif.org/v1/occurrence/search?country=" + countryCode + "&" + "limit=" + filterNumber)
			println(respSpecis.StatusCode)
			if err != nil {
				print("error\n")
				log.Fatalln(err)

			}



			var spectest arraySpeciesJSON
			//	var canonicalNameTest canonicalNameJson

			//	var specKeyArray arraySpeciesKeyJSON

			err = json.NewDecoder(respSpecis.Body).Decode(&spectest) //decodes the json into spectest

			if err != nil {
				print("error\n")
				log.Fatalln(err)

			}

			var countryJsonArray []speciesJSON2
			var keyJsonArray []speciskey
			var duplicateCheck = "" //builds an string with all entires and checks for dupes after

			intFilternumber, _ := strconv.Atoi(filterNumber) //go has some werid problems with conversion of types

			for i := 0; i < intFilternumber; i++ {

				keyTest := fmt.Sprintf("%f", spectest.ArryJson[i].SpecKey) //gets the key

				if strings.Contains(duplicateCheck, keyTest) { //check if key already exists, go out of loop if it does
					continue
				}

				duplicateCheck += keyTest + ","

				key := spectest.ArryJson[i].Key
				kingdom := spectest.ArryJson[i].Kingdom
				phylum := spectest.ArryJson[i].Phylum
				order := spectest.ArryJson[i].Order
				family := spectest.ArryJson[i].Family
				genus := spectest.ArryJson[i].Genus
				scientificName := spectest.ArryJson[i].ScientificName
				canonicalName := spectest.ArryJson[i].CanonicalName
				year := spectest.ArryJson[i].Year
				specKey := spectest.ArryJson[i].SpecKey

				if canonicalName == "" {

					/*
						sometimes the json call does not contain canonicalName, and we have to run another request to gbif with the species key as paramter

					*/

					var y int = int(spectest.ArryJson[i].SpecKey)

					specKeyString := strconv.Itoa(y)
					canonicalNameResp, err := http.Get("http://api.gbif.org/v1/species/" + specKeyString + "/name")

					if err != nil {
						print("error\n")
						log.Fatalln(err)

					}

					defer canonicalNameResp.Body.Close()
					canonicalNamebody, err := ioutil.ReadAll(canonicalNameResp.Body)

					if err != nil {
						print("error\n")
						log.Fatalln(err)
					}

					var canonicalNameMap map[string]interface{}
					json.Unmarshal([]byte(canonicalNamebody), &canonicalNameMap) //again this poor implementation of json, but it works

					canonicalName = canonicalNameMap["canonicalName"].(string)

				}

				tempJson := speciesJSON2{float64(key), kingdom, phylum, order, family,
					genus, scientificName, canonicalName, year, specKey}

				tempKeyJson := speciskey{specKey}

				countryJsonArray = append(countryJsonArray, tempJson)
				keyJsonArray = append(keyJsonArray, tempKeyJson)

			}

			defer resp.Body.Close()
			body, err := ioutil.ReadAll(resp.Body)

			if err != nil {
				print("error\n")
				log.Fatalln(err)

			}

			var info map[string]interface{} //as mentioned in the start of the document this is my bad implementation of json, but since it works im afraid to fix it properly to the other
			//implementation.
			json.Unmarshal([]byte(body), &info)

			tempCode := info["alpha2Code"]
			tempCountryname := info["name"]
			tempCountryFlag := info["flag"]

			//this is so i can collapse this test /
			{
				if tempCode == nil {
					tempCode = "NA"
				}
				if tempCountryname == nil {
					tempCountryname = "NA"
				}
				if tempCountryFlag == nil {
					tempCountryFlag = "NA"
				}

			}

			createdJson := countryJSON{tempCode.(string), tempCountryname.(string), tempCountryFlag.(string),
				countryJsonArray, keyJsonArray}

			e, err := json.Marshal(createdJson)

			//	fmt.Fprint(w,(json.MarshalIndent(string(e), "", "    ")))

			w.Header().Add("content-type", "application/json")

			io.WriteString(w, string(e))
		}
	}
}

func species(w http.ResponseWriter, r *http.Request){
	//io.WriteString(w, "\nspecies\n\n")

	var input = r.URL.Path[:]
	filtred := strings.Replace(input,specisFilter, "", -1) //filter out the paramteter

	resp,err := http.Get("http://api.gbif.org/v1/species/"+filtred) //runs api call with that paramter
	if resp.StatusCode == 404 {
		io.WriteString(w,"404") //if input is invalid we do an 404 check and writes that out on the page if that is the case
	println(resp.StatusCode)
	}else {

		if err != nil {
			print("error\n")
			log.Fatalln(err)

		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)

		if err != nil {
			print("error\n")
			log.Fatalln(err)
		}

		var info map[string]interface{}		//this is an shit way of doing json, it works for easier structures but not with nested, which is the reasons i go away from it later on
		json.Unmarshal([]byte(body), &info)

		tempKey := info["key"]
		tempNameKey := info["nameKey"]
		tempKingdom := info["kingdom"]
		tempPhylum := info["phylum"]
		tempOrder := info["order"]
		tempFamily := info["family"]
		tempGenus := info["genus"]
		tempScientificName := info["scientificName"]
		tempCanonicalName := info["canonicalName"]
		var namekeyInt int = int(tempNameKey.(float64))

		//this is so i can collapse this test
		{
			if tempKey == nil {
				tempKey = "NA"
			}
			if tempKingdom == nil {
				tempKingdom = "NA"
			}
			if tempPhylum == nil {
				tempPhylum = "NA"
			}
			if tempFamily == nil {
				tempFamily = "NA"
			}
			if tempGenus == nil {
				tempGenus = "NA"
			}
			if tempScientificName == nil {
				tempScientificName = "NA"
			}

			if tempCanonicalName == nil {
				tempCanonicalName = "NA"
			}
			if namekeyInt == 0 {
				namekeyInt = 0

			}

		}


		respYear, err := http.Get("http://api.gbif.org/v1/species/" + strconv.Itoa(namekeyInt) + "/name") //uses the namekey from the species call to find year in /name
		println(respYear.StatusCode)
		if err != nil {
			print("error\n")
			log.Fatalln(err)

		}

		defer respYear.Body.Close()
		bodyYear, err := ioutil.ReadAll(respYear.Body)

		if err != nil {
			print("error\n")
			log.Fatalln(err)
		}

		var infoYear map[string]interface{}
		json.Unmarshal([]byte(bodyYear), &infoYear) //again this poor implementation of json, but it works

		tempYear := infoYear["year"]

		createdJson := speciesJSON{tempKey.(float64), tempKingdom.(string), tempPhylum.(string), tempOrder.(string), tempFamily.(string),
			tempGenus.(string), tempScientificName.(string), tempCanonicalName.(string), tempYear.(string)}


		e, err := json.Marshal(createdJson)
		w.Header().Add("content-type", "application/json")

		io.WriteString(w, string(e))
	}
}



func diag(w http.ResponseWriter, r *http.Request){

	gbifCode := 0 		//variable to hold responce code from gbif api
	rescountriesCode := 0	 //variable to hold responce code from restcountries api

	version := runtime.Version() 		//get current go version
	serverRuntime := time.Since(StartTime).Seconds() 	//global variable introdused, it starts counting when main is ran, and calculates time since that happend
	serverRuntime = math.Round(serverRuntime*100)/100	//makes it into secounds with 2 decimales, change the rounding to make it into pure secounds. i prefered it with 2 decimales


	/*
	* this test does a empty get request to the gbif api main page, and based on the responce saves the responce code
	* if the site is unreachable the status code is sat to 503 (service unaviable)
	*/
	respGbif,err := http.Get("http://api.gbif.org/")

	if respGbif == nil {
		gbifCode = 503
	}else{
		if err != nil{
			gbifCode = respGbif.StatusCode

		}else{
			gbifCode = respGbif.StatusCode
		}
	}

	/*
	* this test does a empty get request to the respRestcountries api main page, and based on the responce saves the responce code
	* if the site is unreachable the status code is sat to 503 (service unaviable)
	 */

	respRestcountries,err := http.Get("http://restcountries.eu/")

	if respRestcountries == nil {
		rescountriesCode = 503
	}else {

		if err != nil{
			rescountriesCode = respRestcountries.StatusCode
		}else{
			rescountriesCode = respRestcountries.StatusCode

		}
	}

	// creates an json bassed on the variables above
	createdJson := diagJSON{gbifCode,rescountriesCode,version,serverRuntime}



	e,err := json.Marshal(createdJson)

	if err != nil {
		print("error\n")
		log.Fatalln(err)
	}

	//sets the content type to json to make prettier output
	w.Header().Add("content-type", "application/json")

	//outputs the json to screen
	io.WriteString(w,string(e))


}





