package label

import (
	

	
)

// These are % locations where labels will be placed along the connection
const LEFT_LABEL_POSITION = 1.0 / 4.0
const CENTER_LABEL_POSITION = 2.0 / 4.0
const RIGHT_LABEL_POSITION = 3.0 / 4.0

// This is the space between a node border and its outside label
const PADDING = 5

type Position int8

const (
	Unset Position = iota

	OutsideTopLeft
	OutsideTopCenter
	OutsideTopRight

	OutsideLeftTop
	OutsideLeftMiddle
	OutsideLeftBottom

	OutsideRightTop
	OutsideRightMiddle
	OutsideRightBottom

	OutsideBottomLeft
	OutsideBottomCenter
	OutsideBottomRight

	InsideTopLeft
	InsideTopCenter
	InsideTopRight

	InsideMiddleLeft
	InsideMiddleCenter
	InsideMiddleRight

	InsideBottomLeft
	InsideBottomCenter
	InsideBottomRight

	UnlockedTop
	UnlockedMiddle
	UnlockedBottom
)

func ( string) Position {
	switch  {
	case "OUTSIDE_TOP_LEFT":
		return OutsideTopLeft
	case "OUTSIDE_TOP_CENTER":
		return OutsideTopCenter
	case "OUTSIDE_TOP_RIGHT":
		return OutsideTopRight

	case "OUTSIDE_LEFT_TOP":
		return OutsideLeftTop
	case "OUTSIDE_LEFT_MIDDLE":
		return OutsideLeftMiddle
	case "OUTSIDE_LEFT_BOTTOM":
		return OutsideLeftBottom

	case "OUTSIDE_RIGHT_TOP":
		return OutsideRightTop
	case "OUTSIDE_RIGHT_MIDDLE":
		return OutsideRightMiddle
	case "OUTSIDE_RIGHT_BOTTOM":
		return OutsideRightBottom

	case "OUTSIDE_BOTTOM_LEFT":
		return OutsideBottomLeft
	case "OUTSIDE_BOTTOM_CENTER":
		return OutsideBottomCenter
	case "OUTSIDE_BOTTOM_RIGHT":
		return OutsideBottomRight

	case "INSIDE_TOP_LEFT":
		return InsideTopLeft
	case "INSIDE_TOP_CENTER":
		return InsideTopCenter
	case "INSIDE_TOP_RIGHT":
		return InsideTopRight

	case "INSIDE_MIDDLE_LEFT":
		return InsideMiddleLeft
	case "INSIDE_MIDDLE_CENTER":
		return InsideMiddleCenter
	case "INSIDE_MIDDLE_RIGHT":
		return InsideMiddleRight

	case "INSIDE_BOTTOM_LEFT":
		return InsideBottomLeft
	case "INSIDE_BOTTOM_CENTER":
		return InsideBottomCenter
	case "INSIDE_BOTTOM_RIGHT":
		return InsideBottomRight

	case "UNLOCKED_TOP":
		return UnlockedTop
	case "UNLOCKED_MIDDLE":
		return UnlockedMiddle
	case "UNLOCKED_BOTTOM":
		return UnlockedBottom
	default:
		return Unset
	}
}

func ( Position) () string {
	switch  {
	case OutsideTopLeft:
		return "OUTSIDE_TOP_LEFT"
	case OutsideTopCenter:
		return "OUTSIDE_TOP_CENTER"
	case OutsideTopRight:
		return "OUTSIDE_TOP_RIGHT"

	case OutsideLeftTop:
		return "OUTSIDE_LEFT_TOP"
	case OutsideLeftMiddle:
		return "OUTSIDE_LEFT_MIDDLE"
	case OutsideLeftBottom:
		return "OUTSIDE_LEFT_BOTTOM"

	case OutsideRightTop:
		return "OUTSIDE_RIGHT_TOP"
	case OutsideRightMiddle:
		return "OUTSIDE_RIGHT_MIDDLE"
	case OutsideRightBottom:
		return "OUTSIDE_RIGHT_BOTTOM"

	case OutsideBottomLeft:
		return "OUTSIDE_BOTTOM_LEFT"
	case OutsideBottomCenter:
		return "OUTSIDE_BOTTOM_CENTER"
	case OutsideBottomRight:
		return "OUTSIDE_BOTTOM_RIGHT"

	case InsideTopLeft:
		return "INSIDE_TOP_LEFT"
	case InsideTopCenter:
		return "INSIDE_TOP_CENTER"
	case InsideTopRight:
		return "INSIDE_TOP_RIGHT"

	case InsideMiddleLeft:
		return "INSIDE_MIDDLE_LEFT"
	case InsideMiddleCenter:
		return "INSIDE_MIDDLE_CENTER"
	case InsideMiddleRight:
		return "INSIDE_MIDDLE_RIGHT"

	case InsideBottomLeft:
		return "INSIDE_BOTTOM_LEFT"
	case InsideBottomCenter:
		return "INSIDE_BOTTOM_CENTER"
	case InsideBottomRight:
		return "INSIDE_BOTTOM_RIGHT"

	case UnlockedTop:
		return "UNLOCKED_TOP"
	case UnlockedMiddle:
		return "UNLOCKED_MIDDLE"
	case UnlockedBottom:
		return "UNLOCKED_BOTTOM"

	default:
		return ""
	}
}

func ( Position) () bool {
	switch  {
	case OutsideTopLeft, OutsideTopCenter, OutsideTopRight,
		OutsideBottomLeft, OutsideBottomCenter, OutsideBottomRight,
		OutsideLeftTop, OutsideLeftMiddle, OutsideLeftBottom,
		OutsideRightTop, OutsideRightMiddle, OutsideRightBottom,

		InsideTopLeft, InsideTopCenter, InsideTopRight,
		InsideMiddleLeft, InsideMiddleCenter, InsideMiddleRight,
		InsideBottomLeft, InsideBottomCenter, InsideBottomRight:
		return true
	default:
		return false
	}
}

func ( Position) () bool {
	switch  {
	case OutsideTopLeft, OutsideTopCenter, OutsideTopRight,
		InsideMiddleLeft, InsideMiddleCenter, InsideMiddleRight,
		OutsideBottomLeft, OutsideBottomCenter, OutsideBottomRight,
		UnlockedTop, UnlockedMiddle, UnlockedBottom:
		return true
	default:
		return false
	}
}

func ( Position) () bool {
	switch  {
	case OutsideTopLeft, OutsideTopCenter, OutsideTopRight,
		OutsideBottomLeft, OutsideBottomCenter, OutsideBottomRight,
		OutsideLeftTop, OutsideLeftMiddle, OutsideLeftBottom,
		OutsideRightTop, OutsideRightMiddle, OutsideRightBottom:
		return true
	default:
		return false
	}
}

func ( Position) () bool {
	switch  {
	case UnlockedTop, UnlockedMiddle, UnlockedBottom:
		return true
	default:
		return false
	}
}

func ( Position) () bool {
	switch  {
	case InsideMiddleLeft, InsideMiddleCenter, InsideMiddleRight, UnlockedMiddle:
		return true
	default:
		return false
	}
}

func ( Position) () Position {
	switch  {
	case OutsideTopLeft:
		return OutsideBottomRight
	case OutsideTopCenter:
		return OutsideBottomCenter
	case OutsideTopRight:
		return OutsideBottomLeft

	case OutsideLeftTop:
		return OutsideRightBottom
	case OutsideLeftMiddle:
		return OutsideRightMiddle
	case OutsideLeftBottom:
		return OutsideRightTop

	case OutsideRightTop:
		return OutsideLeftBottom
	case OutsideRightMiddle:
		return OutsideLeftMiddle
	case OutsideRightBottom:
		return OutsideLeftTop

	case OutsideBottomLeft:
		return OutsideTopRight
	case OutsideBottomCenter:
		return OutsideTopCenter
	case OutsideBottomRight:
		return OutsideTopLeft

	case InsideTopLeft:
		return InsideBottomRight
	case InsideTopCenter:
		return InsideBottomCenter
	case InsideTopRight:
		return InsideBottomLeft

	case InsideMiddleLeft:
		return InsideMiddleRight
	case InsideMiddleCenter:
		return InsideMiddleCenter
	case InsideMiddleRight:
		return InsideMiddleLeft

	case InsideBottomLeft:
		return InsideTopRight
	case InsideBottomCenter:
		return InsideTopCenter
	case InsideBottomRight:
		return InsideTopLeft

	case UnlockedTop:
		return UnlockedBottom
	case UnlockedBottom:
		return UnlockedTop
	case UnlockedMiddle:
		return UnlockedMiddle

	default:
		return Unset
	}
}

func ( Position) ( *geo.Box, , ,  float64) *geo.Point {
	 := .TopLeft.Copy()
	 := .Center()

	switch  {
	case OutsideTopLeft:
		.X -= 
		.Y -=  + 
	case OutsideTopCenter:
		.X = .X - /2
		.Y -=  + 
	case OutsideTopRight:
		.X += .Width -  - 
		.Y -=  + 

	case OutsideLeftTop:
		.X -=  + 
		.Y += 
	case OutsideLeftMiddle:
		.X -=  + 
		.Y = .Y - /2
	case OutsideLeftBottom:
		.X -=  + 
		.Y += .Height -  - 

	case OutsideRightTop:
		.X += .Width + 
		.Y += 
	case OutsideRightMiddle:
		.X += .Width + 
		.Y = .Y - /2
	case OutsideRightBottom:
		.X += .Width + 
		.Y += .Height -  - 

	case OutsideBottomLeft:
		.X += 
		.Y += .Height + 
	case OutsideBottomCenter:
		.X = .X - /2
		.Y += .Height + 
	case OutsideBottomRight:
		.X += .Width -  - 
		.Y += .Height + 

	case InsideTopLeft:
		.X += 
		.Y += 
	case InsideTopCenter:
		.X = .X - /2
		.Y += 
	case InsideTopRight:
		.X += .Width -  - 
		.Y += 

	case InsideMiddleLeft:
		.X += 
		.Y = .Y - /2
	case InsideMiddleCenter:
		.X = .X - /2
		.Y = .Y - /2
	case InsideMiddleRight:
		.X += .Width -  - 
		.Y = .Y - /2

	case InsideBottomLeft:
		.X += 
		.Y += .Height -  - 
	case InsideBottomCenter:
		.X = .X - /2
		.Y += .Height -  - 
	case InsideBottomRight:
		.X += .Width -  - 
		.Y += .Height -  - 
	}

	return 
}

// return the top left point of a width x height label at the given label position on the route
// also return the index of the route segment that point is on
func ( Position) ( geo.Route, , , ,  float64) ( *geo.Point,  int) {
	 := .Length()
	 := LEFT_LABEL_POSITION * 
	 := CENTER_LABEL_POSITION * 
	 := RIGHT_LABEL_POSITION * 
	 :=  * 

	// outside labels have to be offset in the direction of the edge's normal Vector
	// Note: we flip the normal for Top labels but keep it as is for Bottom labels since positive Y is below in SVG
	 := func(, ,  *geo.Point,  bool) *geo.Point {
		// get the normal as a unit Vector so we can multiply to project in its direction
		,  := geo.GetUnitNormalVector(
			.X,
			.Y,
			.X,
			.Y,
		)
		if  {
			 *= -1
			 *= -1
		}

		// Horizontal Edge with Outside Label          |      Vertical Edge with Outside Label
		//  ┌────────────────────┐    ┬                |       ┌─┬─┐
		//  │                    │    │                |       │ │ │    ┌───────────┬───────────┐
		//  │                    │    │                |       │ e │    │           │           │
		//  ├────label─center────┤  ┬ ┼label height    |       │ d │    │         label         │
		//  │                    │  │ │                |       │ g │    │         center        │
		//  │                    │  │ │                |       │ e │    │           │           │
		//  └────────────────────┘  │ ┴ ┬              |       │ │ │    └───────────┴───────────┘
		//                          │   │              |       └─┴─┘   offset
		//                    offset│   │label padding |         ├──────────────────┤
		//                          │   │              |
		// ┌──────────────────────┐ │ ┬ ┴              |                ├───────────┼───────────┤
		// │                      │ │ │                |           ├────┤      label width
		// ├─────edge─center──────┤ ┴ ┼stroke width    |        label padding
		// │                      │   │                |       ├─┼─┤
		// └──────────────────────┘   ┴                |    stroke width
		//
		// TODO: get actual edge stroke width on edge
		 := /2 + float64(PADDING) + /2
		 := /2 + float64(PADDING) + /2

		return geo.NewPoint(.X+*, .Y+*)
	}

	var  *geo.Point
	switch  {
	case InsideMiddleLeft:
		,  = .GetPointAtDistance()
	case InsideMiddleCenter:
		,  = .GetPointAtDistance()
	case InsideMiddleRight:
		,  = .GetPointAtDistance()

	case OutsideTopLeft:
		,  := .GetPointAtDistance()
		 = (, [], [+1], true)
	case OutsideTopCenter:
		,  := .GetPointAtDistance()
		 = (, [], [+1], true)
	case OutsideTopRight:
		,  := .GetPointAtDistance()
		 = (, [], [+1], true)

	case OutsideBottomLeft:
		,  := .GetPointAtDistance()
		 = (, [], [+1], false)
	case OutsideBottomCenter:
		,  := .GetPointAtDistance()
		 = (, [], [+1], false)
	case OutsideBottomRight:
		,  := .GetPointAtDistance()
		 = (, [], [+1], false)

	case UnlockedTop:
		,  := .GetPointAtDistance()
		 = (, [], [+1], true)
	case UnlockedMiddle:
		,  = .GetPointAtDistance()
	case UnlockedBottom:
		,  := .GetPointAtDistance()
		 = (, [], [+1], false)
	default:
		return nil, -1
	}
	// convert from center to top left
	.X = chopPrecision(.X - /2)
	.Y = chopPrecision(.Y - /2)
	return , 
}

// TODO probably use math.Big
func chopPrecision( float64) float64 {
	// bring down to float32 precision before rounding for consistency across architectures
	return math.Round(float64(float32(*10000)) / 10000)
}