package dag

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"reflect"
	"sort"
	"strconv"
	"sync"
)

const (
	typeOperation             = "Operation"
	typeTransform             = "Transform"
	typeWalk                  = "Walk"
	typeDepthFirstWalk        = "DepthFirstWalk"
	typeReverseDepthFirstWalk = "ReverseDepthFirstWalk"
	typeTransitiveReduction   = "TransitiveReduction"
	typeEdgeInfo              = "EdgeInfo"
	typeVertexInfo            = "VertexInfo"
	typeVisitInfo             = "VisitInfo"
)

// the marshal* structs are for serialization of the graph data.
type marshalGraph struct {
	// Type is always "Graph", for identification as a top level object in the
	// JSON stream.
	Type string

	// Each marshal structure requires a unique ID so that it can be referenced
	// by other structures.
	ID string `json:",omitempty"`

	// Human readable name for this graph.
	Name string `json:",omitempty"`

	// Arbitrary attributes that can be added to the output.
	Attrs map[string]string `json:",omitempty"`

	// List of graph vertices, sorted by ID.
	Vertices []*marshalVertex `json:",omitempty"`

	// List of edges, sorted by Source ID.
	Edges []*marshalEdge `json:",omitempty"`

	// Any number of subgraphs. A subgraph itself is considered a vertex, and
	// may be referenced by either end of an edge.
	Subgraphs []*marshalGraph `json:",omitempty"`

	// Any lists of vertices that are included in cycles.
	Cycles [][]*marshalVertex `json:",omitempty"`
}

// The add, remove, connect, removeEdge methods mirror the basic Graph
// manipulations to reconstruct a marshalGraph from a debug log.
func (g *marshalGraph) add(v *marshalVertex) {
	g.Vertices = append(g.Vertices, v)
	sort.Sort(vertices(g.Vertices))
}

func (g *marshalGraph) remove(v *marshalVertex) {
	for i, existing := range g.Vertices {
		if v.ID == existing.ID {
			g.Vertices = append(g.Vertices[:i], g.Vertices[i+1:]...)
			return
		}
	}
}

func (g *marshalGraph) connect(e *marshalEdge) {
	g.Edges = append(g.Edges, e)
	sort.Sort(edges(g.Edges))
}

func (g *marshalGraph) removeEdge(e *marshalEdge) {
	for i, existing := range g.Edges {
		if e.Source == existing.Source && e.Target == existing.Target {
			g.Edges = append(g.Edges[:i], g.Edges[i+1:]...)
			return
		}
	}
}

func (g *marshalGraph) vertexByID(id string) *marshalVertex {
	for _, v := range g.Vertices {
		if id == v.ID {
			return v
		}
	}
	return nil
}

type marshalVertex struct {
	// Unique ID, used to reference this vertex from other structures.
	ID string

	// Human readable name
	Name string `json:",omitempty"`

	Attrs map[string]string `json:",omitempty"`

	// This is to help transition from the old Dot interfaces. We record if the
	// node was a GraphNodeDotter here, so we can call it to get attributes.
	graphNodeDotter GraphNodeDotter
}

func newMarshalVertex(v Vertex) *marshalVertex {
	dn, ok := v.(GraphNodeDotter)
	if !ok {
		dn = nil
	}

	return &marshalVertex{
		ID:              marshalVertexID(v),
		Name:            VertexName(v),
		Attrs:           make(map[string]string),
		graphNodeDotter: dn,
	}
}

// vertices is a sort.Interface implementation for sorting vertices by ID
type vertices []*marshalVertex

func (v vertices) Less(i, j int) bool { return v[i].Name < v[j].Name }
func (v vertices) Len() int           { return len(v) }
func (v vertices) Swap(i, j int)      { v[i], v[j] = v[j], v[i] }

type marshalEdge struct {
	// Human readable name
	Name string

	// Source and Target Vertices by ID
	Source string
	Target string

	Attrs map[string]string `json:",omitempty"`
}

func newMarshalEdge(e Edge) *marshalEdge {
	return &marshalEdge{
		Name:   fmt.Sprintf("%s|%s", VertexName(e.Source()), VertexName(e.Target())),
		Source: marshalVertexID(e.Source()),
		Target: marshalVertexID(e.Target()),
		Attrs:  make(map[string]string),
	}
}

// edges is a sort.Interface implementation for sorting edges by Source ID
type edges []*marshalEdge

func (e edges) Less(i, j int) bool { return e[i].Name < e[j].Name }
func (e edges) Len() int           { return len(e) }
func (e edges) Swap(i, j int)      { e[i], e[j] = e[j], e[i] }

// build a marshalGraph structure from a *Graph
func newMarshalGraph(name string, g *Graph) *marshalGraph {
	mg := &marshalGraph{
		Type:  "Graph",
		Name:  name,
		Attrs: make(map[string]string),
	}

	for _, v := range g.Vertices() {
		id := marshalVertexID(v)
		if sg, ok := marshalSubgrapher(v); ok {
			smg := newMarshalGraph(VertexName(v), sg)
			smg.ID = id
			mg.Subgraphs = append(mg.Subgraphs, smg)
		}

		mv := newMarshalVertex(v)
		mg.Vertices = append(mg.Vertices, mv)
	}

	sort.Sort(vertices(mg.Vertices))

	for _, e := range g.Edges() {
		mg.Edges = append(mg.Edges, newMarshalEdge(e))
	}

	sort.Sort(edges(mg.Edges))

	for _, c := range (&AcyclicGraph{*g}).Cycles() {
		var cycle []*marshalVertex
		for _, v := range c {
			mv := newMarshalVertex(v)
			cycle = append(cycle, mv)
		}
		mg.Cycles = append(mg.Cycles, cycle)
	}

	return mg
}

// Attempt to return a unique ID for any vertex.
func marshalVertexID(v Vertex) string {
	val := reflect.ValueOf(v)
	switch val.Kind() {
	case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
		return strconv.Itoa(int(val.Pointer()))
	case reflect.Interface:
		return strconv.Itoa(int(val.InterfaceData()[1]))
	}

	if v, ok := v.(Hashable); ok {
		h := v.Hashcode()
		if h, ok := h.(string); ok {
			return h
		}
	}

	// fallback to a name, which we hope is unique.
	return VertexName(v)

	// we could try harder by attempting to read the arbitrary value from the
	// interface, but we shouldn't get here from terraform right now.
}

// check for a Subgrapher, and return the underlying *Graph.
func marshalSubgrapher(v Vertex) (*Graph, bool) {
	sg, ok := v.(Subgrapher)
	if !ok {
		return nil, false
	}

	switch g := sg.Subgraph().DirectedGraph().(type) {
	case *Graph:
		return g, true
	case *AcyclicGraph:
		return &g.Graph, true
	}

	return nil, false
}

// The DebugOperationEnd func type provides a way to call an End function via a
// method call, allowing for the chaining of methods in a defer statement.
type DebugOperationEnd func(string)

// End calls function e with the info parameter, marking the end of this
// operation in the logs.
func (e DebugOperationEnd) End(info string) { e(info) }

// encoder provides methods to write debug data to an io.Writer, and is a noop
// when no writer is present
type encoder struct {
	sync.Mutex
	w io.Writer
}

// Encode is analogous to json.Encoder.Encode
func (e *encoder) Encode(i interface{}) {
	if e == nil || e.w == nil {
		return
	}
	e.Lock()
	defer e.Unlock()

	js, err := json.Marshal(i)
	if err != nil {
		log.Println("[ERROR] dag:", err)
		return
	}
	js = append(js, '\n')

	_, err = e.w.Write(js)
	if err != nil {
		log.Println("[ERROR] dag:", err)
		return
	}
}

func (e *encoder) Add(v Vertex) {
	if e == nil {
		return
	}
	e.Encode(marshalTransform{
		Type:      typeTransform,
		AddVertex: newMarshalVertex(v),
	})
}

// Remove records the removal of Vertex v.
func (e *encoder) Remove(v Vertex) {
	if e == nil {
		return
	}
	e.Encode(marshalTransform{
		Type:         typeTransform,
		RemoveVertex: newMarshalVertex(v),
	})
}

func (e *encoder) Connect(edge Edge) {
	if e == nil {
		return
	}
	e.Encode(marshalTransform{
		Type:    typeTransform,
		AddEdge: newMarshalEdge(edge),
	})
}

func (e *encoder) RemoveEdge(edge Edge) {
	if e == nil {
		return
	}
	e.Encode(marshalTransform{
		Type:       typeTransform,
		RemoveEdge: newMarshalEdge(edge),
	})
}

// BeginOperation marks the start of set of graph transformations, and returns
// an EndDebugOperation func to be called once the opration is complete.
func (e *encoder) BeginOperation(op string, info string) DebugOperationEnd {
	if e == nil {
		return func(string) {}
	}

	e.Encode(marshalOperation{
		Type:  typeOperation,
		Begin: op,
		Info:  info,
	})

	return func(info string) {
		e.Encode(marshalOperation{
			Type: typeOperation,
			End:  op,
			Info: info,
		})
	}
}

// structure for recording graph transformations
type marshalTransform struct {
	// Type: "Transform"
	Type         string
	AddEdge      *marshalEdge   `json:",omitempty"`
	RemoveEdge   *marshalEdge   `json:",omitempty"`
	AddVertex    *marshalVertex `json:",omitempty"`
	RemoveVertex *marshalVertex `json:",omitempty"`
}

func (t marshalTransform) Transform(g *marshalGraph) {
	switch {
	case t.AddEdge != nil:
		g.connect(t.AddEdge)
	case t.RemoveEdge != nil:
		g.removeEdge(t.RemoveEdge)
	case t.AddVertex != nil:
		g.add(t.AddVertex)
	case t.RemoveVertex != nil:
		g.remove(t.RemoveVertex)
	}
}

// this structure allows us to decode any object in the json stream for
// inspection, then re-decode it into a proper struct if needed.
type streamDecode struct {
	Type string
	Map  map[string]interface{}
	JSON []byte
}

func (s *streamDecode) UnmarshalJSON(d []byte) error {
	s.JSON = d
	err := json.Unmarshal(d, &s.Map)
	if err != nil {
		return err
	}

	if t, ok := s.Map["Type"]; ok {
		s.Type, _ = t.(string)
	}
	return nil
}

// structure for recording the beginning and end of any multi-step
// transformations. These are informational, and not required to reproduce the
// graph state.
type marshalOperation struct {
	Type  string
	Begin string `json:",omitempty"`
	End   string `json:",omitempty"`
	Info  string `json:",omitempty"`
}

// decodeGraph decodes a marshalGraph from an encoded graph stream.
func decodeGraph(r io.Reader) (*marshalGraph, error) {
	dec := json.NewDecoder(r)

	// a stream should always start with a graph
	g := &marshalGraph{}

	err := dec.Decode(g)
	if err != nil {
		return nil, err
	}

	// now replay any operations that occurred on the original graph
	for dec.More() {
		s := &streamDecode{}
		err := dec.Decode(s)
		if err != nil {
			return g, err
		}

		// the only Type we're concerned with here is Transform to complete the
		// Graph
		if s.Type != typeTransform {
			continue
		}

		t := &marshalTransform{}
		err = json.Unmarshal(s.JSON, t)
		if err != nil {
			return g, err
		}
		t.Transform(g)
	}
	return g, nil
}

// marshalVertexInfo allows encoding arbitrary information about the a single
// Vertex in the logs. These are accumulated for informational display while
// rebuilding the graph.
type marshalVertexInfo struct {
	Type   string
	Vertex *marshalVertex
	Info   string
}

func newVertexInfo(infoType string, v Vertex, info string) *marshalVertexInfo {
	return &marshalVertexInfo{
		Type:   infoType,
		Vertex: newMarshalVertex(v),
		Info:   info,
	}
}

// marshalEdgeInfo allows encoding arbitrary information about the a single
// Edge in the logs. These are accumulated for informational display while
// rebuilding the graph.
type marshalEdgeInfo struct {
	Type string
	Edge *marshalEdge
	Info string
}

func newEdgeInfo(infoType string, e Edge, info string) *marshalEdgeInfo {
	return &marshalEdgeInfo{
		Type: infoType,
		Edge: newMarshalEdge(e),
		Info: info,
	}
}

// JSON2Dot reads a Graph debug log from and io.Reader, and converts the final
// graph dot format.
//
// TODO: Allow returning the output at a certain point during decode.
//       Encode extra information from the json log into the Dot.
func JSON2Dot(r io.Reader) ([]byte, error) {
	g, err := decodeGraph(r)
	if err != nil {
		return nil, err
	}

	return g.Dot(nil), nil
}
