// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package promql

import (
	"fmt"
	"reflect"
	"sort"
	"strings"

	clientmodel "github.com/prometheus/client_golang/model"

	"github.com/prometheus/prometheus/storage/metric"
	"github.com/prometheus/prometheus/utility"
)

func (matrix Matrix) String() string {
	metricStrings := make([]string, 0, len(matrix))
	for _, sampleStream := range matrix {
		metricName, hasName := sampleStream.Metric.Metric[clientmodel.MetricNameLabel]
		numLabels := len(sampleStream.Metric.Metric)
		if hasName {
			numLabels--
		}
		labelStrings := make([]string, 0, numLabels)
		for label, value := range sampleStream.Metric.Metric {
			if label != clientmodel.MetricNameLabel {
				labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value))
			}
		}
		sort.Strings(labelStrings)
		valueStrings := make([]string, 0, len(sampleStream.Values))
		for _, value := range sampleStream.Values {
			valueStrings = append(valueStrings,
				fmt.Sprintf("\n%v @[%v]", value.Value, value.Timestamp))
		}
		metricStrings = append(metricStrings,
			fmt.Sprintf("%s{%s} => %s",
				metricName,
				strings.Join(labelStrings, ", "),
				strings.Join(valueStrings, ", ")))
	}
	sort.Strings(metricStrings)
	return strings.Join(metricStrings, "\n")
}

func (vector Vector) String() string {
	metricStrings := make([]string, 0, len(vector))
	for _, sample := range vector {
		metricStrings = append(metricStrings,
			fmt.Sprintf("%s => %v @[%v]",
				sample.Metric,
				sample.Value, sample.Timestamp))
	}
	return strings.Join(metricStrings, "\n")
}

// Tree returns a string of the tree structure of the given node.
func Tree(node Node) string {
	return tree(node, "")
}

func tree(node Node, level string) string {
	if node == nil {
		return fmt.Sprintf("%s |---- %T\n", level, node)
	}
	typs := strings.Split(fmt.Sprintf("%T", node), ".")[1]

	var t string
	// Only print the number of statements for readability.
	if stmts, ok := node.(Statements); ok {
		t = fmt.Sprintf("%s |---- %s :: %d\n", level, typs, len(stmts))
	} else {
		t = fmt.Sprintf("%s |---- %s :: %s\n", level, typs, node)
	}

	level += " · · ·"

	switch n := node.(type) {
	case Statements:
		for _, s := range n {
			t += tree(s, level)
		}
	case *AlertStmt:
		t += tree(n.Expr, level)

	case *EvalStmt:
		t += tree(n.Expr, level)

	case *RecordStmt:
		t += tree(n.Expr, level)

	case Expressions:
		for _, e := range n {
			t += tree(e, level)
		}
	case *AggregateExpr:
		t += tree(n.Expr, level)

	case *BinaryExpr:
		t += tree(n.LHS, level)
		t += tree(n.RHS, level)

	case *Call:
		t += tree(n.Args, level)

	case *ParenExpr:
		t += tree(n.Expr, level)

	case *UnaryExpr:
		t += tree(n.Expr, level)

	case *MatrixSelector, *NumberLiteral, *StringLiteral, *VectorSelector:
		// nothing to do

	default:
		panic("promql.Tree: not all node types covered")
	}
	return t
}

func (stmts Statements) String() (s string) {
	if len(stmts) == 0 {
		return ""
	}
	for _, stmt := range stmts {
		s += stmt.String()
		s += "\n\n"
	}
	return s[:len(s)-2]
}

func (node *AlertStmt) String() string {
	s := fmt.Sprintf("ALERT %s", node.Name)
	s += fmt.Sprintf("\n\tIF %s", node.Expr)
	if node.Duration > 0 {
		s += fmt.Sprintf("\n\tFOR %s", utility.DurationToString(node.Duration))
	}
	if len(node.Labels) > 0 {
		s += fmt.Sprintf("\n\tWITH %s", node.Labels)
	}
	s += fmt.Sprintf("\n\tSUMMARY %q", node.Summary)
	s += fmt.Sprintf("\n\tDESCRIPTION %q", node.Description)
	return s
}

func (node *EvalStmt) String() string {
	return "EVAL " + node.Expr.String()
}

func (node *RecordStmt) String() string {
	s := fmt.Sprintf("%s%s = %s", node.Name, node.Labels, node.Expr)
	return s
}

func (es Expressions) String() (s string) {
	if len(es) == 0 {
		return ""
	}
	for _, e := range es {
		s += e.String()
		s += ", "
	}
	return s[:len(s)-2]
}

func (node *AggregateExpr) String() string {
	aggrString := fmt.Sprintf("%s(%s)", node.Op, node.Expr)
	if len(node.Grouping) > 0 {
		return fmt.Sprintf("%s BY (%s)", aggrString, node.Grouping)
	}
	return aggrString
}

func (node *BinaryExpr) String() string {
	matching := ""
	vm := node.VectorMatching
	if vm != nil && len(vm.On) > 0 {
		matching = fmt.Sprintf(" ON(%s)", vm.On)
		if vm.Card == CardManyToOne {
			matching += fmt.Sprintf(" GROUP_LEFT(%s)", vm.Include)
		}
		if vm.Card == CardOneToMany {
			matching += fmt.Sprintf(" GROUP_RIGHT(%s)", vm.Include)
		}
	}
	return fmt.Sprintf("%s %s%s %s", node.LHS, node.Op, matching, node.RHS)
}

func (node *Call) String() string {
	return fmt.Sprintf("%s(%s)", node.Func.Name, node.Args)
}

func (node *MatrixSelector) String() string {
	vecSelector := &VectorSelector{
		Name:          node.Name,
		LabelMatchers: node.LabelMatchers,
	}
	return fmt.Sprintf("%s[%s]", vecSelector.String(), utility.DurationToString(node.Range))
}

func (node *NumberLiteral) String() string {
	return fmt.Sprint(node.Val)
}

func (node *ParenExpr) String() string {
	return fmt.Sprintf("(%s)", node.Expr)
}

func (node *StringLiteral) String() string {
	return fmt.Sprintf("%q", node.Val)
}

func (node *UnaryExpr) String() string {
	return fmt.Sprintf("%s%s", node.Op, node.Expr)
}

func (node *VectorSelector) String() string {
	labelStrings := make([]string, 0, len(node.LabelMatchers)-1)
	for _, matcher := range node.LabelMatchers {
		// Only include the __name__ label if its no equality matching.
		if matcher.Name == clientmodel.MetricNameLabel && matcher.Type == metric.Equal {
			continue
		}
		labelStrings = append(labelStrings, matcher.String())
	}

	if len(labelStrings) == 0 {
		return node.Name
	}
	sort.Strings(labelStrings)
	return fmt.Sprintf("%s{%s}", node.Name, strings.Join(labelStrings, ","))
}

// DotGraph returns a DOT representation of a statement list.
func (ss Statements) DotGraph() string {
	graph := ""
	for _, stmt := range ss {
		graph += stmt.DotGraph()
	}
	return graph
}

// DotGraph returns a DOT representation of the alert statement.
func (node *AlertStmt) DotGraph() string {
	graph := fmt.Sprintf(
		`digraph "Alert Statement" {
	  %#p[shape="box",label="ALERT %s IF FOR %s"];
		%#p -> %x;
		%s
	}`,
		node, node.Name, utility.DurationToString(node.Duration),
		node, reflect.ValueOf(node.Expr).Pointer(),
		node.Expr.DotGraph(),
	)
	return graph
}

// DotGraph returns a DOT representation of the eval statement.
func (node *EvalStmt) DotGraph() string {
	graph := fmt.Sprintf(
		`%#p[shape="box",label="[%d:%s:%d]";
		%#p -> %x;
		%s
	}`,
		node, node.Start, node.End, node.Interval,
		node, reflect.ValueOf(node.Expr).Pointer(),
		node.Expr.DotGraph(),
	)
	return graph
}

// DotGraph returns a DOT representation of the record statement.
func (node *RecordStmt) DotGraph() string {
	graph := fmt.Sprintf(
		`%#p[shape="box",label="%s = "];
		%#p -> %x;
		%s
	}`,
		node, node.Name,
		node, reflect.ValueOf(node.Expr).Pointer(),
		node.Expr.DotGraph(),
	)
	return graph
}

// DotGraph returns a DOT representation of // DotGraph returns a DOT representation of the record statement.
// DotGraph returns a DOT representation of a statement list.
func (es Expressions) DotGraph() string {
	graph := ""
	for _, expr := range es {
		graph += expr.DotGraph()
	}
	return graph
}

// DotGraph returns a DOT representation of the vector aggregation.
func (node *AggregateExpr) DotGraph() string {
	groupByStrings := make([]string, 0, len(node.Grouping))
	for _, label := range node.Grouping {
		groupByStrings = append(groupByStrings, string(label))
	}

	graph := fmt.Sprintf("%#p[label=\"%s BY (%s)\"]\n",
		node,
		node.Op,
		strings.Join(groupByStrings, ", "))
	graph += fmt.Sprintf("%#p -> %x;\n", node, reflect.ValueOf(node.Expr).Pointer())
	graph += node.Expr.DotGraph()
	return graph
}

// DotGraph returns a DOT representation of the expression.
func (node *BinaryExpr) DotGraph() string {
	nodeAddr := reflect.ValueOf(node).Pointer()
	graph := fmt.Sprintf(
		`
		%x[label="%s"];
		%x -> %x;
		%x -> %x;
		%s
		%s
	}`,
		nodeAddr, node.Op,
		nodeAddr, reflect.ValueOf(node.LHS).Pointer(),
		nodeAddr, reflect.ValueOf(node.RHS).Pointer(),
		node.LHS.DotGraph(),
		node.RHS.DotGraph(),
	)
	return graph
}

// DotGraph returns a DOT representation of the function call.
func (node *Call) DotGraph() string {
	graph := fmt.Sprintf("%#p[label=\"%s\"];\n", node, node.Func.Name)
	graph += functionArgsToDotGraph(node, node.Args)
	return graph
}

// DotGraph returns a DOT representation of the number literal.
func (node *NumberLiteral) DotGraph() string {
	return fmt.Sprintf("%#p[label=\"%v\"];\n", node, node.Val)
}

// DotGraph returns a DOT representation of the encapsulated expression.
func (node *ParenExpr) DotGraph() string {
	return node.Expr.DotGraph()
}

// DotGraph returns a DOT representation of the matrix selector.
func (node *MatrixSelector) DotGraph() string {
	return fmt.Sprintf("%#p[label=\"%s\"];\n", node, node)
}

// DotGraph returns a DOT representation of the string literal.
func (node *StringLiteral) DotGraph() string {
	return fmt.Sprintf("%#p[label=\"'%q'\"];\n", node, node.Val)
}

// DotGraph returns a DOT representation of the unary expression.
func (node *UnaryExpr) DotGraph() string {
	nodeAddr := reflect.ValueOf(node).Pointer()
	graph := fmt.Sprintf(
		`
		%x[label="%s"];
		%x -> %x;
		%s
		%s
	}`,
		nodeAddr, node.Op,
		nodeAddr, reflect.ValueOf(node.Expr).Pointer(),
		node.Expr.DotGraph(),
	)
	return graph
}

// DotGraph returns a DOT representation of the vector selector.
func (node *VectorSelector) DotGraph() string {
	return fmt.Sprintf("%#p[label=\"%s\"];\n", node, node)
}

func functionArgsToDotGraph(node Node, args Expressions) string {
	graph := args.DotGraph()
	for _, arg := range args {
		graph += fmt.Sprintf("%x -> %x;\n", reflect.ValueOf(node).Pointer(), reflect.ValueOf(arg).Pointer())
	}
	return graph
}
