package d2target

import (
	
	
	
	
	
	

	

	
	
	
	
	
	
)

const (
	DEFAULT_ICON_SIZE = 32
	MAX_ICON_SIZE     = 64

	SHADOW_SIZE_X    = 3
	SHADOW_SIZE_Y    = 5
	THREE_DEE_OFFSET = 15
	MULTIPLE_OFFSET  = 10

	INNER_BORDER_OFFSET = 5

	BG_COLOR = color.N7
	FG_COLOR = color.N1

	MIN_ARROWHEAD_STROKE_WIDTH = 2
	ARROWHEAD_PADDING          = 2.
)

var BorderOffset = geo.NewVector(5, 5)

type Config struct {
	Sketch             *bool           `json:"sketch"`
	ThemeID            *int64          `json:"themeID"`
	DarkThemeID        *int64          `json:"darkThemeID"`
	Pad                *int64          `json:"pad"`
	Center             *bool           `json:"center"`
	LayoutEngine       *string         `json:"layoutEngine"`
	ThemeOverrides     *ThemeOverrides `json:"themeOverrides,omitempty"`
	DarkThemeOverrides *ThemeOverrides `json:"darkThemeOverrides,omitempty"`
	// Data is a data structure for holding user-defined data
	// useful for plugins that allow users to configure within source code
	Data map[string]interface{} `json:"data,omitempty"`
}

type ThemeOverrides struct {
	N1  *string `json:"n1"`
	N2  *string `json:"n2"`
	N3  *string `json:"n3"`
	N4  *string `json:"n4"`
	N5  *string `json:"n5"`
	N6  *string `json:"n6"`
	N7  *string `json:"n7"`
	B1  *string `json:"b1"`
	B2  *string `json:"b2"`
	B3  *string `json:"b3"`
	B4  *string `json:"b4"`
	B5  *string `json:"b5"`
	B6  *string `json:"b6"`
	AA2 *string `json:"aa2"`
	AA4 *string `json:"aa4"`
	AA5 *string `json:"aa5"`
	AB4 *string `json:"ab4"`
	AB5 *string `json:"ab5"`
}

type Diagram struct {
	Name   string  `json:"name"`
	Config *Config `json:"config,omitempty"`
	// See docs on the same field in d2graph to understand what it means.
	IsFolderOnly bool                `json:"isFolderOnly"`
	Description  string              `json:"description,omitempty"`
	FontFamily   *d2fonts.FontFamily `json:"fontFamily,omitempty"`

	Shapes      []Shape      `json:"shapes"`
	Connections []Connection `json:"connections"`

	Root Shape `json:"root"`
	// Maybe Icon can be used as a watermark in the root shape

	Layers    []*Diagram `json:"layers,omitempty"`
	Scenarios []*Diagram `json:"scenarios,omitempty"`
	Steps     []*Diagram `json:"steps,omitempty"`
}

func ( *Diagram) ( []string) *Diagram {
	if len() == 0 {
		return 
	}

	 := [0]

	if len() == 1 && .Name ==  {
		return 
	}

	switch  {
	case "layers":
		if len() < 2 {
			return nil
		}
		for ,  := range .Layers {
			if .Name == [1] {
				return .([2:])
			}
		}
	case "scenarios":
		if len() < 2 {
			return nil
		}
		for ,  := range .Scenarios {
			if .Name == [1] {
				return .([2:])
			}
		}
	case "steps":
		if len() < 2 {
			return nil
		}
		for ,  := range .Steps {
			if .Name == [1] {
				return .([2:])
			}
		}
	}

	for ,  := range .Layers {
		if .Name ==  {
			return .([1:])
		}
	}
	for ,  := range .Scenarios {
		if .Name ==  {
			return .([1:])
		}
	}
	for ,  := range .Steps {
		if .Name ==  {
			return .([1:])
		}
	}
	return nil
}

func ( Diagram) () ([]byte, error) {
	,  := json.Marshal(.Shapes)
	if  != nil {
		return nil, 
	}
	,  := json.Marshal(.Connections)
	if  != nil {
		return nil, 
	}
	,  := json.Marshal(.Root)
	if  != nil {
		return nil, 
	}
	 := append(append(, ...), ...)

	if .Config != nil {
		,  := json.Marshal(.Config)
		if  != nil {
			return nil, 
		}
		 = append(, ...)
	}

	for ,  := range .Layers {
		,  := .()
		if  != nil {
			return nil, 
		}
		 = append(, ...)
	}
	for ,  := range .Scenarios {
		,  := .()
		if  != nil {
			return nil, 
		}
		 = append(, ...)
	}
	for ,  := range .Steps {
		,  := .()
		if  != nil {
			return nil, 
		}
		 = append(, ...)
	}

	return , nil
}

func ( Diagram) ( func(Shape) bool) bool {
	for ,  := range .Layers {
		if .() {
			return true
		}
	}
	for ,  := range .Scenarios {
		if .() {
			return true
		}
	}
	for ,  := range .Steps {
		if .() {
			return true
		}
	}
	for ,  := range .Shapes {
		if () {
			return true
		}
	}
	return false
}

func ( Diagram) ( *string) (string, error) {
	,  := .Bytes()
	if  != nil {
		return "", 
	}
	 := fnv.New32a()
	.Write()
	if  != nil {
		.Write([]byte(*))
	}
	// CSS names can't start with numbers, so prepend a little something
	return fmt.Sprintf("d2-%d", .Sum32()), nil
}

func ( Diagram) () (,  Point) {
	,  := .BoundingBox()
	for ,  := range .Layers {
		,  := .()
		.X = go2.Min(.X, .X)
		.Y = go2.Min(.Y, .Y)
		.X = go2.Max(.X, .X)
		.Y = go2.Max(.Y, .Y)
	}
	for ,  := range .Scenarios {
		,  := .()
		.X = go2.Min(.X, .X)
		.Y = go2.Min(.Y, .Y)
		.X = go2.Max(.X, .X)
		.Y = go2.Max(.Y, .Y)
	}
	for ,  := range .Steps {
		,  := .()
		.X = go2.Min(.X, .X)
		.Y = go2.Min(.Y, .Y)
		.X = go2.Max(.X, .X)
		.Y = go2.Max(.Y, .Y)
	}
	return , 
}

func ( Diagram) () (,  Point) {
	if len(.Shapes) == 0 {
		return Point{0, 0}, Point{0, 0}
	}
	 := int(math.MaxInt32)
	 := int(math.MaxInt32)
	 := int(math.MinInt32)
	 := int(math.MinInt32)

	for ,  := range .Shapes {
		 = go2.Min(, .Pos.X-int(math.Ceil(float64(.StrokeWidth)/2.)))
		 = go2.Min(, .Pos.Y-int(math.Ceil(float64(.StrokeWidth)/2.)))
		 = go2.Max(, .Pos.X+.Width+int(math.Ceil(float64(.StrokeWidth)/2.)))
		 = go2.Max(, .Pos.Y+.Height+int(math.Ceil(float64(.StrokeWidth)/2.)))

		if .Tooltip != "" || .Link != "" {
			// 16 is the icon radius
			 = go2.Min(, .Pos.Y-.StrokeWidth-16)
			 = go2.Max(, .Pos.X+.StrokeWidth+.Width+16)
		}
		if .Shadow {
			 = go2.Max(, .Pos.Y+.Height+int(math.Ceil(float64(.StrokeWidth)/2.))+SHADOW_SIZE_Y)
			 = go2.Max(, .Pos.X+.Width+int(math.Ceil(float64(.StrokeWidth)/2.))+SHADOW_SIZE_X)
		}

		if .ThreeDee {
			 := THREE_DEE_OFFSET
			if .Type == ShapeHexagon {
				 /= 2
			}
			 = go2.Min(, .Pos.Y--.StrokeWidth)
			 = go2.Max(, .Pos.X+THREE_DEE_OFFSET+.Width+.StrokeWidth)
		}
		if .Multiple {
			 = go2.Min(, .Pos.Y-MULTIPLE_OFFSET-.StrokeWidth)
			 = go2.Max(, .Pos.X+MULTIPLE_OFFSET+.Width+.StrokeWidth)
		}

		if .Icon != nil && label.FromString(.IconPosition).IsOutside() {
			 := geo.NewBox(geo.NewPoint(0, 0), float64(.Width), float64(.Height))
			 := shape.NewShape(.Type, )
			 := GetIconSize(.GetInnerBox(), .IconPosition)

			if strings.HasPrefix(.IconPosition, "OUTSIDE_TOP") {
				 = go2.Min(, .Pos.Y-label.PADDING-)
			} else if strings.HasPrefix(.IconPosition, "OUTSIDE_BOTTOM") {
				 = go2.Max(, .Pos.Y+.Height+label.PADDING+)
			} else if strings.HasPrefix(.IconPosition, "OUTSIDE_LEFT") {
				 = go2.Min(, .Pos.X-label.PADDING-)
			} else if strings.HasPrefix(.IconPosition, "OUTSIDE_RIGHT") {
				 = go2.Max(, .Pos.X+.Width+label.PADDING+)
			}
		}

		if .Label != "" {
			 := label.FromString(.LabelPosition)

			 := DSL_SHAPE_TO_SHAPE_TYPE[.Type]
			 := shape.NewShape(,
				geo.NewBox(
					geo.NewPoint(float64(.Pos.X), float64(.Pos.Y)),
					float64(.Width),
					float64(.Height),
				),
			)

			 := .GetPointOnBox(.GetBox(), label.PADDING, float64(.LabelWidth), float64(.LabelHeight))
			if .ThreeDee {
				 := THREE_DEE_OFFSET
				if .Type == ShapeHexagon {
					 /= 2
				}
				if strings.HasPrefix(.LabelPosition, "OUTSIDE_RIGHT") {
					.X += float64()
				}
				if strings.HasPrefix(.LabelPosition, "OUTSIDE_TOP") {
					.Y -= float64()
				}
			}
			 = go2.Min(, int(.X))
			 = go2.Min(, int(.Y))
			 = go2.Max(, int(.X)+.LabelWidth)
			 = go2.Max(, int(.Y)+.LabelHeight)
		}
	}

	for ,  := range .Connections {
		for ,  := range .Route {
			 = go2.Min(, int(math.Floor(.X))-int(math.Ceil(float64(.StrokeWidth)/2.)))
			 = go2.Min(, int(math.Floor(.Y))-int(math.Ceil(float64(.StrokeWidth)/2.)))
			 = go2.Max(, int(math.Ceil(.X))+int(math.Ceil(float64(.StrokeWidth)/2.)))
			 = go2.Max(, int(math.Ceil(.Y))+int(math.Ceil(float64(.StrokeWidth)/2.)))
		}

		if .Label != "" {
			 := .GetLabelTopLeft()
			 = go2.Min(, int(.X))
			 = go2.Min(, int(.Y))
			 = go2.Max(, int(.X)+.LabelWidth)
			 = go2.Max(, int(.Y)+.LabelHeight)
		}
		if .SrcLabel != nil && .SrcLabel.Label != "" {
			 := .GetArrowheadLabelPosition(false)
			 = go2.Min(, int(.X))
			 = go2.Min(, int(.Y))
			 = go2.Max(, int(.X)+.SrcLabel.LabelWidth)
			 = go2.Max(, int(.Y)+.SrcLabel.LabelHeight)
		}
		if .DstLabel != nil && .DstLabel.Label != "" {
			 := .GetArrowheadLabelPosition(true)
			 = go2.Min(, int(.X))
			 = go2.Min(, int(.Y))
			 = go2.Max(, int(.X)+.DstLabel.LabelWidth)
			 = go2.Max(, int(.Y)+.DstLabel.LabelHeight)
		}
	}

	return Point{, }, Point{, }
}

func ( Diagram) () string {
	 := .GetCorpus()
	for ,  := range .Layers {
		 += .()
	}
	for ,  := range .Scenarios {
		 += .()
	}
	for ,  := range .Steps {
		 += .()
	}

	return 
}

func ( Diagram) () string {
	var  string
	 := 0
	for ,  := range .Shapes {
		 += .Label
		if .Tooltip != "" {
			 += .Tooltip
			++
			 += fmt.Sprint()
		}
		if .Link != "" {
			 += .Link
			++
			 += fmt.Sprint()
		}
		 += .PrettyLink
		if .Type == ShapeClass {
			for ,  := range .Fields {
				 += .Text(0).Text + .VisibilityToken()
			}
			for ,  := range .Methods {
				 += .Text(0).Text + .VisibilityToken()
			}
		}
		if .Type == ShapeSQLTable {
			for ,  := range .Columns {
				for ,  := range .Texts(0) {
					 += .Text
				}
				 += .ConstraintAbbr()
			}
		}
	}
	for ,  := range .Connections {
		 += .Label
		if .SrcLabel != nil {
			 += .SrcLabel.Label
		}
		if .DstLabel != nil {
			 += .DstLabel.Label
		}
	}

	return 
}

func () *Diagram {
	return &Diagram{
		Root: Shape{
			Fill: BG_COLOR,
		},
	}
}

type Shape struct {
	ID   string `json:"id"`
	Type string `json:"type"`

	Classes []string `json:"classes,omitempty"`

	Pos    Point `json:"pos"`
	Width  int   `json:"width"`
	Height int   `json:"height"`

	Opacity     float64 `json:"opacity"`
	StrokeDash  float64 `json:"strokeDash"`
	StrokeWidth int     `json:"strokeWidth"`

	BorderRadius int `json:"borderRadius"`

	Fill        string `json:"fill"`
	FillPattern string `json:"fillPattern,omitempty"`
	Stroke      string `json:"stroke"`

	Animated     bool `json:"animated"`
	Shadow       bool `json:"shadow"`
	ThreeDee     bool `json:"3d"`
	Multiple     bool `json:"multiple"`
	DoubleBorder bool `json:"double-border"`

	Tooltip      string   `json:"tooltip"`
	Link         string   `json:"link"`
	PrettyLink   string   `json:"prettyLink,omitempty"`
	Icon         *url.URL `json:"icon"`
	IconPosition string   `json:"iconPosition"`

	// Whether the shape should allow shapes behind it to bleed through
	// Currently just used for sequence diagram groups
	Blend bool `json:"blend"`

	Class
	SQLTable

	ContentAspectRatio *float64 `json:"contentAspectRatio,omitempty"`

	Text

	LabelPosition string `json:"labelPosition,omitempty"`

	ZIndex int `json:"zIndex"`
	Level  int `json:"level"`

	// These are used for special shapes, sql_table and class
	PrimaryAccentColor   string `json:"primaryAccentColor,omitempty"`
	SecondaryAccentColor string `json:"secondaryAccentColor,omitempty"`
	NeutralAccentColor   string `json:"neutralAccentColor,omitempty"`
}

func ( Shape) () string {
	if .Type == ShapeClass || .Type == ShapeSQLTable {
		if !color.IsThemeColor(.Color) {
			return .Color
		}
		return .Stroke
	}
	if .Color != color.Empty {
		return .Color
	}
	return color.N1
}

// TODO remove this function, just set fields on themeable
func ( Shape) () string {
	 := ""

	 += fmt.Sprintf(`stroke-width:%d;`, .StrokeWidth)
	if .StrokeDash != 0 {
		,  := svg.GetStrokeDashAttributes(float64(.StrokeWidth), .StrokeDash)
		 += fmt.Sprintf(`stroke-dasharray:%f,%f;`, , )
	}

	return 
}

func ( *Shape) ( string) {
	// Some types are synonyms of other types, but with hinting for autolayout
	// They should only have one representation in the final export
	if strings.EqualFold(, ShapeCircle) {
		 = ShapeOval
	} else if strings.EqualFold(, ShapeSquare) {
		 = ShapeRectangle
	}
	.Type = strings.ToLower()
}

func ( Shape) () int {
	return .ZIndex
}

func ( Shape) () string {
	return .ID
}

type Text struct {
	Label      string `json:"label"`
	FontSize   int    `json:"fontSize"`
	FontFamily string `json:"fontFamily"`
	Language   string `json:"language"`
	Color      string `json:"color"`

	Italic    bool `json:"italic"`
	Bold      bool `json:"bold"`
	Underline bool `json:"underline"`

	LabelWidth  int    `json:"labelWidth"`
	LabelHeight int    `json:"labelHeight"`
	LabelFill   string `json:"labelFill,omitempty"`
}

func () *Shape {
	return &Shape{
		Opacity:     1,
		StrokeDash:  0,
		StrokeWidth: 2,
		Text: Text{
			Bold:       true,
			FontFamily: "DEFAULT",
		},
	}
}

type Connection struct {
	ID string `json:"id"`

	Classes []string `json:"classes,omitempty"`

	Src      string    `json:"src"`
	SrcArrow Arrowhead `json:"srcArrow"`
	SrcLabel *Text     `json:"srcLabel,omitempty"`

	Dst      string    `json:"dst"`
	DstArrow Arrowhead `json:"dstArrow"`
	DstLabel *Text     `json:"dstLabel,omitempty"`

	Opacity      float64 `json:"opacity"`
	StrokeDash   float64 `json:"strokeDash"`
	StrokeWidth  int     `json:"strokeWidth"`
	Stroke       string  `json:"stroke"`
	Fill         string  `json:"fill,omitempty"`
	BorderRadius float64 `json:"borderRadius,omitempty"`

	Text
	LabelPosition   string  `json:"labelPosition"`
	LabelPercentage float64 `json:"labelPercentage"`

	Link       string `json:"link"`
	PrettyLink string `json:"prettyLink,omitempty"`

	Route   []*geo.Point `json:"route"`
	IsCurve bool         `json:"isCurve,omitempty"`

	Animated bool     `json:"animated"`
	Tooltip  string   `json:"tooltip"`
	Icon     *url.URL `json:"icon"`

	ZIndex int `json:"zIndex"`
}

func () *Connection {
	return &Connection{
		SrcArrow:     NoArrowhead,
		DstArrow:     NoArrowhead,
		Route:        make([]*geo.Point, 0),
		Opacity:      1,
		StrokeDash:   0,
		StrokeWidth:  2,
		BorderRadius: 10,
		Text: Text{
			Italic:     true,
			FontFamily: "DEFAULT",
		},
	}
}

func ( Connection) () string {
	if .Color != color.Empty {
		return .Color
	}
	return color.N1
}

func ( Connection) () string {
	 := ""

	 += fmt.Sprintf(`stroke-width:%d;`, .StrokeWidth)
	 := .StrokeDash
	if  == 0 && .Animated {
		 = 5
	}
	if  != 0 {
		,  := svg.GetStrokeDashAttributes(float64(.StrokeWidth), )
		 += fmt.Sprintf(`stroke-dasharray:%f,%f;`, , )

		if .Animated {
			 := -10
			if .SrcArrow != NoArrowhead && .DstArrow == NoArrowhead {
				 = 10
			}
			 += fmt.Sprintf(`stroke-dashoffset:%f;`, float64()*(+))
			 += fmt.Sprintf(`animation: dashdraw %fs linear infinite;`, *0.5)
		}
	}
	return 
}

func ( *Connection) () *geo.Point {
	,  := label.FromString(.LabelPosition).GetPointOnRoute(
		.Route,
		float64(.StrokeWidth),
		.LabelPercentage,
		float64(.LabelWidth),
		float64(.LabelHeight),
	)
	return 
}

func ( *Connection) ( bool) *geo.Point {
	var ,  float64
	if  {
		 = float64(.DstLabel.LabelWidth)
		 = float64(.DstLabel.LabelHeight)
	} else {
		 = float64(.SrcLabel.LabelWidth)
		 = float64(.SrcLabel.LabelHeight)
	}

	// get the start/end points of edge segment with arrowhead
	 := 0
	if  {
		 = len(.Route) - 2
	}
	,  := .Route[], .Route[+1]
	// Note: end to start to get normal towards unlocked top position
	,  := geo.GetUnitNormalVector(.X, .Y, .X, .Y)

	// determine how much to move the label back from the very end of the edge
	// e.g. if normal points up {x: 0, y:1}, shift width/2 + padding to fit
	 := math.Abs()*(/2.+label.PADDING) +
		math.Abs()*(/2.+label.PADDING)

	 := geo.Route(.Route).Length()
	var  float64
	if  {
		 = 1.
		if  > 0 {
			 -=  / 
		}
	} else {
		 = 0.
		if  > 0 {
			 =  / 
		}
	}

	 := float64(.StrokeWidth)

	,  := label.UnlockedTop.GetPointOnRoute(.Route, , , , )

	var  float64
	if  && .DstArrow != NoArrowhead {
		// Note: these dimensions are for rendering arrowheads on their side so we want the height
		_,  = .DstArrow.Dimensions()
	} else if .SrcArrow != NoArrowhead {
		_,  = .SrcArrow.Dimensions()
	}

	if  > 0 {
		// labelTL already accounts for strokeWidth and padding, we only want to shift further if the arrow is larger than this
		 := (/2 + ARROWHEAD_PADDING) - /2 - label.PADDING
		if  > 0 {
			.X +=  * 
			.Y +=  * 
		}
	}

	return 
}

func ( Connection) () int {
	return .ZIndex
}

func ( Connection) () string {
	return .ID
}

type Arrowhead string

const (
	NoArrowhead               Arrowhead = "none"
	ArrowArrowhead            Arrowhead = "arrow"
	UnfilledTriangleArrowhead Arrowhead = "unfilled-triangle"
	TriangleArrowhead         Arrowhead = "triangle"
	DiamondArrowhead          Arrowhead = "diamond"
	FilledDiamondArrowhead    Arrowhead = "filled-diamond"
	CircleArrowhead           Arrowhead = "circle"
	FilledCircleArrowhead     Arrowhead = "filled-circle"
	BoxArrowhead              Arrowhead = "box"
	FilledBoxArrowhead        Arrowhead = "filled-box"

	// For fat arrows
	LineArrowhead Arrowhead = "line"

	// Crows feet notation
	CfOne          Arrowhead = "cf-one"
	CfMany         Arrowhead = "cf-many"
	CfOneRequired  Arrowhead = "cf-one-required"
	CfManyRequired Arrowhead = "cf-many-required"

	DefaultArrowhead Arrowhead = TriangleArrowhead
)

// valid values for arrowhead.shape
var Arrowheads = map[string]struct{}{
	string(NoArrowhead):       {},
	string(ArrowArrowhead):    {},
	string(TriangleArrowhead): {},
	string(DiamondArrowhead):  {},
	string(CircleArrowhead):   {},
	string(BoxArrowhead):      {},
	string(CfOne):             {},
	string(CfMany):            {},
	string(CfOneRequired):     {},
	string(CfManyRequired):    {},
}

func ( string,  *bool) Arrowhead {
	switch  {
	case string(DiamondArrowhead):
		if  != nil && * {
			return FilledDiamondArrowhead
		}
		return DiamondArrowhead
	case string(CircleArrowhead):
		if  != nil && * {
			return FilledCircleArrowhead
		}
		return CircleArrowhead
	case string(NoArrowhead):
		return NoArrowhead
	case string(ArrowArrowhead):
		return ArrowArrowhead
	case string(TriangleArrowhead):
		if  != nil && !(*) {
			return UnfilledTriangleArrowhead
		}
		return TriangleArrowhead
	case string(BoxArrowhead):
		if  != nil && * {
			return FilledBoxArrowhead
		}
		return BoxArrowhead
	case string(CfOne):
		return CfOne
	case string(CfMany):
		return CfMany
	case string(CfOneRequired):
		return CfOneRequired
	case string(CfManyRequired):
		return CfManyRequired
	default:
		if DefaultArrowhead == TriangleArrowhead &&
			 != nil && !(*) {
			return UnfilledTriangleArrowhead
		}
		return DefaultArrowhead
	}
}

func ( Arrowhead) ( float64) (,  float64) {
	var ,  float64
	var ,  float64
	switch  {
	case ArrowArrowhead:
		 = 4
		 = 4
		 = 4
		 = 4
	case TriangleArrowhead:
		 = 4
		 = 4
		 = 3
		 = 4
	case UnfilledTriangleArrowhead:
		 = 7
		 = 7
		 = 3
		 = 4
	case LineArrowhead:
		 = 5
		 = 8
	case FilledDiamondArrowhead:
		 = 11
		 = 7
		 = 5.5
		 = 3.5
	case DiamondArrowhead:
		 = 11
		 = 9
		 = 5.5
		 = 4.5
	case FilledCircleArrowhead, CircleArrowhead:
		 = 8
		 = 8
		 = 5
		 = 5
	case FilledBoxArrowhead, BoxArrowhead:
		 = 6
		 = 6
		 = 5
		 = 5
	case CfOne, CfMany, CfOneRequired, CfManyRequired:
		 = 9
		 = 9
		 = 4.5
		 = 4.5
	}

	 := go2.Max(MIN_ARROWHEAD_STROKE_WIDTH, )
	return  + *,  + *
}

type Point struct {
	X int `json:"x"`
	Y int `json:"y"`
}

func (,  int) Point {
	return Point{X: , Y: }
}

const (
	ShapeRectangle       = "rectangle"
	ShapeSquare          = "square"
	ShapePage            = "page"
	ShapeParallelogram   = "parallelogram"
	ShapeDocument        = "document"
	ShapeCylinder        = "cylinder"
	ShapeQueue           = "queue"
	ShapePackage         = "package"
	ShapeStep            = "step"
	ShapeCallout         = "callout"
	ShapeStoredData      = "stored_data"
	ShapePerson          = "person"
	ShapeDiamond         = "diamond"
	ShapeOval            = "oval"
	ShapeCircle          = "circle"
	ShapeHexagon         = "hexagon"
	ShapeCloud           = "cloud"
	ShapeText            = "text"
	ShapeCode            = "code"
	ShapeClass           = "class"
	ShapeSQLTable        = "sql_table"
	ShapeImage           = "image"
	ShapeSequenceDiagram = "sequence_diagram"
	ShapeHierarchy       = "hierarchy"
)

var Shapes = []string{
	ShapeRectangle,
	ShapeSquare,
	ShapePage,
	ShapeParallelogram,
	ShapeDocument,
	ShapeCylinder,
	ShapeQueue,
	ShapePackage,
	ShapeStep,
	ShapeCallout,
	ShapeStoredData,
	ShapePerson,
	ShapeDiamond,
	ShapeOval,
	ShapeCircle,
	ShapeHexagon,
	ShapeCloud,
	ShapeText,
	ShapeCode,
	ShapeClass,
	ShapeSQLTable,
	ShapeImage,
	ShapeSequenceDiagram,
	ShapeHierarchy,
}

func ( string) bool {
	if  == "" {
		// Default shape is rectangle.
		return true
	}
	for ,  := range Shapes {
		if strings.EqualFold(, ) {
			return true
		}
	}
	return false
}

type MText struct {
	Text     string `json:"text"`
	FontSize int    `json:"fontSize"`
	IsBold   bool   `json:"isBold"`
	IsItalic bool   `json:"isItalic"`
	Language string `json:"language"`
	Shape    string `json:"shape"`

	Dimensions TextDimensions `json:"dimensions,omitempty"`
}

type TextDimensions struct {
	Width  int `json:"width"`
	Height int `json:"height"`
}

func (,  int) *TextDimensions {
	return &TextDimensions{Width: , Height: }
}

func ( MText) ( bool) string {
	if  {
		return color.N2
	}
	return color.N1
}

var DSL_SHAPE_TO_SHAPE_TYPE = map[string]string{
	"":                   shape.SQUARE_TYPE,
	ShapeRectangle:       shape.SQUARE_TYPE,
	ShapeSquare:          shape.REAL_SQUARE_TYPE,
	ShapePage:            shape.PAGE_TYPE,
	ShapeParallelogram:   shape.PARALLELOGRAM_TYPE,
	ShapeDocument:        shape.DOCUMENT_TYPE,
	ShapeCylinder:        shape.CYLINDER_TYPE,
	ShapeQueue:           shape.QUEUE_TYPE,
	ShapePackage:         shape.PACKAGE_TYPE,
	ShapeStep:            shape.STEP_TYPE,
	ShapeCallout:         shape.CALLOUT_TYPE,
	ShapeStoredData:      shape.STORED_DATA_TYPE,
	ShapePerson:          shape.PERSON_TYPE,
	ShapeDiamond:         shape.DIAMOND_TYPE,
	ShapeOval:            shape.OVAL_TYPE,
	ShapeCircle:          shape.CIRCLE_TYPE,
	ShapeHexagon:         shape.HEXAGON_TYPE,
	ShapeCloud:           shape.CLOUD_TYPE,
	ShapeText:            shape.TEXT_TYPE,
	ShapeCode:            shape.CODE_TYPE,
	ShapeClass:           shape.CLASS_TYPE,
	ShapeSQLTable:        shape.TABLE_TYPE,
	ShapeImage:           shape.IMAGE_TYPE,
	ShapeSequenceDiagram: shape.SQUARE_TYPE,
	ShapeHierarchy:       shape.SQUARE_TYPE,
}

var SHAPE_TYPE_TO_DSL_SHAPE map[string]string

func init() {
	SHAPE_TYPE_TO_DSL_SHAPE = make(map[string]string, len(DSL_SHAPE_TO_SHAPE_TYPE))
	for ,  := range DSL_SHAPE_TO_SHAPE_TYPE {
		SHAPE_TYPE_TO_DSL_SHAPE[] = 
	}
	// SQUARE_TYPE is defined twice in the map, make sure it doesn't get set to the empty string one
	SHAPE_TYPE_TO_DSL_SHAPE[shape.SQUARE_TYPE] = ShapeRectangle
}

func ( *geo.Box,  string) int {
	 := label.FromString()

	 := int(math.Min(.Width, .Height))
	 := int(math.Ceil(0.5 * float64()))

	var  int

	if  == label.InsideMiddleCenter {
		 = 
	} else {
		 = go2.Min(
			,
			go2.Max(DEFAULT_ICON_SIZE, ),
		)
	}

	 = go2.Min(, MAX_ICON_SIZE)

	if !.IsOutside() {
		 = go2.Min(,
			go2.Min(
				go2.Max(int(.Width)-2*label.PADDING, 0),
				go2.Max(int(.Height)-2*label.PADDING, 0),
			),
		)
	}

	return 
}