package influxdb

import (
	"context"
	"encoding/json"
	"fmt"
	"net/url"
	"sort"
	"time"

	"github.com/influxdata/influxdb/v2/kit/platform"
	"github.com/influxdata/influxdb/v2/kit/platform/errors"
)

// ErrDashboardNotFound is the error msg for a missing dashboard.
const ErrDashboardNotFound = "dashboard not found"

// ErrCellNotFound is the error msg for a missing cell.
const ErrCellNotFound = "cell not found"

// ErrViewNotFound is the error msg for a missing View.
const ErrViewNotFound = "view not found"

// ops for dashboard service.
const (
	OpFindDashboardByID       = "FindDashboardByID"
	OpFindDashboards          = "FindDashboards"
	OpCreateDashboard         = "CreateDashboard"
	OpUpdateDashboard         = "UpdateDashboard"
	OpAddDashboardCell        = "AddDashboardCell"
	OpRemoveDashboardCell     = "RemoveDashboardCell"
	OpUpdateDashboardCell     = "UpdateDashboardCell"
	OpGetDashboardCellView    = "GetDashboardCellView"
	OpUpdateDashboardCellView = "UpdateDashboardCellView"
	OpDeleteDashboard         = "DeleteDashboard"
	OpReplaceDashboardCells   = "ReplaceDashboardCells"
)

// DashboardService represents a service for managing dashboard data.
type DashboardService interface {
	// FindDashboardByID returns a single dashboard by ID.
	FindDashboardByID(ctx context.Context, id platform.ID) (*Dashboard, error)

	// FindDashboards returns a list of dashboards that match filter and the total count of matching dashboards.
	// Additional options provide pagination & sorting.
	FindDashboards(ctx context.Context, filter DashboardFilter, opts FindOptions) ([]*Dashboard, int, error)

	// CreateDashboard creates a new dashboard and sets b.ID with the new identifier.
	CreateDashboard(ctx context.Context, b *Dashboard) error

	// UpdateDashboard updates a single dashboard with changeset.
	// Returns the new dashboard state after update.
	UpdateDashboard(ctx context.Context, id platform.ID, upd DashboardUpdate) (*Dashboard, error)

	// AddDashboardCell adds a cell to a dashboard.
	AddDashboardCell(ctx context.Context, id platform.ID, c *Cell, opts AddDashboardCellOptions) error

	// RemoveDashboardCell removes a dashboard.
	RemoveDashboardCell(ctx context.Context, dashboardID, cellID platform.ID) error

	// UpdateDashboardCell replaces the dashboard cell with the provided ID.
	UpdateDashboardCell(ctx context.Context, dashboardID, cellID platform.ID, upd CellUpdate) (*Cell, error)

	// GetDashboardCellView retrieves a dashboard cells view.
	GetDashboardCellView(ctx context.Context, dashboardID, cellID platform.ID) (*View, error)

	// UpdateDashboardCellView retrieves a dashboard cells view.
	UpdateDashboardCellView(ctx context.Context, dashboardID, cellID platform.ID, upd ViewUpdate) (*View, error)

	// DeleteDashboard removes a dashboard by ID.
	DeleteDashboard(ctx context.Context, id platform.ID) error

	// ReplaceDashboardCells replaces all cells in a dashboard
	ReplaceDashboardCells(ctx context.Context, id platform.ID, c []*Cell) error
}

// Dashboard represents all visual and query data for a dashboard.
type Dashboard struct {
	ID             platform.ID   `json:"id,omitempty"`
	OrganizationID platform.ID   `json:"orgID,omitempty"`
	Name           string        `json:"name"`
	Description    string        `json:"description"`
	Cells          []*Cell       `json:"cells"`
	Meta           DashboardMeta `json:"meta"`
	OwnerID        *platform.ID  `json:"owner,omitempty"`
}

// DashboardMeta contains meta information about dashboards
type DashboardMeta struct {
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
}

// DefaultDashboardFindOptions are the default find options for dashboards
var DefaultDashboardFindOptions = FindOptions{
	SortBy: "ID",
}

// SortDashboards sorts a slice of dashboards by a field.
func SortDashboards(opts FindOptions, ds []*Dashboard) {
	var sorter func(i, j int) bool
	switch opts.SortBy {
	case "CreatedAt":
		sorter = func(i, j int) bool {
			return ds[j].Meta.CreatedAt.After(ds[i].Meta.CreatedAt)
		}
	case "UpdatedAt":
		sorter = func(i, j int) bool {
			return ds[j].Meta.UpdatedAt.After(ds[i].Meta.UpdatedAt)
		}
	case "Name":
		sorter = func(i, j int) bool {
			return ds[i].Name < ds[j].Name
		}
	default:
		sorter = func(i, j int) bool {
			if opts.Descending {
				return ds[i].ID > ds[j].ID
			}
			return ds[i].ID < ds[j].ID
		}
	}

	sort.Slice(ds, sorter)
}

// Cell holds positional information about a cell on dashboard and a reference to a cell.
type Cell struct {
	ID platform.ID `json:"id,omitempty"`
	CellProperty
	View *View `json:"-"`
}

// Marshals the cell
func (c *Cell) MarshalJSON() ([]byte, error) {
	type resp struct {
		ID             *platform.ID    `json:"id,omitempty"`
		Name           string          `json:"name,omitempty"`
		ViewProperties json.RawMessage `json:"properties,omitempty"`
		CellProperty
	}
	response := resp{
		CellProperty: c.CellProperty,
	}
	if c.ID != 0 {
		response.ID = &c.ID
	}
	if c.View != nil {
		response.Name = c.View.Name
		rawJSON, err := MarshalViewPropertiesJSON(c.View.Properties)
		if err != nil {
			return nil, err
		}
		response.ViewProperties = rawJSON
	}
	return json.Marshal(response)
}

func (c *Cell) UnmarshalJSON(b []byte) error {
	var newCell struct {
		ID             platform.ID     `json:"id,omitempty"`
		Name           string          `json:"name,omitempty"`
		ViewProperties json.RawMessage `json:"properties,omitempty"`
		CellProperty
	}
	if err := json.Unmarshal(b, &newCell); err != nil {
		return err
	}

	c.ID = newCell.ID
	c.CellProperty = newCell.CellProperty

	if newCell.Name != "" {
		if c.View == nil {
			c.View = new(View)
		}
		c.View.Name = newCell.Name
	}

	props, err := UnmarshalViewPropertiesJSON(newCell.ViewProperties)
	if err == nil {
		if c.View == nil {
			c.View = new(View)
		}
		c.View.Properties = props
	}

	return nil
}

// CellProperty contains the properties of a cell.
type CellProperty struct {
	X int32 `json:"x"`
	Y int32 `json:"y"`
	W int32 `json:"w"`
	H int32 `json:"h"`
}

// DashboardFilter is a filter for dashboards.
type DashboardFilter struct {
	IDs            []*platform.ID
	OrganizationID *platform.ID
	Organization   *string
	OwnerID        *platform.ID
}

// QueryParams turns a dashboard filter into query params
//
// It implements PagingFilter.
func (f DashboardFilter) QueryParams() map[string][]string {
	qp := url.Values{}
	for _, id := range f.IDs {
		if id != nil {
			qp.Add("id", id.String())
		}
	}

	if f.OrganizationID != nil {
		qp.Add("orgID", f.OrganizationID.String())
	}

	if f.Organization != nil {
		qp.Add("org", *f.Organization)
	}

	if f.OwnerID != nil {
		qp.Add("owner", f.OwnerID.String())
	}

	return qp
}

// DashboardUpdate is the patch structure for a dashboard.
type DashboardUpdate struct {
	Name        *string  `json:"name"`
	Description *string  `json:"description"`
	Cells       *[]*Cell `json:"cells"`
}

// Apply applies an update to a dashboard.
func (u DashboardUpdate) Apply(d *Dashboard) error {
	if u.Name != nil {
		d.Name = *u.Name
	}

	if u.Description != nil {
		d.Description = *u.Description
	}

	if u.Cells != nil {
		d.Cells = *u.Cells
	}

	return nil
}

// Valid returns an error if the dashboard update is invalid.
func (u DashboardUpdate) Valid() *errors.Error {
	if u.Name == nil && u.Description == nil {
		return &errors.Error{
			Code: errors.EInvalid,
			Msg:  "must update at least one attribute",
		}
	}

	return nil
}

// AddDashboardCellOptions are options for adding a dashboard.
type AddDashboardCellOptions struct {
	View *View
}

// CellUpdate is the patch structure for a cell.
type CellUpdate struct {
	X *int32 `json:"x"`
	Y *int32 `json:"y"`
	W *int32 `json:"w"`
	H *int32 `json:"h"`
}

// Apply applies an update to a Cell.
func (u CellUpdate) Apply(c *Cell) error {
	if u.X != nil {
		c.X = *u.X
	}

	if u.Y != nil {
		c.Y = *u.Y
	}

	if u.W != nil {
		c.W = *u.W
	}

	if u.H != nil {
		c.H = *u.H
	}

	return nil
}

// Valid returns an error if the cell update is invalid.
func (u CellUpdate) Valid() *errors.Error {
	if u.H == nil && u.W == nil && u.Y == nil && u.X == nil {
		return &errors.Error{
			Code: errors.EInvalid,
			Msg:  "must update at least one attribute",
		}
	}

	return nil
}

// ViewUpdate is a struct for updating Views.
type ViewUpdate struct {
	ViewContentsUpdate
	Properties ViewProperties
}

// Valid validates the update struct. It expects minimal values to be set.
func (u ViewUpdate) Valid() *errors.Error {
	_, ok := u.Properties.(EmptyViewProperties)
	if u.Name == nil && ok {
		return &errors.Error{
			Code: errors.EInvalid,
			Msg:  "expected at least one attribute to be updated",
		}
	}

	return nil
}

// Apply updates a view with the view updates properties.
func (u ViewUpdate) Apply(v *View) error {
	if err := u.Valid(); err != nil {
		return err
	}

	if u.Name != nil {
		v.Name = *u.Name
	}

	if u.Properties != nil {
		v.Properties = u.Properties
	}

	return nil
}

// ViewContentsUpdate is a struct for updating the non properties content of a View.
type ViewContentsUpdate struct {
	Name *string `json:"name"`
}

// ViewFilter represents a set of filter that restrict the returned results.
type ViewFilter struct {
	ID    *platform.ID
	Types []string
}

// View holds positional and visual information for a View.
type View struct {
	ViewContents
	Properties ViewProperties
}

// ViewContents is the id and name of a specific view.
type ViewContents struct {
	ID   platform.ID `json:"id,omitempty"`
	Name string      `json:"name"`
}

// Values for all supported view property types.
const (
	ViewPropertyTypeCheck              = "check"
	ViewPropertyTypeGauge              = "gauge"
	ViewPropertyTypeHeatMap            = "heatmap"
	ViewPropertyTypeHistogram          = "histogram"
	ViewPropertyTypeLogViewer          = "log-viewer"
	ViewPropertyTypeMarkdown           = "markdown"
	ViewPropertyTypeScatter            = "scatter"
	ViewPropertyTypeSingleStat         = "single-stat"
	ViewPropertyTypeSingleStatPlusLine = "line-plus-single-stat"
	ViewPropertyTypeTable              = "table"
	ViewPropertyTypeXY                 = "xy"
	ViewPropertyTypeMosaic             = "mosaic"
	ViewPropertyTypeBand               = "band"
	ViewPropertyTypeGeo                = "geo"
)

// ViewProperties is used to mark other structures as conforming to a View.
type ViewProperties interface {
	viewProperties()
	GetType() string
}

// EmptyViewProperties is visualization that has no values
type EmptyViewProperties struct{}

func (v EmptyViewProperties) viewProperties() {}

func (v EmptyViewProperties) GetType() string { return "" }

// UnmarshalViewPropertiesJSON unmarshals JSON bytes into a ViewProperties.
func UnmarshalViewPropertiesJSON(b []byte) (ViewProperties, error) {
	var v struct {
		B json.RawMessage `json:"properties"`
	}

	if err := json.Unmarshal(b, &v); err != nil {
		return nil, err
	}

	if len(v.B) == 0 {
		// Then there wasn't any visualization field, so there's no need unmarshal it
		return EmptyViewProperties{}, nil
	}

	var t struct {
		Shape string `json:"shape"`
		Type  string `json:"type"`
	}

	if err := json.Unmarshal(v.B, &t); err != nil {
		return nil, err
	}

	var vis ViewProperties
	switch t.Shape {
	case "chronograf-v2":
		switch t.Type {
		case ViewPropertyTypeCheck:
			var cv CheckViewProperties
			if err := json.Unmarshal(v.B, &cv); err != nil {
				return nil, err
			}
			vis = cv
		case ViewPropertyTypeXY:
			var xyv XYViewProperties
			if err := json.Unmarshal(v.B, &xyv); err != nil {
				return nil, err
			}
			vis = xyv
		case ViewPropertyTypeSingleStat:
			var ssv SingleStatViewProperties
			if err := json.Unmarshal(v.B, &ssv); err != nil {
				return nil, err
			}
			vis = ssv
		case ViewPropertyTypeGauge:
			var gv GaugeViewProperties
			if err := json.Unmarshal(v.B, &gv); err != nil {
				return nil, err
			}
			vis = gv
		case ViewPropertyTypeGeo:
			var gvw GeoViewProperties
			if err := json.Unmarshal(v.B, &gvw); err != nil {
				return nil, err
			}
			vis = gvw
		case ViewPropertyTypeTable:
			var tv TableViewProperties
			if err := json.Unmarshal(v.B, &tv); err != nil {
				return nil, err
			}
			vis = tv
		case ViewPropertyTypeMarkdown:
			var mv MarkdownViewProperties
			if err := json.Unmarshal(v.B, &mv); err != nil {
				return nil, err
			}
			vis = mv
		case ViewPropertyTypeLogViewer: // happens in log viewer stays in log viewer.
			var lv LogViewProperties
			if err := json.Unmarshal(v.B, &lv); err != nil {
				return nil, err
			}
			vis = lv
		case ViewPropertyTypeSingleStatPlusLine:
			var lv LinePlusSingleStatProperties
			if err := json.Unmarshal(v.B, &lv); err != nil {
				return nil, err
			}
			vis = lv
		case ViewPropertyTypeHistogram:
			var hv HistogramViewProperties
			if err := json.Unmarshal(v.B, &hv); err != nil {
				return nil, err
			}
			vis = hv
		case ViewPropertyTypeHeatMap:
			var hv HeatmapViewProperties
			if err := json.Unmarshal(v.B, &hv); err != nil {
				return nil, err
			}
			vis = hv
		case ViewPropertyTypeScatter:
			var sv ScatterViewProperties
			if err := json.Unmarshal(v.B, &sv); err != nil {
				return nil, err
			}
			vis = sv
		case ViewPropertyTypeMosaic:
			var mv MosaicViewProperties
			if err := json.Unmarshal(v.B, &mv); err != nil {
				return nil, err
			}
			vis = mv
		case ViewPropertyTypeBand:
			var bv BandViewProperties
			if err := json.Unmarshal(v.B, &bv); err != nil {
				return nil, err
			}
			vis = bv
		}
	case "empty":
		var ev EmptyViewProperties
		if err := json.Unmarshal(v.B, &ev); err != nil {
			return nil, err
		}
		vis = ev
	default:
		return nil, fmt.Errorf("unknown shape %v", t.Shape)
	}

	return vis, nil
}

// MarshalViewPropertiesJSON encodes a view into JSON bytes.
func MarshalViewPropertiesJSON(v ViewProperties) ([]byte, error) {
	var s interface{}
	switch vis := v.(type) {
	case SingleStatViewProperties:
		s = struct {
			Shape string `json:"shape"`
			SingleStatViewProperties
		}{
			Shape: "chronograf-v2",

			SingleStatViewProperties: vis,
		}
	case TableViewProperties:
		s = struct {
			Shape string `json:"shape"`
			TableViewProperties
		}{
			Shape: "chronograf-v2",

			TableViewProperties: vis,
		}
	case GaugeViewProperties:
		s = struct {
			Shape string `json:"shape"`
			GaugeViewProperties
		}{
			Shape: "chronograf-v2",

			GaugeViewProperties: vis,
		}
	case GeoViewProperties:
		s = struct {
			Shape string `json:"shape"`
			GeoViewProperties
		}{
			Shape:             "chronograf-v2",
			GeoViewProperties: vis,
		}
	case XYViewProperties:
		s = struct {
			Shape string `json:"shape"`
			XYViewProperties
		}{
			Shape: "chronograf-v2",

			XYViewProperties: vis,
		}
	case BandViewProperties:
		s = struct {
			Shape string `json:"shape"`
			BandViewProperties
		}{
			Shape: "chronograf-v2",

			BandViewProperties: vis,
		}
	case LinePlusSingleStatProperties:
		s = struct {
			Shape string `json:"shape"`
			LinePlusSingleStatProperties
		}{
			Shape: "chronograf-v2",

			LinePlusSingleStatProperties: vis,
		}
	case HistogramViewProperties:
		s = struct {
			Shape string `json:"shape"`
			HistogramViewProperties
		}{
			Shape: "chronograf-v2",

			HistogramViewProperties: vis,
		}
	case HeatmapViewProperties:
		s = struct {
			Shape string `json:"shape"`
			HeatmapViewProperties
		}{
			Shape: "chronograf-v2",

			HeatmapViewProperties: vis,
		}
	case ScatterViewProperties:
		s = struct {
			Shape string `json:"shape"`
			ScatterViewProperties
		}{
			Shape: "chronograf-v2",

			ScatterViewProperties: vis,
		}
	case MosaicViewProperties:
		s = struct {
			Shape string `json:"shape"`
			MosaicViewProperties
		}{
			Shape: "chronograf-v2",

			MosaicViewProperties: vis,
		}
	case MarkdownViewProperties:
		s = struct {
			Shape string `json:"shape"`
			MarkdownViewProperties
		}{
			Shape: "chronograf-v2",

			MarkdownViewProperties: vis,
		}
	case LogViewProperties:
		s = struct {
			Shape string `json:"shape"`
			LogViewProperties
		}{
			Shape:             "chronograf-v2",
			LogViewProperties: vis,
		}
	case CheckViewProperties:
		s = struct {
			Shape string `json:"shape"`
			CheckViewProperties
		}{
			Shape: "chronograf-v2",

			CheckViewProperties: vis,
		}
	default:
		s = struct {
			Shape string `json:"shape"`
			EmptyViewProperties
		}{
			Shape:               "empty",
			EmptyViewProperties: EmptyViewProperties{},
		}
	}
	return json.Marshal(s)
}

// MarshalJSON encodes a view to JSON bytes.
func (v View) MarshalJSON() ([]byte, error) {
	viewProperties, err := MarshalViewPropertiesJSON(v.Properties)
	if err != nil {
		return nil, err
	}

	return json.Marshal(struct {
		ViewContents
		ViewProperties json.RawMessage `json:"properties"`
	}{
		ViewContents:   v.ViewContents,
		ViewProperties: viewProperties,
	})
}

// UnmarshalJSON decodes JSON bytes into the corresponding view type (those that implement ViewProperties).
func (c *View) UnmarshalJSON(b []byte) error {
	if err := json.Unmarshal(b, &c.ViewContents); err != nil {
		return err
	}

	v, err := UnmarshalViewPropertiesJSON(b)
	if err != nil {
		return err
	}
	c.Properties = v
	return nil
}

// UnmarshalJSON decodes JSON bytes into the corresponding view update type (those that implement ViewProperties).
func (u *ViewUpdate) UnmarshalJSON(b []byte) error {
	if err := json.Unmarshal(b, &u.ViewContentsUpdate); err != nil {
		return err
	}

	v, err := UnmarshalViewPropertiesJSON(b)
	if err != nil {
		return err
	}
	u.Properties = v
	return nil
}

// MarshalJSON encodes a view to JSON bytes.
func (u ViewUpdate) MarshalJSON() ([]byte, error) {
	vis, err := MarshalViewPropertiesJSON(u.Properties)
	if err != nil {
		return nil, err
	}

	return json.Marshal(struct {
		ViewContentsUpdate
		ViewProperties json.RawMessage `json:"properties,omitempty"`
	}{
		ViewContentsUpdate: u.ViewContentsUpdate,
		ViewProperties:     vis,
	})
}

// LinePlusSingleStatProperties represents options for line plus single stat view in Chronograf
type LinePlusSingleStatProperties struct {
	Queries                    []DashboardQuery `json:"queries"`
	Axes                       map[string]Axis  `json:"axes"`
	Type                       string           `json:"type"`
	StaticLegend               StaticLegend     `json:"staticLegend"`
	ViewColors                 []ViewColor      `json:"colors"`
	Prefix                     string           `json:"prefix"`
	Suffix                     string           `json:"suffix"`
	DecimalPlaces              DecimalPlaces    `json:"decimalPlaces"`
	Note                       string           `json:"note"`
	ShowNoteWhenEmpty          bool             `json:"showNoteWhenEmpty"`
	XColumn                    string           `json:"xColumn"`
	GenerateXAxisTicks         []string         `json:"generateXAxisTicks"`
	XTotalTicks                int              `json:"xTotalTicks"`
	XTickStart                 float64          `json:"xTickStart"`
	XTickStep                  float64          `json:"xTickStep"`
	YColumn                    string           `json:"yColumn"`
	GenerateYAxisTicks         []string         `json:"generateYAxisTicks"`
	YTotalTicks                int              `json:"yTotalTicks"`
	YTickStart                 float64          `json:"yTickStart"`
	YTickStep                  float64          `json:"yTickStep"`
	ShadeBelow                 bool             `json:"shadeBelow"`
	Position                   string           `json:"position"`
	TimeFormat                 string           `json:"timeFormat"`
	HoverDimension             string           `json:"hoverDimension"`
	LegendColorizeRows         bool             `json:"legendColorizeRows"`
	LegendHide                 bool             `json:"legendHide"`
	LegendOpacity              float64          `json:"legendOpacity"`
	LegendOrientationThreshold int              `json:"legendOrientationThreshold"`
}

// XYViewProperties represents options for line, bar, step, or stacked view in Chronograf
type XYViewProperties struct {
	Queries                    []DashboardQuery `json:"queries"`
	Axes                       map[string]Axis  `json:"axes"`
	Type                       string           `json:"type"`
	StaticLegend               StaticLegend     `json:"staticLegend"`
	Geom                       string           `json:"geom"` // Either "line", "step", "stacked", or "bar"
	ViewColors                 []ViewColor      `json:"colors"`
	Note                       string           `json:"note"`
	ShowNoteWhenEmpty          bool             `json:"showNoteWhenEmpty"`
	XColumn                    string           `json:"xColumn"`
	GenerateXAxisTicks         []string         `json:"generateXAxisTicks"`
	XTotalTicks                int              `json:"xTotalTicks"`
	XTickStart                 float64          `json:"xTickStart"`
	XTickStep                  float64          `json:"xTickStep"`
	YColumn                    string           `json:"yColumn"`
	GenerateYAxisTicks         []string         `json:"generateYAxisTicks"`
	YTotalTicks                int              `json:"yTotalTicks"`
	YTickStart                 float64          `json:"yTickStart"`
	YTickStep                  float64          `json:"yTickStep"`
	ShadeBelow                 bool             `json:"shadeBelow"`
	Position                   string           `json:"position"`
	TimeFormat                 string           `json:"timeFormat"`
	HoverDimension             string           `json:"hoverDimension"`
	LegendColorizeRows         bool             `json:"legendColorizeRows"`
	LegendHide                 bool             `json:"legendHide"`
	LegendOpacity              float64          `json:"legendOpacity"`
	LegendOrientationThreshold int              `json:"legendOrientationThreshold"`
}

// BandViewProperties represents options for the band view
type BandViewProperties struct {
	Queries                    []DashboardQuery `json:"queries"`
	Axes                       map[string]Axis  `json:"axes"`
	Type                       string           `json:"type"`
	StaticLegend               StaticLegend     `json:"staticLegend"`
	Geom                       string           `json:"geom"`
	ViewColors                 []ViewColor      `json:"colors"`
	Note                       string           `json:"note"`
	ShowNoteWhenEmpty          bool             `json:"showNoteWhenEmpty"`
	TimeFormat                 string           `json:"timeFormat"`
	HoverDimension             string           `json:"hoverDimension"`
	XColumn                    string           `json:"xColumn"`
	GenerateXAxisTicks         []string         `json:"generateXAxisTicks"`
	XTotalTicks                int              `json:"xTotalTicks"`
	XTickStart                 float64          `json:"xTickStart"`
	XTickStep                  float64          `json:"xTickStep"`
	YColumn                    string           `json:"yColumn"`
	GenerateYAxisTicks         []string         `json:"generateYAxisTicks"`
	YTotalTicks                int              `json:"yTotalTicks"`
	YTickStart                 float64          `json:"yTickStart"`
	YTickStep                  float64          `json:"yTickStep"`
	UpperColumn                string           `json:"upperColumn"`
	MainColumn                 string           `json:"mainColumn"`
	LowerColumn                string           `json:"lowerColumn"`
	LegendColorizeRows         bool             `json:"legendColorizeRows"`
	LegendHide                 bool             `json:"legendHide"`
	LegendOpacity              float64          `json:"legendOpacity"`
	LegendOrientationThreshold int              `json:"legendOrientationThreshold"`
}

// CheckViewProperties represents options for a view representing a check
type CheckViewProperties struct {
	Type                       string           `json:"type"`
	CheckID                    string           `json:"checkID"`
	Queries                    []DashboardQuery `json:"queries"`
	ViewColors                 []string         `json:"colors"`
	LegendColorizeRows         bool             `json:"legendColorizeRows"`
	LegendHide                 bool             `json:"legendHide"`
	LegendOpacity              float64          `json:"legendOpacity"`
	LegendOrientationThreshold int              `json:"legendOrientationThreshold"`
}

// SingleStatViewProperties represents options for single stat view in Chronograf
type SingleStatViewProperties struct {
	Type              string           `json:"type"`
	Queries           []DashboardQuery `json:"queries"`
	Prefix            string           `json:"prefix"`
	TickPrefix        string           `json:"tickPrefix"`
	Suffix            string           `json:"suffix"`
	TickSuffix        string           `json:"tickSuffix"`
	ViewColors        []ViewColor      `json:"colors"`
	DecimalPlaces     DecimalPlaces    `json:"decimalPlaces"`
	Note              string           `json:"note"`
	ShowNoteWhenEmpty bool             `json:"showNoteWhenEmpty"`
}

// HistogramViewProperties represents options for histogram view in Chronograf
type HistogramViewProperties struct {
	Type                       string           `json:"type"`
	Queries                    []DashboardQuery `json:"queries"`
	ViewColors                 []ViewColor      `json:"colors"`
	XColumn                    string           `json:"xColumn"`
	FillColumns                []string         `json:"fillColumns"`
	XDomain                    []float64        `json:"xDomain,omitempty"`
	XAxisLabel                 string           `json:"xAxisLabel"`
	Position                   string           `json:"position"`
	BinCount                   int              `json:"binCount"`
	Note                       string           `json:"note"`
	ShowNoteWhenEmpty          bool             `json:"showNoteWhenEmpty"`
	LegendColorizeRows         bool             `json:"legendColorizeRows"`
	LegendHide                 bool             `json:"legendHide"`
	LegendOpacity              float64          `json:"legendOpacity"`
	LegendOrientationThreshold int              `json:"legendOrientationThreshold"`
}

// HeatmapViewProperties represents options for heatmap view in Chronograf
type HeatmapViewProperties struct {
	Type                       string           `json:"type"`
	Queries                    []DashboardQuery `json:"queries"`
	ViewColors                 []string         `json:"colors"`
	BinSize                    int32            `json:"binSize"`
	XColumn                    string           `json:"xColumn"`
	GenerateXAxisTicks         []string         `json:"generateXAxisTicks"`
	XTotalTicks                int              `json:"xTotalTicks"`
	XTickStart                 float64          `json:"xTickStart"`
	XTickStep                  float64          `json:"xTickStep"`
	YColumn                    string           `json:"yColumn"`
	GenerateYAxisTicks         []string         `json:"generateYAxisTicks"`
	YTotalTicks                int              `json:"yTotalTicks"`
	YTickStart                 float64          `json:"yTickStart"`
	YTickStep                  float64          `json:"yTickStep"`
	XDomain                    []float64        `json:"xDomain,omitempty"`
	YDomain                    []float64        `json:"yDomain,omitempty"`
	XAxisLabel                 string           `json:"xAxisLabel"`
	YAxisLabel                 string           `json:"yAxisLabel"`
	XPrefix                    string           `json:"xPrefix"`
	XSuffix                    string           `json:"xSuffix"`
	YPrefix                    string           `json:"yPrefix"`
	YSuffix                    string           `json:"ySuffix"`
	Note                       string           `json:"note"`
	ShowNoteWhenEmpty          bool             `json:"showNoteWhenEmpty"`
	TimeFormat                 string           `json:"timeFormat"`
	LegendColorizeRows         bool             `json:"legendColorizeRows"`
	LegendHide                 bool             `json:"legendHide"`
	LegendOpacity              float64          `json:"legendOpacity"`
	LegendOrientationThreshold int              `json:"legendOrientationThreshold"`
}

// ScatterViewProperties represents options for scatter view in Chronograf
type ScatterViewProperties struct {
	Type                       string           `json:"type"`
	Queries                    []DashboardQuery `json:"queries"`
	ViewColors                 []string         `json:"colors"`
	FillColumns                []string         `json:"fillColumns"`
	SymbolColumns              []string         `json:"symbolColumns"`
	XColumn                    string           `json:"xColumn"`
	GenerateXAxisTicks         []string         `json:"generateXAxisTicks"`
	XTotalTicks                int              `json:"xTotalTicks"`
	XTickStart                 float64          `json:"xTickStart"`
	XTickStep                  float64          `json:"xTickStep"`
	YColumn                    string           `json:"yColumn"`
	GenerateYAxisTicks         []string         `json:"generateYAxisTicks"`
	YTotalTicks                int              `json:"yTotalTicks"`
	YTickStart                 float64          `json:"yTickStart"`
	YTickStep                  float64          `json:"yTickStep"`
	XDomain                    []float64        `json:"xDomain,omitempty"`
	YDomain                    []float64        `json:"yDomain,omitempty"`
	XAxisLabel                 string           `json:"xAxisLabel"`
	YAxisLabel                 string           `json:"yAxisLabel"`
	XPrefix                    string           `json:"xPrefix"`
	XSuffix                    string           `json:"xSuffix"`
	YPrefix                    string           `json:"yPrefix"`
	YSuffix                    string           `json:"ySuffix"`
	Note                       string           `json:"note"`
	ShowNoteWhenEmpty          bool             `json:"showNoteWhenEmpty"`
	TimeFormat                 string           `json:"timeFormat"`
	LegendColorizeRows         bool             `json:"legendColorizeRows"`
	LegendHide                 bool             `json:"legendHide"`
	LegendOpacity              float64          `json:"legendOpacity"`
	LegendOrientationThreshold int              `json:"legendOrientationThreshold"`
}

// MosaicViewProperties represents options for mosaic view in Chronograf
type MosaicViewProperties struct {
	Type                       string           `json:"type"`
	Queries                    []DashboardQuery `json:"queries"`
	ViewColors                 []string         `json:"colors"`
	FillColumns                []string         `json:"fillColumns"`
	XColumn                    string           `json:"xColumn"`
	GenerateXAxisTicks         []string         `json:"generateXAxisTicks"`
	XTotalTicks                int              `json:"xTotalTicks"`
	XTickStart                 float64          `json:"xTickStart"`
	XTickStep                  float64          `json:"xTickStep"`
	YLabelColumnSeparator      string           `json:"yLabelColumnSeparator"`
	YLabelColumns              []string         `json:"yLabelColumns"`
	YSeriesColumns             []string         `json:"ySeriesColumns"`
	XDomain                    []float64        `json:"xDomain,omitempty"`
	YDomain                    []float64        `json:"yDomain,omitempty"`
	XAxisLabel                 string           `json:"xAxisLabel"`
	YAxisLabel                 string           `json:"yAxisLabel"`
	XPrefix                    string           `json:"xPrefix"`
	XSuffix                    string           `json:"xSuffix"`
	YPrefix                    string           `json:"yPrefix"`
	YSuffix                    string           `json:"ySuffix"`
	Note                       string           `json:"note"`
	ShowNoteWhenEmpty          bool             `json:"showNoteWhenEmpty"`
	TimeFormat                 string           `json:"timeFormat"`
	HoverDimension             string           `json:"hoverDimension"`
	LegendColorizeRows         bool             `json:"legendColorizeRows"`
	LegendHide                 bool             `json:"legendHide"`
	LegendOpacity              float64          `json:"legendOpacity"`
	LegendOrientationThreshold int              `json:"legendOrientationThreshold"`
}

// GaugeViewProperties represents options for gauge view in Chronograf
type GaugeViewProperties struct {
	Type              string           `json:"type"`
	Queries           []DashboardQuery `json:"queries"`
	Prefix            string           `json:"prefix"`
	TickPrefix        string           `json:"tickPrefix"`
	Suffix            string           `json:"suffix"`
	TickSuffix        string           `json:"tickSuffix"`
	ViewColors        []ViewColor      `json:"colors"`
	DecimalPlaces     DecimalPlaces    `json:"decimalPlaces"`
	Note              string           `json:"note"`
	ShowNoteWhenEmpty bool             `json:"showNoteWhenEmpty"`
}

// Geographical coordinates
type Datum struct {
	Lat float64 `json:"lat"`
	Lon float64 `json:"lon"`
}

// Single visualization layer properties of a chronograf map widget
type GeoLayer struct {
	Type           string `json:"type"`
	RadiusField    string `json:"radiusField"`
	ColorField     string `json:"colorField"`
	IntensityField string `json:"intensityField"`
	// circle layer properties
	ViewColors         []ViewColor `json:"colors"`
	Radius             int32       `json:"radius"`
	Blur               int32       `json:"blur"`
	RadiusDimension    Axis        `json:"radiusDimension,omitempty"`
	ColorDimension     Axis        `json:"colorDimension,omitempty"`
	IntensityDimension Axis        `json:"intensityDimension,omitempty"`
	InterpolateColors  bool        `json:"interpolateColors"`
	// track layer properties
	TrackWidth   int32 `json:"trackWidth"`
	Speed        int32 `json:"speed"`
	RandomColors bool  `json:"randomColors"`
	// point layer properties
	IsClustered bool `json:"isClustered"`
}

// GeoViewProperties represents options for map view in Chronograf
type GeoViewProperties struct {
	Type                   string           `json:"type"`
	Queries                []DashboardQuery `json:"queries"`
	Center                 Datum            `json:"center"`
	Zoom                   float64          `json:"zoom"`
	MapStyle               string           `json:"mapStyle"`
	AllowPanAndZoom        bool             `json:"allowPanAndZoom"`
	DetectCoordinateFields bool             `json:"detectCoordinateFields"`
	ViewColor              []ViewColor      `json:"colors"`
	GeoLayers              []GeoLayer       `json:"layers"`
	Note                   string           `json:"note"`
	ShowNoteWhenEmpty      bool             `json:"showNoteWhenEmpty"`
}

// TableViewProperties represents options for table view in Chronograf
type TableViewProperties struct {
	Type              string           `json:"type"`
	Queries           []DashboardQuery `json:"queries"`
	ViewColors        []ViewColor      `json:"colors"`
	TableOptions      TableOptions     `json:"tableOptions"`
	FieldOptions      []RenamableField `json:"fieldOptions"`
	TimeFormat        string           `json:"timeFormat"`
	DecimalPlaces     DecimalPlaces    `json:"decimalPlaces"`
	Note              string           `json:"note"`
	ShowNoteWhenEmpty bool             `json:"showNoteWhenEmpty"`
}

type MarkdownViewProperties struct {
	Type string `json:"type"`
	Note string `json:"note"`
}

// LogViewProperties represents options for log viewer in Chronograf.
type LogViewProperties struct {
	Type    string            `json:"type"`
	Columns []LogViewerColumn `json:"columns"`
}

// LogViewerColumn represents a specific column in a Log Viewer.
type LogViewerColumn struct {
	Name     string             `json:"name"`
	Position int32              `json:"position"`
	Settings []LogColumnSetting `json:"settings"`
}

// LogColumnSetting represent the settings for a specific column of a Log Viewer.
type LogColumnSetting struct {
	Type  string `json:"type"`
	Value string `json:"value"`
	Name  string `json:"name,omitempty"`
}

func (XYViewProperties) viewProperties()             {}
func (BandViewProperties) viewProperties()           {}
func (LinePlusSingleStatProperties) viewProperties() {}
func (SingleStatViewProperties) viewProperties()     {}
func (HistogramViewProperties) viewProperties()      {}
func (HeatmapViewProperties) viewProperties()        {}
func (ScatterViewProperties) viewProperties()        {}
func (MosaicViewProperties) viewProperties()         {}
func (GaugeViewProperties) viewProperties()          {}
func (GeoViewProperties) viewProperties()            {}
func (TableViewProperties) viewProperties()          {}
func (MarkdownViewProperties) viewProperties()       {}
func (LogViewProperties) viewProperties()            {}
func (CheckViewProperties) viewProperties()          {}

func (v XYViewProperties) GetType() string             { return v.Type }
func (v BandViewProperties) GetType() string           { return v.Type }
func (v LinePlusSingleStatProperties) GetType() string { return v.Type }
func (v SingleStatViewProperties) GetType() string     { return v.Type }
func (v HistogramViewProperties) GetType() string      { return v.Type }
func (v HeatmapViewProperties) GetType() string        { return v.Type }
func (v ScatterViewProperties) GetType() string        { return v.Type }
func (v MosaicViewProperties) GetType() string         { return v.Type }
func (v GaugeViewProperties) GetType() string          { return v.Type }
func (v GeoViewProperties) GetType() string            { return v.Type }
func (v TableViewProperties) GetType() string          { return v.Type }
func (v MarkdownViewProperties) GetType() string       { return v.Type }
func (v LogViewProperties) GetType() string            { return v.Type }
func (v CheckViewProperties) GetType() string          { return v.Type }

/////////////////////////////
// Old Chronograf Types
/////////////////////////////

// DashboardQuery represents a query used in a dashboard cell
type DashboardQuery struct {
	Text          string        `json:"text"`
	EditMode      string        `json:"editMode"` // Either "builder" or "advanced"
	Name          string        `json:"name"`     // Term or phrase that refers to the query
	BuilderConfig BuilderConfig `json:"builderConfig"`
}

type BuilderConfig struct {
	Buckets []string `json:"buckets"`
	Tags    []struct {
		Key                   string   `json:"key"`
		Values                []string `json:"values"`
		AggregateFunctionType string   `json:"aggregateFunctionType"`
	} `json:"tags"`
	Functions []struct {
		Name string `json:"name"`
	} `json:"functions"`
	AggregateWindow struct {
		Period     string `json:"period"`
		FillValues bool   `json:"fillValues"`
	} `json:"aggregateWindow"`
}

// MarshalJSON is necessary for the time being. UI keeps breaking
// b/c it relies on these slices being populated/not nil. Other
// consumers may have same issue.
func (b BuilderConfig) MarshalJSON() ([]byte, error) {
	type alias BuilderConfig
	copyCfg := alias(b)
	if copyCfg.Buckets == nil {
		copyCfg.Buckets = []string{}
	}
	if copyCfg.Tags == nil {
		copyCfg.Tags = []struct {
			Key                   string   `json:"key"`
			Values                []string `json:"values"`
			AggregateFunctionType string   `json:"aggregateFunctionType"`
		}{}
	}
	if copyCfg.Functions == nil {
		copyCfg.Functions = []struct {
			Name string `json:"name"`
		}{}
	}
	return json.Marshal(copyCfg)
}

// NewBuilderTag is a constructor for the builder config types. This
// isn't technically required, but working with struct literals with embedded
// struct tags is really painful. This is to get around that bit. Would be nicer
// to have these as actual types maybe.
func NewBuilderTag(key string, functionType string, values ...string) struct {
	Key                   string   `json:"key"`
	Values                []string `json:"values"`
	AggregateFunctionType string   `json:"aggregateFunctionType"`
} {
	return struct {
		Key                   string   `json:"key"`
		Values                []string `json:"values"`
		AggregateFunctionType string   `json:"aggregateFunctionType"`
	}{
		Key:                   key,
		Values:                values,
		AggregateFunctionType: functionType,
	}
}

// Axis represents the visible extents of a visualization
type Axis struct {
	Bounds       []string `json:"bounds"` // bounds are an arbitrary list of client-defined strings that specify the viewport for a View
	LegacyBounds [2]int64 `json:"-"`      // legacy bounds are for testing a migration from an earlier version of axis
	Label        string   `json:"label"`  // label is a description of this Axis
	Prefix       string   `json:"prefix"` // Prefix represents a label prefix for formatting axis values
	Suffix       string   `json:"suffix"` // Suffix represents a label suffix for formatting axis values
	Base         string   `json:"base"`   // Base represents the radix for formatting axis values
	Scale        string   `json:"scale"`  // Scale is the axis formatting scale. Supported: "log", "linear"
}

// ViewColor represents the encoding of data into visualizations
type ViewColor struct {
	ID    string  `json:"id"`    // ID is the unique id of the View color
	Type  string  `json:"type"`  // Type is how the color is used. Accepted (min,max,threshold)
	Hex   string  `json:"hex"`   // Hex is the hex number of the color
	Name  string  `json:"name"`  // Name is the user-facing name of the hex color
	Value float64 `json:"value"` // Value is the data value mapped to this color
}

// StaticLegend represents the options specific to the static legend
type StaticLegend struct {
	ColorizeRows         bool    `json:"colorizeRows,omitempty"`
	HeightRatio          float64 `json:"heightRatio,omitempty"`
	Show                 bool    `json:"show,omitempty"`
	Opacity              float64 `json:"opacity,omitempty"`
	OrientationThreshold int     `json:"orientationThreshold,omitempty"`
	ValueAxis            string  `json:"valueAxis,omitempty"`
	WidthRatio           float64 `json:"widthRatio,omitempty"`
}

// TableOptions is a type of options for a DashboardView with type Table
type TableOptions struct {
	VerticalTimeAxis bool           `json:"verticalTimeAxis"`
	SortBy           RenamableField `json:"sortBy"`
	Wrapping         string         `json:"wrapping"`
	FixFirstColumn   bool           `json:"fixFirstColumn"`
}

// RenamableField is a column/row field in a DashboardView of type Table
type RenamableField struct {
	InternalName string `json:"internalName"`
	DisplayName  string `json:"displayName"`
	Visible      bool   `json:"visible"`
}

// DecimalPlaces indicates whether decimal places should be enforced, and how many digits it should show.
type DecimalPlaces struct {
	IsEnforced bool  `json:"isEnforced"`
	Digits     int32 `json:"digits"`
}
