package graph

import (
	"time"

	"cloud.o-forge.io/core/oc-lib/models/resources"
	"cloud.o-forge.io/core/oc-lib/tools"
)

// Graph is a struct that represents a graph
type Graph struct {
	Partial bool                 `json:"partial" default:"false"`                             // Partial is a flag that indicates if the graph is partial
	Zoom    float64              `bson:"zoom" json:"zoom" default:"1"`                        // Zoom is the graphical zoom of the graph
	Items   map[string]GraphItem `bson:"items" json:"items" default:"{}" validate:"required"` // Items is the list of elements in the graph
	Links   []GraphLink          `bson:"links" json:"links" default:"{}" validate:"required"` // Links is the list of links between elements in the graph
}

func (g *Graph) Clear(id string) {
	realItems := map[string]GraphItem{}
	for k, it := range g.Items {
		if k == id {
			realinks := []GraphLink{}
			for _, link := range g.Links {
				if link.Source.ID != id && link.Destination.ID != id {
					realinks = append(realinks, link)
				}
			}
			g.Links = realinks
			g.Partial = true
		} else {
			realItems[k] = it
		}
	}
	g.Items = realItems
}

func (wf *Graph) IsProcessing(item GraphItem) bool {
	return item.Processing != nil
}

func (wf *Graph) IsCompute(item GraphItem) bool {
	return item.Compute != nil
}

func (wf *Graph) IsData(item GraphItem) bool {
	return item.Data != nil
}

func (wf *Graph) IsStorage(item GraphItem) bool {
	return item.Storage != nil
}

func (wf *Graph) IsWorkflow(item GraphItem) bool {
	return item.Workflow != nil
}

func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.ProcessingResource, resource resources.ResourceInterface,
	f func(GraphItem) resources.ResourceInterface, request *tools.APIRequest) (float64, float64) {
	nearestStart := float64(10000000000)
	oneIsInfinite := false
	longestDuration := float64(0)
	for _, link := range g.Links {
		for _, processing := range processings {
			var source string                                                                                                                             // source is the source of the link
			if link.Destination.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the destination is the processing and the source is not a compute
				source = link.Source.ID
			} else if link.Source.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the source is the processing and the destination is not a compute
				source = link.Destination.ID
			}
			priced := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, request)
			if source != "" {
				if priced.GetLocationStart() != nil {
					near := float64(priced.GetLocationStart().Sub(start).Seconds())
					if near < nearestStart {
						nearestStart = near
					}

				}
				if priced.GetLocationEnd() != nil {
					duration := float64(priced.GetLocationEnd().Sub(*priced.GetLocationStart()).Seconds())
					if longestDuration < duration {
						longestDuration = duration
					}
				} else {
					oneIsInfinite = true
				}
			}
		}
	}
	if oneIsInfinite {
		return nearestStart, -1
	}
	return nearestStart, longestDuration
}

/*
* GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing
 */
func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string, request *tools.APIRequest) float64 {
	currents := []float64{}        // list of current time
	for _, link := range g.Links { // for each link
		var source string                                                                     // source is the source of the link
		if link.Destination.ID == processingID && g.Items[link.Source.ID].Processing == nil { // if the destination is the processing and the source is not a compute
			source = link.Source.ID
		} else if link.Source.ID == processingID && g.Items[link.Source.ID].Processing == nil { // if the source is the processing and the destination is not a compute
			source = link.Destination.ID
		}
		if source == "" { // if source is empty, continue
			continue
		}
		dt, r := g.GetResource(source) // get the resource of the source
		if r == nil {                  // if item is nil, continue
			continue
		}
		priced := r.ConvertToPricedResource(dt, request)
		current := priced.GetExplicitDurationInS() // get the explicit duration of the item
		if current < 0 {                           // if current is negative, its means that duration of a before could be infinite continue
			return current
		}
		current += g.GetAverageTimeProcessingBeforeStart(current, source, request) // get the average time before start of the source
		currents = append(currents, current)                                       // append the current to the currents
	}
	var max float64 // get the max time to wait dependancies to finish
	for _, current := range currents {
		if current > max {
			max = current
		}
	}
	return max
}

func (g *Graph) GetResource(id string) (tools.DataType, resources.ResourceInterface) {
	if item, ok := g.Items[id]; ok {
		if item.Data != nil {
			return tools.DATA_RESOURCE, item.Data
		} else if item.Compute != nil {
			return tools.COMPUTE_RESOURCE, item.Compute
		} else if item.Workflow != nil {
			return tools.WORKFLOW_RESOURCE, item.Workflow
		} else if item.Processing != nil {
			return tools.PROCESSING_RESOURCE, item.Processing
		} else if item.Storage != nil {
			return tools.STORAGE_RESOURCE, item.Storage
		}
	}
	return tools.INVALID, nil
}
