// Copyright ©2018 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package gexf12 implements marshaling and unmarshaling of GEXF1.2 documents.
//
// For details of GEXF see https://gephi.org/gexf/format/.
package gexf12 // import "gonum.org/v1/gonum/graph/formats/gexf12"

import (
	"bytes"
	"encoding/xml"
	"time"
)

// BUG(kortschak): The namespace for GEFX1.2 is 1.2draft, though it has
// already been deprecated. There is no specification for 1.3, although
// it is being used in the wild.

// Content holds a GEFX graph and metadata.
type Content struct {
	XMLName xml.Name `xml:"http://www.gexf.net/1.2draft gexf"`
	Meta    *Meta    `xml:"meta,omitempty"`
	Graph   Graph    `xml:"graph"`
	// Version must be "1.2".
	Version string `xml:"version,attr"`
	Variant string `xml:"variant,attr,omitempty"`
}

// Meta holds optional metadata associated with the graph.
type Meta struct {
	Creator      string    `xml:"creator,omitempty"`
	Keywords     string    `xml:"keywords,omitempty"`
	Description  string    `xml:"description,omitempty"`
	LastModified time.Time `xml:"lastmodifieddate,attr,omitempty"`
}

// MarshalXML implements the xml.Marshaler interface.
func (t *Meta) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	type T Meta
	var layout struct {
		*T
		LastModified *xsdDate `xml:"lastmodifieddate,attr,omitempty"`
	}
	layout.T = (*T)(t)
	layout.LastModified = (*xsdDate)(&layout.T.LastModified)
	return e.EncodeElement(layout, start)
}

// UnmarshalXML implements the xml.Unmarshaler interface.
func (t *Meta) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	type T Meta
	var overlay struct {
		*T
		LastModified *xsdDate `xml:"lastmodifieddate,attr,omitempty"`
	}
	overlay.T = (*T)(t)
	overlay.LastModified = (*xsdDate)(&overlay.T.LastModified)
	return d.DecodeElement(&overlay, &start)
}

// Graph stores the graph nodes, edges, dynamics and visualization data.
type Graph struct {
	Attributes []Attributes `xml:"attributes"`
	Nodes      Nodes        `xml:"nodes"`
	Edges      Edges        `xml:"edges"`
	// TimeFormat may be one of "integer", "double", "date" or "dateTime".
	TimeFormat string `xml:"timeformat,attr,omitempty"`
	Start      string `xml:"start,attr,omitempty"`
	StartOpen  string `xml:"startopen,attr,omitempty"`
	End        string `xml:"end,attr,omitempty"`
	EndOpen    string `xml:"endopen,attr,omitempty"`
	// DefaultEdgeType may be one of "directed", "undirected" or "mutual".
	DefaultEdgeType string `xml:"defaultedgetype,attr,omitempty"`
	// IDType may be one of "integer" or "string".
	IDType string `xml:"idtype,attr,omitempty"`
	// Mode may be "static" or "dynamic".
	Mode string `xml:"mode,attr,omitempty"`
}

// Attributes holds a collection of potentially dynamic attributes
// associated with a graph.
type Attributes struct {
	Attributes []Attribute `xml:"attribute,omitempty"`
	// Class be one of "node" or "edge".
	Class string `xml:"class,attr"`
	// Mode may be "static" or "dynamic".
	Mode      string `xml:"mode,attr,omitempty"`
	Start     string `xml:"start,attr,omitempty"`
	StartOpen string `xml:"startopen,attr,omitempty"`
	End       string `xml:"end,attr,omitempty"`
	EndOpen   string `xml:"endopen,attr,omitempty"`
}

// Attribute holds a single graph attribute.
type Attribute struct {
	ID    string `xml:"id,attr"`
	Title string `xml:"title,attr"`
	// Type may be one of "integer", "long", "double", "float",
	// "boolean", "liststring", "string", or "anyURI".
	Type    string `xml:"type,attr"`
	Default string `xml:"default,omitempty"`
	Options string `xml:"options,omitempty"`
}

// Nodes holds a collection of nodes constituting a graph or subgraph.
type Nodes struct {
	Count int    `xml:"count,attr,omitempty"`
	Nodes []Node `xml:"node,omitempty"`
}

// Node is a single node and its associated attributes.
type Node struct {
	ID        string     `xml:"id,attr,omitempty"`
	Label     string     `xml:"label,attr,omitempty"`
	AttValues *AttValues `xml:"attvalues"`
	Spells    *Spells    `xml:"spells"`
	Nodes     *Nodes     `xml:"nodes"`
	Edges     *Edges     `xml:"edges"`
	ParentID  string     `xml:"pid,attr,omitempty"`
	Parents   *Parents   `xml:"parents"`
	Color     *Color     `xml:"http://www.gexf.net/1.2draft/viz color"`
	Position  *Position  `xml:"http://www.gexf.net/1.2draft/viz position"`
	Size      *Size      `xml:"http://www.gexf.net/1.2draft/viz size"`
	Shape     *NodeShape `xml:"http://www.gexf.net/1.2draft/viz shape"`
	Start     string     `xml:"start,attr,omitempty"`
	StartOpen string     `xml:"startopen,attr,omitempty"`
	End       string     `xml:"end,attr,omitempty"`
	EndOpen   string     `xml:"endopen,attr,omitempty"`
}

// NodeShape holds the visual representation of a node with associated
// dynamics.
type NodeShape struct {
	Spells *Spells `xml:"spells,omitempty"`
	// Value be one of "disc", "square", "triangle",
	// "diamond" or "image".
	Shape     string `xml:"value,attr"`
	URI       string `xml:"uri,attr,omitempty"`
	Start     string `xml:"start,attr,omitempty"`
	StartOpen string `xml:"startopen,attr,omitempty"`
	End       string `xml:"end,attr,omitempty"`
	EndOpen   string `xml:"endopen,attr,omitempty"`
}

// Color represents a node or edge color and its associated dynamics.
type Color struct {
	Spells    *Spells `xml:"spells,omitempty"`
	R         byte    `xml:"r,attr"`
	G         byte    `xml:"g,attr"`
	B         byte    `xml:"b,attr"`
	A         float64 `xml:"a,attr,omitempty"`
	Start     string  `xml:"start,attr,omitempty"`
	StartOpen string  `xml:"startopen,attr,omitempty"`
	End       string  `xml:"end,attr,omitempty"`
	EndOpen   string  `xml:"endopen,attr,omitempty"`
}

// Edges holds a collection of edges constituting a graph or subgraph.
type Edges struct {
	Count int    `xml:"count,attr,omitempty"`
	Edges []Edge `xml:"edge,omitempty"`
}

// Edge is a single edge and its associated attributes.
type Edge struct {
	ID        string     `xml:"id,attr,omitempty"`
	AttValues *AttValues `xml:"attvalues"`
	Spells    *Spells    `xml:"spells"`
	Color     *Color     `xml:"http://www.gexf.net/1.2draft/viz color"`
	Thickness *Thickness `xml:"http://www.gexf.net/1.2draft/viz thickness"`
	Shape     *Edgeshape `xml:"http://www.gexf.net/1.2draft/viz shape"`
	Start     string     `xml:"start,attr,omitempty"`
	StartOpen string     `xml:"startopen,attr,omitempty"`
	End       string     `xml:"end,attr,omitempty"`
	EndOpen   string     `xml:"endopen,attr,omitempty"`
	// Type may be one of directed, undirected, mutual
	Type   string  `xml:"type,attr,omitempty"`
	Label  string  `xml:"label,attr,omitempty"`
	Source string  `xml:"source,attr"`
	Target string  `xml:"target,attr"`
	Weight float64 `xml:"weight,attr,omitempty"`
}

// AttValues holds a collection of attribute values.
type AttValues struct {
	AttValues []AttValue `xml:"attvalue,omitempty"`
}

// AttValue holds a single attribute value and its associated dynamics.
type AttValue struct {
	For       string `xml:"for,attr"`
	Value     string `xml:"value,attr"`
	Start     string `xml:"start,attr,omitempty"`
	StartOpen string `xml:"startopen,attr,omitempty"`
	End       string `xml:"end,attr,omitempty"`
	EndOpen   string `xml:"endopen,attr,omitempty"`
}

// Edgeshape holds the visual representation of an edge with associated
// dynamics.
type Edgeshape struct {
	// Shape be one of solid, dotted, dashed, double
	Shape     string  `xml:"value,attr"`
	Spells    *Spells `xml:"spells,omitempty"`
	Start     string  `xml:"start,attr,omitempty"`
	StartOpen string  `xml:"startopen,attr,omitempty"`
	End       string  `xml:"end,attr,omitempty"`
	EndOpen   string  `xml:"endopen,attr,omitempty"`
}

// Parents holds parent relationships between nodes in a hierarchical
// graph.
type Parents struct {
	Parents []Parent `xml:"parent,omitempty"`
}

// Parent is a single parent relationship.
type Parent struct {
	For string `xml:"for,attr"`
}

// Position hold the spatial position of a node and its dynamics.
type Position struct {
	X         float64 `xml:"x,attr"`
	Y         float64 `xml:"y,attr"`
	Z         float64 `xml:"z,attr"`
	Spells    *Spells `xml:"spells,omitempty"`
	Start     string  `xml:"start,attr,omitempty"`
	StartOpen string  `xml:"startopen,attr,omitempty"`
	End       string  `xml:"end,attr,omitempty"`
	EndOpen   string  `xml:"endopen,attr,omitempty"`
}

// Size hold the visual size of a node and its dynamics.
type Size struct {
	Value     float64 `xml:"value,attr"`
	Spells    *Spells `xml:"http://www.gexf.net/1.2draft/viz spells,omitempty"`
	Start     string  `xml:"start,attr,omitempty"`
	StartOpen string  `xml:"startopen,attr,omitempty"`
	End       string  `xml:"end,attr,omitempty"`
	EndOpen   string  `xml:"endopen,attr,omitempty"`
}

// Thickness hold the visual thickness of an edge and its dynamics.
type Thickness struct {
	Value     float64 `xml:"value,attr"`
	Spells    *Spells `xml:"http://www.gexf.net/1.2draft/viz spells,omitempty"`
	Start     string  `xml:"start,attr,omitempty"`
	StartOpen string  `xml:"startopen,attr,omitempty"`
	End       string  `xml:"end,attr,omitempty"`
	EndOpen   string  `xml:"endopen,attr,omitempty"`
}

// Spells holds a collection of time dynamics for a graph entity.
type Spells struct {
	Spells []Spell `xml:"spell"`
}

// Spell is a time interval.
type Spell struct {
	Start     string `xml:"start,attr,omitempty"`
	StartOpen string `xml:"startopen,attr,omitempty"`
	End       string `xml:"end,attr,omitempty"`
	EndOpen   string `xml:"endopen,attr,omitempty"`
}

type xsdDate time.Time

func (t *xsdDate) UnmarshalText(text []byte) error {
	return _unmarshalTime(text, (*time.Time)(t), "2006-01-02")
}

func (t xsdDate) MarshalText() ([]byte, error) {
	return []byte((time.Time)(t).Format("2006-01-02")), nil
}

func (t xsdDate) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	if (time.Time)(t).IsZero() {
		return nil
	}
	m, err := t.MarshalText()
	if err != nil {
		return err
	}
	return e.EncodeElement(m, start)
}

func (t xsdDate) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
	if (time.Time)(t).IsZero() {
		return xml.Attr{}, nil
	}
	m, err := t.MarshalText()
	return xml.Attr{Name: name, Value: string(m)}, err
}

func _unmarshalTime(text []byte, t *time.Time, format string) (err error) {
	s := string(bytes.TrimSpace(text))
	*t, err = time.Parse(format, s)
	if _, ok := err.(*time.ParseError); ok {
		*t, err = time.Parse(format+"Z07:00", s)
	}
	return err
}
