package graph

import (
	"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 NewGraph() *Graph {
	return &Graph{
		Partial: false,
		Zoom:    1,
		Items:   map[string]GraphItem{},
		Links:   []GraphLink{},
	}
}

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) IsService(item GraphItem) bool {
	return item.Service != nil
}

func (wf *Graph) IsNativeTool(item GraphItem) bool {
	return item.NativeTool != 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 (wf *Graph) IsDynamic(item GraphItem) bool {
	return item.Dynamic != nil
}

func (g *Graph) GetAverageTimeRelatedToProcessingActivity(processings []*resources.ProcessingResource, resource resources.ResourceInterface,
	f func(GraphItem) resources.ResourceInterface, instance int, partnership int, buying int, strategy int, bookingMode int, request *tools.APIRequest) (float64, float64, error) {
	oneIsInfinite := false
	longestDuration := float64(0)
	for _, link := range g.Links {
		for _, processing := range processings {
			if !(link.Destination.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID()) &&
				!(link.Source.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID()) {
				continue
			}
			priced, err := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &instance, &partnership, &buying, &strategy, &bookingMode, request)
			if err != nil {
				return 0, 0, err
			}
			duration := priced.GetExplicitDurationInS()
			if duration < 0 {
				oneIsInfinite = true
			} else if longestDuration < duration {
				longestDuration = duration
			}
		}
	}
	if oneIsInfinite {
		return 0, -1, nil
	}
	return 0, longestDuration, nil
}

/*
* GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing
 */
func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string,
	instance int, partnership int, buying int, strategy int, bookingMode int, request *tools.APIRequest) (float64, error) {
	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, err := r.ConvertToPricedResource(dt, &instance, &partnership, &buying, &strategy, &bookingMode, request)
		if err != nil {
			return 0, err
		}
		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, nil
		}
		add, err := g.GetAverageTimeProcessingBeforeStart(current, source, instance, partnership, buying, strategy, bookingMode, request)
		if err != nil {
			return 0, err
		}
		current += add                       // 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, nil
}

// DataStorageLink represents a resolved Data→Storage pair found in the graph.
type DataStorageLink struct {
	DataItemID    string
	StorageItemID string
}

// GetDataStorageLinks returns all links that connect a Data item to a Storage item.
// These links are mandatory when the Data instance has a Source configured:
// the workflow builder uses them to know where to download the data before
// any processing step that consumes that storage.
func (g *Graph) GetDataStorageLinks() []DataStorageLink {
	var result []DataStorageLink
	for _, link := range g.Links {
		srcItem, srcOk := g.Items[link.Source.ID]
		dstItem, dstOk := g.Items[link.Destination.ID]
		if !srcOk || !dstOk {
			continue
		}
		if g.IsData(srcItem) && g.IsStorage(dstItem) {
			result = append(result, DataStorageLink{
				DataItemID:    link.Source.ID,
				StorageItemID: link.Destination.ID,
			})
		} else if g.IsStorage(srcItem) && g.IsData(dstItem) {
			result = append(result, DataStorageLink{
				DataItemID:    link.Destination.ID,
				StorageItemID: link.Source.ID,
			})
		}
	}
	return result
}

// GetLinkedStorageForData returns the storage item IDs linked to a given Data item.
func (g *Graph) GetLinkedStorageForData(dataItemID string) []string {
	var storageIDs []string
	for _, dsl := range g.GetDataStorageLinks() {
		if dsl.DataItemID == dataItemID {
			storageIDs = append(storageIDs, dsl.StorageItemID)
		}
	}
	return storageIDs
}

func (g *Graph) GetResource(id string) (tools.DataType, resources.ResourceInterface) {
	if item, ok := g.Items[id]; ok {
		if item.NativeTool != nil {
			return tools.NATIVE_TOOL, item.NativeTool
		} else 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
		} else if item.Service != nil {
			return tools.SERVICE_RESOURCE, item.Service
		}
	}
	return tools.INVALID, nil
}
