package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"sort"
	"strconv"
	"time"

	"gopkg.in/mgo.v2/bson"

	"github.com/gorilla/mux"
)

type Routes []Route

var routes = Routes{
	Route{
		"Index",
		"GET",
		"/",
		Index,
	},
	Route{
		"Stats",
		"Get",
		"/stats",
		Stats,
	},
	Route{
		"AllAddresses",
		"Get",
		"/alladdresses",
		AllAddresses,
	},
	Route{
		"Blocks",
		"Get",
		"/blocks/{page}/{count}",
		Blocks,
	},
	Route{
		"Txs",
		"Get",
		"/txs/{page}/{count}",
		Txs,
	},
	Route{
		"Addresses",
		"Get",
		"/addresses/{page}/{count}",
		Addresses,
	},
	Route{
		"Block",
		"GET",
		"/block/{height}",
		Block,
	},
	Route{
		"Tx",
		"GET",
		"/tx/{txid}",
		Tx,
	},
	Route{
		"Address",
		"GET",
		"/address/{hash}",
		Address,
	},
	Route{
		"AddressNetwork",
		"GET",
		"/address/network/{address}",
		AddressNetwork,
	},
	Route{
		"BlockSankey",
		"GET",
		"/block/{height}/sankey",
		BlockSankey,
	},
	Route{
		"TxSankey",
		"GET",
		"/tx/{txid}/sankey",
		TxSankey,
	},
	Route{
		"AddressSankey",
		"GET",
		"/address/sankey/{address}",
		AddressSankey,
	},
	Route{
		"NetworkMap",
		"Get",
		"/map",
		NetworkMap,
	},
	Route{
		"GetTotalHourAnalysis",
		"Get",
		"/totalhouranalysis",
		GetTotalHourAnalysis,
	},
	Route{
		"GetLast24HourAnalysis",
		"Get",
		"/last24hour",
		GetLast24HourAnalysis,
	},
	Route{
		"GetLast7DayAnalysis",
		"Get",
		"/last7day",
		GetLast7DayAnalysis,
	},
	Route{
		"GetLast7DayHourAnalysis",
		"Get",
		"/last7dayhour",
		GetLast7DayHourAnalysis,
	},
	Route{
		"GetLastMonthsAnalysis",
		"Get",
		"/lastmonths/{count}",
		GetLastMonthsAnalysis,
	},
	Route{
		"GetAddressTimeChart",
		"GET",
		"/addresstimechart/{hash}",
		GetAddressTimeChart,
	},
}

//ROUTES

func Index(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "ask for recommendations in /r")
	//http.FileServer(http.Dir("./web"))
}

/*
func NewUser(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)
	decoder := json.NewDecoder(r.Body)
	var newUser UserModel
	err := decoder.Decode(&newUser)
	check(err)
	defer r.Body.Close()

	saveUser(userCollection, newUser)

	fmt.Println(newUser)
	fmt.Fprintln(w, "new user added: ", newUser.ID)
}
*/
func Stats(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)

	stats := getStats()

	jsonResp, err := json.Marshal(stats)
	check(err)

	fmt.Fprintln(w, string(jsonResp))
}
func AllAddresses(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)

	nodes := []NodeModel{}
	iter := nodeCollection.Find(bson.M{"type": "address"}).Limit(10000).Iter()
	err := iter.All(&nodes)

	//convert []resp struct to json
	jsonNodes, err := json.Marshal(nodes)
	check(err)

	fmt.Fprintln(w, string(jsonNodes))
}
func Blocks(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)
	vars := mux.Vars(r)
	page, err := strconv.Atoi(vars["page"])
	check(err)
	count, err := strconv.Atoi(vars["count"])
	check(err)

	blocks := []BlockModel{}
	err = blockCollection.Find(bson.M{}).Skip((page - 1) * 20).Limit(count).Sort("-$natural").All(&blocks)
	check(err)

	for _, block := range blocks {
		//blockheight := strconv.FormatInt(block.Height, 10)
		blockheight := block.Height
		txs := []TxModel{}
		err = txCollection.Find(bson.M{"blockheight": blockheight}).All(&txs)
		block.Txs = txs
	}

	//convert []resp struct to json
	jsonData, err := json.Marshal(blocks)
	check(err)

	fmt.Fprintln(w, string(jsonData))
}
func Txs(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)
	vars := mux.Vars(r)
	page, err := strconv.Atoi(vars["page"])
	check(err)
	count, err := strconv.Atoi(vars["count"])
	check(err)

	txs := []TxModel{}
	err = txCollection.Find(bson.M{}).Skip((page - 1) * 20).Limit(count).Sort("-$natural").All(&txs)
	check(err)

	//convert []resp struct to json
	jsonData, err := json.Marshal(txs)
	check(err)

	fmt.Fprintln(w, string(jsonData))
}
func Addresses(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)
	vars := mux.Vars(r)
	page, err := strconv.Atoi(vars["page"])
	check(err)
	count, err := strconv.Atoi(vars["count"])
	check(err)

	addresses := []AddressModel{}
	err = addressCollection.Find(bson.M{}).Skip((page - 1) * 20).Limit(count).Sort("-$natural").All(&addresses)
	check(err)

	//convert []resp struct to json
	jsonResp, err := json.Marshal(addresses)
	check(err)

	fmt.Fprintln(w, string(jsonResp))
}
func Block(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)

	vars := mux.Vars(r)
	var heightString string
	heightString = vars["height"]
	height, err := strconv.ParseInt(heightString, 10, 64)
	if err != nil {
		fmt.Fprintln(w, "not valid height")
	} else {
		block := BlockModel{}
		err := blockCollection.Find(bson.M{"height": height}).One(&block)

		txs := []TxModel{}
		err = txCollection.Find(bson.M{"blockheight": heightString}).All(&txs)
		block.Txs = txs

		//convert []resp struct to json
		jsonResp, err := json.Marshal(block)
		check(err)

		fmt.Fprintln(w, string(jsonResp))
	}
}
func Tx(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)

	vars := mux.Vars(r)
	txid := vars["txid"]
	if txid == "undefined" {
		fmt.Fprintln(w, "not valid txid")
	} else {
		tx := TxModel{}
		err := txCollection.Find(bson.M{"txid": txid}).One(&tx)

		//convert []resp struct to json
		jsonResp, err := json.Marshal(tx)
		check(err)

		fmt.Fprintln(w, string(jsonResp))
	}
}
func Address(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)

	vars := mux.Vars(r)
	hash := vars["hash"]
	if hash == "undefined" {
		fmt.Fprintln(w, "not valid hash")
	} else {
		address := AddressModel{}
		err := addressCollection.Find(bson.M{"hash": hash}).One(&address)

		txs := []TxModel{}
		err = txCollection.Find(bson.M{"$or": []bson.M{bson.M{"vin.address": hash}, bson.M{"vout.address": hash}}}).All(&txs)
		address.Txs = txs

		for _, tx := range address.Txs {
			blocks := []BlockModel{}
			err = blockCollection.Find(bson.M{"hash": tx.BlockHash}).All(&blocks)
			for _, block := range blocks {
				address.Blocks = append(address.Blocks, block)
			}
		}

		//convert []resp struct to json
		jsonResp, err := json.Marshal(address)
		check(err)

		fmt.Fprintln(w, string(jsonResp))
	}
}
func AddressNetwork(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)

	vars := mux.Vars(r)
	address := vars["address"]
	if address == "undefined" {
		fmt.Fprintln(w, "not valid address")
	} else {
		network := addressTree(address)
		network.Nodes[0].Shape = "triangle"

		//convert []resp struct to json
		jNetwork, err := json.Marshal(network)
		check(err)

		fmt.Fprintln(w, string(jNetwork))
	}
}
func BlockSankey(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)
	vars := mux.Vars(r)
	var heightString string
	heightString = vars["height"]
	height, err := strconv.ParseInt(heightString, 10, 64)
	if err != nil {
		fmt.Fprintln(w, "not valid height")
	} else {
		block := BlockModel{}
		err := blockCollection.Find(bson.M{"height": height}).One(&block)

		txs := []TxModel{}
		err = txCollection.Find(bson.M{"blockheight": heightString}).All(&txs)
		block.Txs = txs

		var nodesCount int
		mapNodeK := make(map[string]int)
		var sankey SankeyModel
		for _, tx := range block.Txs {
			var sankeyNodeA SankeyNodeModel
			sankeyNodeA.Node = nodesCount
			mapNodeK["tx"] = nodesCount
			nodesCount++
			sankeyNodeA.Name = "tx"
			sankey.Nodes = append(sankey.Nodes, sankeyNodeA)

			for _, vin := range tx.Vin {
				var sankeyNode SankeyNodeModel
				sankeyNode.Node = nodesCount
				mapNodeK[vin.Address] = nodesCount
				nodesCount++
				sankeyNode.Name = vin.Address
				sankey.Nodes = append(sankey.Nodes, sankeyNode)

				var sankeyLink SankeyLinkModel
				sankeyLink.Source = mapNodeK[vin.Address]
				sankeyLink.Target = mapNodeK["tx"]
				sankeyLink.Value = vin.Amount
				fmt.Println(sankeyLink)
				sankey.Links = append(sankey.Links, sankeyLink)
				fmt.Println(sankey.Links)
			}

			for _, vout := range tx.Vout {
				var sankeyNode SankeyNodeModel
				sankeyNode.Node = nodesCount
				mapNodeK[vout.Address] = nodesCount
				nodesCount++
				sankeyNode.Name = vout.Address
				sankey.Nodes = append(sankey.Nodes, sankeyNode)

				var sankeyLink SankeyLinkModel
				sankeyLink.Source = mapNodeK["tx"]
				sankeyLink.Target = mapNodeK[vout.Address]
				sankeyLink.Value = vout.Value
				fmt.Println(sankeyLink)
				sankey.Links = append(sankey.Links, sankeyLink)
			}

		}

		fmt.Println("Sankey generated")
		fmt.Println(sankey)

		//convert []resp struct to json
		jsonSankey, err := json.Marshal(sankey)
		check(err)

		fmt.Fprintln(w, string(jsonSankey))
	}
}
func TxSankey(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)
	vars := mux.Vars(r)
	txid := vars["txid"]
	if txid == "undefined" {
		fmt.Fprintln(w, "not valid height")
	} else {

		tx := TxModel{}
		err := txCollection.Find(bson.M{"txid": txid}).One(&tx)

		var nodesCount int
		mapNodeK := make(map[string]int)
		var sankey SankeyModel
		var sankeyNodeA SankeyNodeModel
		sankeyNodeA.Node = nodesCount
		mapNodeK["tx"] = nodesCount
		nodesCount++
		sankeyNodeA.Name = "tx"
		sankey.Nodes = append(sankey.Nodes, sankeyNodeA)

		fmt.Println(tx.Vin)
		for _, vin := range tx.Vin {
			var sankeyNode SankeyNodeModel
			sankeyNode.Node = nodesCount
			mapNodeK[vin.Address] = nodesCount
			nodesCount++
			sankeyNode.Name = vin.Address
			sankey.Nodes = append(sankey.Nodes, sankeyNode)

			var sankeyLink SankeyLinkModel
			sankeyLink.Source = mapNodeK[vin.Address]
			sankeyLink.Target = mapNodeK["tx"]
			sankeyLink.Value = vin.Amount
			sankey.Links = append(sankey.Links, sankeyLink)
		}

		for _, vout := range tx.Vout {
			var sankeyNode SankeyNodeModel
			sankeyNode.Node = nodesCount
			mapNodeK[vout.Address] = nodesCount
			nodesCount++
			sankeyNode.Name = vout.Address
			sankey.Nodes = append(sankey.Nodes, sankeyNode)

			var sankeyLink SankeyLinkModel
			sankeyLink.Source = mapNodeK["tx"]
			sankeyLink.Target = mapNodeK[vout.Address]
			sankeyLink.Value = vout.Value
			sankey.Links = append(sankey.Links, sankeyLink)
		}

		fmt.Println("Sankey generated")

		//convert []resp struct to json
		jsonSankey, err := json.Marshal(sankey)
		check(err)

		fmt.Fprintln(w, string(jsonSankey))
	}
}
func AddressSankey(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)
	vars := mux.Vars(r)
	address := vars["address"]
	if address == "undefined" {
		fmt.Fprintln(w, "not valid address")
	} else {
		network := addressTree(address)
		var sankey SankeyModel

		fmt.Println("network generated")
		mapNodeK := make(map[string]int)
		for k, n := range network.Nodes {
			var sankeyNode SankeyNodeModel
			//sankeyNode.StringNode = n.Id
			sankeyNode.Node = k
			sankeyNode.Name = n.Id
			sankey.Nodes = append(sankey.Nodes, sankeyNode)
			mapNodeK[n.Id] = k
		}
		for _, e := range network.Edges {
			var sankeyLink SankeyLinkModel
			//sankeyLink.StringSource = e.From
			sankeyLink.Source = mapNodeK[e.From]
			//sankeyLink.StringTarget = e.To
			sankeyLink.Target = mapNodeK[e.To]
			sankeyLink.Value = e.Label
			sankey.Links = append(sankey.Links, sankeyLink)
		}
		fmt.Println("Sankey generated")

		//convert []resp struct to json
		jsonSankey, err := json.Marshal(sankey)
		check(err)

		fmt.Fprintln(w, string(jsonSankey))
	}
}
func NetworkMap(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)

	nodes, err := getAllNodes()
	check(err)
	edges, err := getAllEdges()
	check(err)

	var network NetworkModel
	network.Nodes = nodes
	network.Edges = edges

	//convert []resp struct to json
	jNetwork, err := json.Marshal(network)
	check(err)

	fmt.Fprintln(w, string(jNetwork))
}

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

	hourAnalysis := []ChartCountModel{}
	iter := hourCountCollection.Find(bson.M{}).Limit(10000).Iter()
	err := iter.All(&hourAnalysis)

	//sort by hour
	sort.Slice(hourAnalysis, func(i, j int) bool {
		return hourAnalysis[i].Elem < hourAnalysis[j].Elem
	})

	var resp ChartAnalysisResp
	for _, d := range hourAnalysis {
		resp.Labels = append(resp.Labels, strconv.Itoa(d.Elem))
		resp.Data = append(resp.Data, d.Count)
	}

	//convert []resp struct to json
	jsonResp, err := json.Marshal(resp)
	check(err)
	fmt.Fprintln(w, string(jsonResp))
}

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

	fromDate := time.Now().AddDate(0, 0, -1)
	toDate := time.Now()

	txs := []TxModel{}
	err := txCollection.Find(bson.M{
		"datet": bson.M{
			"$gt": fromDate,
			"$lt": toDate,
		},
	}).Sort("-$natural").All(&txs)
	check(err)

	//generate map with 24 hours
	hourFrequencies := map24hours()
	for _, tx := range txs {
		hourFrequencies[tx.Date.Hour]++
	}
	var hourCount []ChartCountModel
	for hour, frequency := range hourFrequencies {
		hourCount = append(hourCount, ChartCountModel{hour, frequency})
	}
	//sort by hour
	sort.Slice(hourCount, func(i, j int) bool {
		return hourCount[i].Elem < hourCount[j].Elem
	})

	var resp ChartAnalysisResp
	for _, d := range hourCount {
		resp.Labels = append(resp.Labels, strconv.Itoa(d.Elem))
		resp.Data = append(resp.Data, d.Count)
	}

	//convert []resp struct to json
	jsonResp, err := json.Marshal(resp)
	check(err)
	fmt.Fprintln(w, string(jsonResp))
}
func GetLast7DayAnalysis(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)

	fromDate := time.Now().AddDate(0, 0, -7)
	toDate := time.Now()

	txs := []TxModel{}
	err := txCollection.Find(bson.M{
		"datet": bson.M{
			"$gt": fromDate,
			"$lt": toDate,
		},
	}).Sort("-$natural").All(&txs)
	check(err)

	//generate map with 24 hours
	//hourFrequencies := map24hours()
	dayFrequencies := make(map[int]int)
	for _, tx := range txs {
		dayFrequencies[tx.Date.Day]++
	}
	var dayCount []ChartCountModel
	for day, frequency := range dayFrequencies {
		dayCount = append(dayCount, ChartCountModel{day, frequency})
	}
	//sort by hour
	sort.Slice(dayCount, func(i, j int) bool {
		return dayCount[i].Elem < dayCount[j].Elem
	})

	var resp ChartAnalysisResp
	for _, d := range dayCount {
		resp.Labels = append(resp.Labels, strconv.Itoa(d.Elem))
		resp.Data = append(resp.Data, d.Count)
	}

	//convert []resp struct to json
	jsonResp, err := json.Marshal(resp)
	check(err)
	fmt.Fprintln(w, string(jsonResp))
}

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

	var resp ChartSeriesAnalysisResp

	for i := 0; i < 7; i++ {
		fromDate := time.Now().AddDate(0, 0, -i-1)
		toDate := time.Now().AddDate(0, 0, -i)
		txs := []TxModel{}
		err := txCollection.Find(bson.M{
			"datet": bson.M{
				"$gt": fromDate,
				"$lt": toDate,
			},
		}).Sort("-$natural").All(&txs)
		check(err)

		//generate map with 24 hours
		hourFrequencies := map24hours()
		for _, tx := range txs {
			hourFrequencies[tx.Date.Hour]++
		}
		var hourCount []ChartCountModel
		for hour, frequency := range hourFrequencies {
			hourCount = append(hourCount, ChartCountModel{hour, frequency})
		}
		//sort by hour
		sort.Slice(hourCount, func(i, j int) bool {
			return hourCount[i].Elem < hourCount[j].Elem
		})
		var dayData []int
		for _, d := range hourCount {
			dayData = append(dayData, d.Count)
		}
		if len(txs) > 0 {
			resp.Series = append(resp.Series, txs[0].Date.Day)
			resp.Data = append(resp.Data, dayData)
		}
	}
	hourLabels := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"}
	resp.Labels = hourLabels

	//convert []resp struct to json
	jsonResp, err := json.Marshal(resp)
	check(err)
	fmt.Fprintln(w, string(jsonResp))
}
func GetLastMonthsAnalysis(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)
	vars := mux.Vars(r)
	count, err := strconv.Atoi(vars["count"])
	check(err)
	fmt.Println(count)
	fromDate := time.Now().AddDate(0, -count, 0)
	toDate := time.Now()

	txs := []TxModel{}
	err = txCollection.Find(bson.M{
		"datet": bson.M{
			"$gt": fromDate,
			"$lt": toDate,
		},
	}).Sort("-$natural").All(&txs)
	check(err)

	//generate map with 24 hours
	//hourFrequencies := map24hours()
	dayFrequencies := make(map[float64]int)
	for _, tx := range txs {
		dayFrequencies[float64(tx.Date.Month) + float64(tx.Date.Day)/100]++
	}
	var dayCount []ChartCountFloat64Model
	for day, frequency := range dayFrequencies {
		dayCount = append(dayCount, ChartCountFloat64Model{day, frequency})
	}
	//sort by hour
	sort.Slice(dayCount, func(i, j int) bool {
		return dayCount[i].Elem < dayCount[j].Elem
	})

	var resp ChartAnalysisResp
	for _, d := range dayCount {
		resp.Labels = append(resp.Labels, strconv.FormatFloat(d.Elem, 'f', -1, 64))
		resp.Data = append(resp.Data, d.Count)
	}

	//convert []resp struct to json
	jsonResp, err := json.Marshal(resp)
	check(err)
	fmt.Fprintln(w, string(jsonResp))
}
func GetAddressTimeChart(w http.ResponseWriter, r *http.Request) {
	ipFilter(w, r)

	vars := mux.Vars(r)
	hash := vars["hash"]
	if hash == "undefined" {
		fmt.Fprintln(w, "not valid hash")
	} else {
		address := AddressModel{}
		err := addressCollection.Find(bson.M{"hash": hash}).One(&address)

		txs := []TxModel{}
		err = txCollection.Find(bson.M{"$or": []bson.M{bson.M{"vin.address": hash}, bson.M{"vout.address": hash}}}).All(&txs)
		address.Txs = txs

		for _, tx := range address.Txs {
			blocks := []BlockModel{}
			err = blockCollection.Find(bson.M{"hash": tx.BlockHash}).All(&blocks)
			for _, block := range blocks {
				address.Blocks = append(address.Blocks, block)
			}
		}

		count := make(map[time.Time]float64)
		for _, tx := range txs {
			var val float64
			for _, vin := range tx.Vin {
				val = val + vin.Amount
			}
			count[tx.DateT] = val
		}
		var dateSorted []time.Time
		for t, _ := range count {
			dateSorted = append(dateSorted, t)
		}
		sort.Slice(dateSorted, func(i, j int) bool {
			//return dateSorted[i] < dateSorted[j]
			return dateBeforeThan(dateSorted[i], dateSorted[j])
		})

		var resp ChartAnalysisRespFloat64
		for _, t := range dateSorted {
			resp.Labels = append(resp.Labels, t.String())
			resp.Data = append(resp.Data, count[t])
		}

		//convert []resp struct to json
		jsonResp, err := json.Marshal(resp)
		check(err)

		fmt.Fprintln(w, string(jsonResp))
	}
}
