using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Reflection;
[CustomEditor(typeof(PolyMesh))]
public class PolyMeshEditor : Editor
{
	/// 
	/// Sceneビュー上でのステータスを示す構造体
	/// 
	enum State { Hover, Drag, BoxSelect, DragSelected, RotateSelected, ScaleSelected, Extrude }
	/// 
	/// クリック判定領域
	/// 
	const float clickRadius = 0.12f;
	/// 
	/// ???
	/// 
	FieldInfo undoCallback;
	/// 
	/// Editモードかどうか
	/// 
	bool editing;
	/// 
	/// ???
	/// 
	bool tabDown;
	/// 
	/// ???
	/// 
	State state;
	/// 
	/// 直線パス
	/// 
	List keyPoints;
	/// 
	/// 曲線パス
	/// 
	List curvePoints;
	/// 
	/// カーブするかどうか???
	/// 
	List isCurve;
	/// 
	/// global座標をlocal座標で持つ???
	/// 
	Matrix4x4 worldToLocal;
	/// 
	/// 回転角度 Quaternionでもつの???
	/// 
	Quaternion inverseRotation;
	
	/// 
	/// マウス座標
	/// 
	Vector3 mousePosition;
	/// 
	/// クリック座標
	/// 
	Vector3 clickPosition;
	/// 
	/// スクリーン上でのマウスの座標
	/// 
	Vector3 screenMousePosition;
	/// 
	/// マウスカーソルの状態をArrowに指定
	/// 
	MouseCursor mouseCursor = MouseCursor.Arrow;
	/// 
	/// スナップ???
	/// 
	float snap;
	/// 
	/// ???
	/// 
	int dragIndex;
	/// 
	/// ???
	/// 
	List selectedIndices = new List();
	/// 
	/// ???
	/// 
	int nearestLine;
	/// 
	/// ???
	/// 
	Vector3 splitPosition;
	/// 
	/// ???
	/// 
	bool extrudeKeyDown;
	/// 
	/// ???
	/// 
	bool doExtrudeUpdate;
	/// 
	/// ???
	/// 
	bool draggingCurve;
	#region Inspector GUI
	public override void OnInspectorGUI() {
		if (target == null) {
			return;
		}
		if (polyMesh.keyPoints.Count == 0) {
			CreateSquare(polyMesh, 0.5f);
		}
		// Editモードに入るかどうか
		if (editing) {
			if (GUILayout.Button("Stop Editing"))
			{
				editing = false;
				HideWireframe(false);
			}
		}
		else if (GUILayout.Button("Edit PolyMesh")) {
			editing = true;
			HideWireframe(hideWireframe);
		}
		// UV設定
		if (uvSettings = EditorGUILayout.Foldout(uvSettings, "UVs")) {
			var uvPosition = EditorGUILayout.Vector2Field("Position", polyMesh.uvPosition);
			var uvScale = EditorGUILayout.FloatField("Scale", polyMesh.uvScale);
			var uvRotation = EditorGUILayout.Slider("Rotation", polyMesh.uvRotation, -180, 180) % 360;
			if (uvRotation < -180)
				uvRotation += 360;
			if (GUI.changed) {
				RecordUndo();
				polyMesh.uvPosition = uvPosition;
				polyMesh.uvScale = uvScale;
				polyMesh.uvRotation = uvRotation;
			}
			if (GUILayout.Button("Reset UVs")) {
				polyMesh.uvPosition = Vector3.zero;
				polyMesh.uvScale = 1;
				polyMesh.uvRotation = 0;
			}
		}
		// Mesh設定
		if (meshSettings = EditorGUILayout.Foldout(meshSettings, "Mesh")) {
			var curveDetail = EditorGUILayout.Slider("Curve Detail", polyMesh.curveDetail, 0.01f, 1f);
			curveDetail = Mathf.Clamp(curveDetail, 0.01f, 1f);
			if (GUI.changed) {
				RecordUndo();
				polyMesh.curveDetail = curveDetail;
			}
			//Buttons
			EditorGUILayout.BeginHorizontal();
			if (GUILayout.Button("Build Mesh")) {
				polyMesh.BuildMesh();				
			}
			if (GUILayout.Button("Make Mesh Unique")) {
				RecordUndo();
				polyMesh.GetComponent().mesh = null;
				polyMesh.BuildMesh();
			}
			EditorGUILayout.EndHorizontal();
		}
		// Colliderの生成
		if (colliderSettings = EditorGUILayout.Foldout(colliderSettings, "Collider")) {
			// Colliderの深さ設定(z軸の長さ)
			var colliderDepth = EditorGUILayout.FloatField("Depth", polyMesh.colliderDepth);
			colliderDepth = Mathf.Max(colliderDepth, 0.01f);
			var buildColliderEdges = EditorGUILayout.Toggle("Build Edges", polyMesh.buildColliderEdges);
			var buildColliderFront = EditorGUILayout.Toggle("Build Font", polyMesh.buildColliderFront);
			if (GUI.changed) {
				RecordUndo();
				polyMesh.colliderDepth = colliderDepth;
				polyMesh.buildColliderEdges = buildColliderEdges;
				polyMesh.buildColliderFront = buildColliderFront;
			}
			// Colliderの破壊
			if (polyMesh.meshCollider == null) {
				if (GUILayout.Button("Create Collider")) {
					RecordDeepUndo();
					var obj = new GameObject("Collider", typeof(MeshCollider));
					polyMesh.meshCollider = obj.GetComponent();
					obj.transform.parent = polyMesh.transform;
					obj.transform.localPosition = Vector3.zero;
				}
			} else if (GUILayout.Button("Destroy Collider")) {
				RecordDeepUndo();
				DestroyImmediate(polyMesh.meshCollider.gameObject);
			}
		}
		//Update mesh
		if (GUI.changed) {
			polyMesh.BuildMesh();
		}
		//Editor settings
		if (editorSettings = EditorGUILayout.Foldout(editorSettings, "Editor")) {
			gridSnap = EditorGUILayout.FloatField("Grid Snap", gridSnap);
			autoSnap = EditorGUILayout.Toggle("Auto Snap", autoSnap);
			globalSnap = EditorGUILayout.Toggle("Global Snap", globalSnap);
			EditorGUI.BeginChangeCheck();
			hideWireframe = EditorGUILayout.Toggle("Hide Wireframe", hideWireframe);
			if (EditorGUI.EndChangeCheck()) {
				HideWireframe(hideWireframe);
			}
			editKey = (KeyCode)EditorGUILayout.EnumPopup("[Toggle Edit] Key", editKey);
			selectAllKey = (KeyCode)EditorGUILayout.EnumPopup("[Select All] Key", selectAllKey);
			splitKey = (KeyCode)EditorGUILayout.EnumPopup("[Split] Key", splitKey);
			extrudeKey = (KeyCode)EditorGUILayout.EnumPopup("[Extrude] Key", extrudeKey);
		}
	}
	#endregion
	#region Scene GUI
	void OnSceneGUI() {
		if (target == null) {
			return;
		}
		if (KeyPressed(editKey)) {
			editing = !editing;
		}
		if (editing) {
			// 各種パスを取得
			if (keyPoints == null) {
				keyPoints = new List(polyMesh.keyPoints);
				curvePoints = new List(polyMesh.curvePoints);
				isCurve = new List(polyMesh.isCurve);
			}
			// Undo時の動作を設定
			if (undoCallback == null) {
				undoCallback = typeof(EditorApplication).GetField("undoRedoPerformed", BindingFlags.NonPublic | BindingFlags.Static);
				if (undoCallback != null) {
					undoCallback.SetValue(null, new EditorApplication.CallbackFunction(OnUndoRedo));
				}
			}
			// ハンドルの座標を読み込む
			Handles.matrix = polyMesh.transform.localToWorldMatrix;
			// パスと線の描画
			DrawAxis();
			Handles.color = Color.white;
			for (int i = 0; i < keyPoints.Count; i++) {
				// PolyMeshの外枠を描画
				Handles.color = nearestLine == i ? Color.green : Color.white;
				DrawSegment(i);
				// 選択中のパスの描画
				if (selectedIndices.Contains(i)) {
					Handles.color = Color.green;
					DrawCircle(keyPoints[i], 0.08f);
				} else {
					Handles.color = Color.white;
				}
				DrawKeyPoint(i);
				// 曲線パスについての描画???
				if (isCurve[i]) {
					Handles.color = (draggingCurve && dragIndex == i) ? Color.white : Color.blue;
					DrawCurvePoint(i);
				}
			}
			// ツール切り替え時はキャンセル
			if (e.type == EventType.KeyDown) {
				switch (e.keyCode) {
				case KeyCode.Q:
				case KeyCode.W:
				case KeyCode.E:
				case KeyCode.R:
					return;
				}
			}
			//パニング中、もしくはカメラがシーン中にない場合はキャンセル
			if (Tools.current == Tool.View || (e.isMouse && e.button > 0) || Camera.current == null || e.type == EventType.ScrollWheel) {
				return;
			}
			//レイアウトの更新時はキャンセル
			if (e.type == EventType.Layout) {
				HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
				return;
			}
			//Cursor rectangle
			EditorGUIUtility.AddCursorRect(new Rect(0, 0, Camera.current.pixelWidth, Camera.current.pixelHeight), mouseCursor);
			mouseCursor = MouseCursor.Arrow;
			//Extrude key state
			if (e.keyCode == extrudeKey) {
				if (extrudeKeyDown) {
					if (e.type == EventType.KeyUp)
						extrudeKeyDown = false;
				} else if (e.type == EventType.KeyDown) {
					extrudeKeyDown = true;
				}
			}
			//Update matrices and snap
			worldToLocal = polyMesh.transform.worldToLocalMatrix;
			inverseRotation = Quaternion.Inverse(polyMesh.transform.rotation) * Camera.current.transform.rotation;
			snap = gridSnap;
			
			//Update mouse position
			screenMousePosition = new Vector3(e.mousePosition.x, Camera.current.pixelHeight - e.mousePosition.y);
			var plane = new Plane(-polyMesh.transform.forward, polyMesh.transform.position);
			var ray = Camera.current.ScreenPointToRay(screenMousePosition);
			float hit;
			if (plane.Raycast(ray, out hit)) {
				mousePosition = worldToLocal.MultiplyPoint(ray.GetPoint(hit));
			} else {
				return;
			}
			//Update nearest line and split position
			nearestLine = NearestLine(out splitPosition);
			
			//Update the state and repaint
			var newState = UpdateState();
			if (state != newState)
				SetState(newState);
			HandleUtility.Repaint();
			e.Use();
		}
	}
	void HideWireframe(bool hide)
	{
        Renderer renderer = polyMesh.GetComponent();
		if (renderer != null) {
			EditorUtility.SetSelectedWireframeHidden(renderer, hide);
		}
	}
	void RecordUndo() {
		Undo.RecordObject(target, "PolyMesh Changed");
	}
	void RecordDeepUndo() {
		Undo.RegisterFullObjectHierarchyUndo(target, "PolyMesh Changed");
	}
	#endregion
	#region State Control
	//Initialize state
	void SetState(State newState) {
		state = newState;
		switch (state) {
		case State.Hover:
			break;
		}
	}
	//Update state
	State UpdateState() {
		switch (state) {
			//Hovering
		case State.Hover:
			DrawNearestLineAndSplit();
			if (Tools.current == Tool.Move && TryDragSelected())
				return State.DragSelected;
			if (Tools.current == Tool.Rotate && TryRotateSelected())
				return State.RotateSelected;
			if (Tools.current == Tool.Scale && TryScaleSelected())
				return State.ScaleSelected;
			if (Tools.current == Tool.Move && TryExtrude())
				return State.Extrude;
			if (TrySelectAll())
				return State.Hover;
			if (TrySplitLine())
				return State.Hover;
			if (TryDeleteSelected())
				return State.Hover;
			if (TryHoverCurvePoint(out dragIndex) && TryDragCurvePoint(dragIndex))
				return State.Drag;
			if (TryHoverKeyPoint(out dragIndex) && TryDragKeyPoint(dragIndex))
				return State.Drag;
			if (TryBoxSelect())
				return State.BoxSelect;
			break;
			//Dragging
		case State.Drag:
			mouseCursor = MouseCursor.MoveArrow;
			DrawCircle(keyPoints[dragIndex], clickRadius);
			if (draggingCurve)
				MoveCurvePoint(dragIndex, mousePosition - clickPosition);
			else
				MoveKeyPoint(dragIndex, mousePosition - clickPosition);
			if (TryStopDrag())
				return State.Hover;
			break;
			//Box Selecting
		case State.BoxSelect:
			if (TryBoxSelectEnd())
				return State.Hover;
			break;
			//Dragging selected
		case State.DragSelected:
			mouseCursor = MouseCursor.MoveArrow;
			MoveSelected(mousePosition - clickPosition);
			if (TryStopDrag())
				return State.Hover;
			break;
			//Rotating selected
		case State.RotateSelected:
			mouseCursor = MouseCursor.RotateArrow;
			RotateSelected();
			if (TryStopDrag())
				return State.Hover;
			break;
			//Scaling selected
		case State.ScaleSelected:
			mouseCursor = MouseCursor.ScaleArrow;
			ScaleSelected();
			if (TryStopDrag())
				return State.Hover;
			break;
			//Extruding
		case State.Extrude:
			mouseCursor = MouseCursor.MoveArrow;
			MoveSelected(mousePosition - clickPosition);
			if (doExtrudeUpdate && mousePosition != clickPosition)
			{
				UpdatePoly(false, false);
				doExtrudeUpdate = false;
			}
			if (TryStopDrag())
				return State.Hover;
			break;
		}
		return state;
	}
	//Update the mesh on undo/redo
	void OnUndoRedo() {
		keyPoints = new List(polyMesh.keyPoints);
		curvePoints = new List(polyMesh.curvePoints);
		isCurve = new List(polyMesh.isCurve);
		polyMesh.BuildMesh();
	}
	
	void LoadPoly() {
		for (int i = 0; i < keyPoints.Count; i++) {
			keyPoints[i] = polyMesh.keyPoints[i];
			curvePoints[i] = polyMesh.curvePoints[i];
			isCurve[i] = polyMesh.isCurve[i];
		}
	}
	
	void TransformPoly(Matrix4x4 matrix) {
		for (int i = 0; i < keyPoints.Count; i++) {
			keyPoints[i] = matrix.MultiplyPoint(polyMesh.keyPoints[i]);
			curvePoints[i] = matrix.MultiplyPoint(polyMesh.curvePoints[i]);
		}
	}
	
	void UpdatePoly(bool sizeChanged, bool recordUndo) {
		if (recordUndo) {
			RecordUndo();
		}
		if (sizeChanged) {
			polyMesh.keyPoints = new List(keyPoints);
			polyMesh.curvePoints = new List(curvePoints);
			polyMesh.isCurve = new List(isCurve);
		} else {
			for (int i = 0; i < keyPoints.Count; i++) {
				polyMesh.keyPoints[i] = keyPoints[i];
				polyMesh.curvePoints[i] = curvePoints[i];
				polyMesh.isCurve[i] = isCurve[i];
			}
		}
		for (int i = 0; i < keyPoints.Count; i++){ 
			if (!isCurve[i]) {
				polyMesh.curvePoints[i] = curvePoints[i] = Vector3.Lerp(keyPoints[i], keyPoints[(i + 1) % keyPoints.Count], 0.5f);
			}
		}
		polyMesh.BuildMesh();
	}
	void MoveKeyPoint(int index, Vector3 amount) {
		var moveCurve = selectedIndices.Contains((index + 1) % keyPoints.Count);
		if (doSnap) {
			if (globalSnap) {
				keyPoints[index] = Snap(polyMesh.keyPoints[index] + amount);
				if (moveCurve){
					curvePoints[index] = Snap(polyMesh.curvePoints[index] + amount);
				}
			} else {
				amount = Snap(amount);
				keyPoints[index] = polyMesh.keyPoints[index] + amount;
				if (moveCurve) {
					curvePoints[index] = polyMesh.curvePoints[index] + amount;
				}
			}
		} else {
			keyPoints[index] = polyMesh.keyPoints[index] + amount;
			if (moveCurve) {
				curvePoints[index] = polyMesh.curvePoints[index] + amount;
			}
		}
	}
	void MoveCurvePoint(int index, Vector3 amount) {
		isCurve[index] = true;
		if (doSnap) {
			if (globalSnap){
				curvePoints[index] = Snap(polyMesh.curvePoints[index] + amount);
			} else {
				curvePoints[index] = polyMesh.curvePoints[index] + amount;
			}
		} else {
			curvePoints[index] = polyMesh.curvePoints[index] + amount;
		}
	}
	void MoveSelected(Vector3 amount) {
		foreach (var i in selectedIndices) {
			MoveKeyPoint(i, amount);
		}
	}
	void RotateSelected() {
		var center = GetSelectionCenter();
		Handles.color = Color.white;
		Handles.DrawLine(center, clickPosition);
		Handles.color = Color.green;
		Handles.DrawLine(center, mousePosition);
		var clickOffset = clickPosition - center;
		var mouseOffset = mousePosition - center;
		var clickAngle = Mathf.Atan2(clickOffset.y, clickOffset.x);
		var mouseAngle = Mathf.Atan2(mouseOffset.y, mouseOffset.x);
		var angleOffset = mouseAngle - clickAngle;
		foreach (var i in selectedIndices) {
			var point = polyMesh.keyPoints[i];
			var pointOffset = point - center;
			var a = Mathf.Atan2(pointOffset.y, pointOffset.x) + angleOffset;
			var d = pointOffset.magnitude;
			keyPoints[i] = center + new Vector3(Mathf.Cos(a) * d, Mathf.Sin(a) * d);
		}
	}
	void ScaleSelected() {
		Handles.color = Color.green;
		Handles.DrawLine(clickPosition, mousePosition);
		var center = GetSelectionCenter();
		var scale = mousePosition - clickPosition;
		//Uniform scaling if shift pressed
		if (e.shift) {
			if (Mathf.Abs(scale.x) > Mathf.Abs(scale.y)) {
				scale.y = scale.x;
			} else {
				scale.x = scale.y;
			}
		}
		//Determine direction of scaling
		if (scale.x < 0) {
			scale.x = 1 / (-scale.x + 1);
		}else {
			scale.x = 1 + scale.x;
		}
		if (scale.y < 0) {
			scale.y = 1 / (-scale.y + 1);
		}else {
			scale.y = 1 + scale.y;
		}
		foreach (var i in selectedIndices) {
			var point = polyMesh.keyPoints[i];
			var offset = point - center;
			offset.x *= scale.x;
			offset.y *= scale.y;
			keyPoints[i] = center + offset;
		}
	}
	#endregion
	#region Drawing
	void DrawAxis() {
		Handles.color = Color.red;
		var size = HandleUtility.GetHandleSize(Vector3.zero) * 0.1f;
		Handles.DrawLine(new Vector3(-size, 0), new Vector3(size, 0));
		Handles.DrawLine(new Vector3(0, -size), new Vector2(0, size));
	}
	void DrawKeyPoint(int index) {
		Handles.DotCap(0, keyPoints[index], Quaternion.identity, HandleUtility.GetHandleSize(keyPoints[index]) * 0.03f);
	}
	void DrawCurvePoint(int index) {
		Handles.DotCap(0, curvePoints[index], Quaternion.identity, HandleUtility.GetHandleSize(keyPoints[index]) * 0.03f);
	}
	void DrawSegment(int index) {
		var from = keyPoints[index];
		var to = keyPoints[(index + 1) % keyPoints.Count];
		if (isCurve[index]) {
			var control = Bezier.Control(from, to, curvePoints[index]);
			var count = Mathf.Ceil(1 / polyMesh.curveDetail);
			for (int i = 0; i < count; i++) {
				Handles.DrawLine(Bezier.Curve(from, control, to, i / count), Bezier.Curve(from, control, to, (i + 1) / count));
			}
		} else {
			Handles.DrawLine(from, to);
		}
	}
	void DrawCircle(Vector3 position, float size) {
		Handles.CircleCap(0, position, inverseRotation, HandleUtility.GetHandleSize(position) * size);
	}
	void DrawNearestLineAndSplit() {
		if (nearestLine >= 0) {
			Handles.color = Color.green;
			DrawSegment(nearestLine);
			Handles.color = Color.red;
			Handles.DotCap(0, splitPosition, Quaternion.identity, HandleUtility.GetHandleSize(splitPosition) * 0.03f);
		}
	}
	#endregion
	#region State Checking
	bool TryHoverKeyPoint(out int index) {
		if (TryHover(keyPoints, Color.white, out index)) {
			mouseCursor = MouseCursor.MoveArrow;
			return true;
		}
		return false;
	}
	bool TryHoverCurvePoint(out int index) {
		if (TryHover(curvePoints, Color.white, out index)) {
			mouseCursor = MouseCursor.MoveArrow;
			return true;
		}
		return false;
	}
	bool TryDragKeyPoint(int index) {
		if (TryDrag(keyPoints, index)) {
			draggingCurve = false;
			return true;
		}
		return false;
	}
	bool TryDragCurvePoint(int index) {
		if (TryDrag(curvePoints, index)) {
			draggingCurve = true;
			return true;
		}
		return false;
	}
	bool TryHover(List points, Color color, out int index) {
		if (Tools.current == Tool.Move) {
			index = NearestPoint(points);
			if (index >= 0 && IsHovering(points[index])) {
				Handles.color = color;
				DrawCircle(points[index], clickRadius);
				return true;
			}
		}
		index = -1;
		return false;
	}
	bool TryDrag(List points, int index) {
		if (e.type == EventType.MouseDown && IsHovering(points[index])) {
			clickPosition = mousePosition;
			return true;
		}
		return false;
	}
	bool TryStopDrag() {
		if (e.type == EventType.MouseUp) {
			dragIndex = -1;
			UpdatePoly(false, state != State.Extrude);
			return true;
		}
		return false;
	}
	bool TryBoxSelect() {
		if (e.type == EventType.MouseDown) {
			clickPosition = mousePosition;
			return true;
		}
		return false;
	}
	bool TryBoxSelectEnd() {
		var min = new Vector3(Mathf.Min(clickPosition.x, mousePosition.x), Mathf.Min(clickPosition.y, mousePosition.y));
		var max = new Vector3(Mathf.Max(clickPosition.x, mousePosition.x), Mathf.Max(clickPosition.y, mousePosition.y));
		Handles.color = Color.white;
		Handles.DrawLine(new Vector3(min.x, min.y), new Vector3(max.x, min.y));
		Handles.DrawLine(new Vector3(min.x, max.y), new Vector3(max.x, max.y));
		Handles.DrawLine(new Vector3(min.x, min.y), new Vector3(min.x, max.y));
		Handles.DrawLine(new Vector3(max.x, min.y), new Vector3(max.x, max.y));
		if (e.type == EventType.MouseUp) {
			var rect = new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
			if (!control) { 
				selectedIndices.Clear();
			}
			for (int i = 0; i < keyPoints.Count; i++){
				if (rect.Contains(keyPoints[i])) {
					selectedIndices.Add(i);
				}
			}
			return true;
		}
		return false;
	}
	bool TryDragSelected() {
		if (selectedIndices.Count > 0 && TryDragButton(GetSelectionCenter(), 0.2f)) {
			clickPosition = mousePosition;
			return true;
		}
		return false;
	}
	bool TryRotateSelected() {
		if (selectedIndices.Count > 0 && TryRotateButton(GetSelectionCenter(), 0.3f)) {
			clickPosition = mousePosition;
			return true;
		}
		return false;
	}
	bool TryScaleSelected() {
		if (selectedIndices.Count > 0 && TryScaleButton(GetSelectionCenter(), 0.3f)) {
			clickPosition = mousePosition;
			return true;
		}
		return false;
	}
	bool TryDragButton(Vector3 position, float size) {
		size *= HandleUtility.GetHandleSize(position);
		if (Vector3.Distance(mousePosition, position) < size) {
			if (e.type == EventType.MouseDown) {
				return true;
			} else {
				mouseCursor = MouseCursor.MoveArrow;
				Handles.color = Color.green;
			}
		} else {
			Handles.color = Color.white;
		}
		var buffer = size / 2;
		Handles.DrawLine(new Vector3(position.x - buffer, position.y), new Vector3(position.x + buffer, position.y));
		Handles.DrawLine(new Vector3(position.x, position.y - buffer), new Vector3(position.x, position.y + buffer));
		Handles.RectangleCap(0, position, Quaternion.identity, size);
		return false;
	}
	bool TryRotateButton(Vector3 position, float size) {
		size *= HandleUtility.GetHandleSize(position);
		var dist = Vector3.Distance(mousePosition, position);
		var buffer = size / 4;
		if (dist < size + buffer && dist > size - buffer) {
			if (e.type == EventType.MouseDown) {
				return true;
			}else {
				mouseCursor = MouseCursor.RotateArrow;
				Handles.color = Color.green;
			}
		} else {
			Handles.color = Color.white;
		}
		Handles.CircleCap(0, position, inverseRotation, size - buffer / 2);
		Handles.CircleCap(0, position, inverseRotation, size + buffer / 2);
		return false;
	}
	bool TryScaleButton(Vector3 position, float size) {
		size *= HandleUtility.GetHandleSize(position);
		if (Vector3.Distance(mousePosition, position) < size) {
			if (e.type == EventType.MouseDown) {
				return true;
			}else {
				mouseCursor = MouseCursor.ScaleArrow;
				Handles.color = Color.green;
			}
		}
		else
			Handles.color = Color.white;
		var buffer = size / 4;
		Handles.DrawLine(new Vector3(position.x - size - buffer, position.y), new Vector3(position.x - size + buffer, position.y));
		Handles.DrawLine(new Vector3(position.x + size - buffer, position.y), new Vector3(position.x + size + buffer, position.y));
		Handles.DrawLine(new Vector3(position.x, position.y - size - buffer), new Vector3(position.x, position.y - size + buffer));
		Handles.DrawLine(new Vector3(position.x, position.y + size - buffer), new Vector3(position.x, position.y + size + buffer));
		Handles.RectangleCap(0, position, Quaternion.identity, size);
		return false;
	}
	bool TrySelectAll(){
		if (KeyPressed(selectAllKey)) {
			selectedIndices.Clear();
			for (int i = 0; i < keyPoints.Count; i++){
				selectedIndices.Add(i);
			}
			return true;
		}
		return false;
	}
	bool TrySplitLine() {
		if (nearestLine >= 0 && KeyPressed(splitKey)) {
			if (nearestLine == keyPoints.Count - 1) {
				keyPoints.Add(splitPosition);
				curvePoints.Add(Vector3.zero);
				isCurve.Add(false);
			} else {
				keyPoints.Insert(nearestLine + 1, splitPosition);
				curvePoints.Insert(nearestLine + 1, Vector3.zero);
				isCurve.Insert(nearestLine + 1, false);
			}
			isCurve[nearestLine] = false;
			UpdatePoly(true, true);
			return true;
		}
		return false;
	}
	bool TryExtrude() {
		if (nearestLine >= 0 && extrudeKeyDown && e.type == EventType.MouseDown) {
			var a = nearestLine;
			var b = (nearestLine + 1) % keyPoints.Count;
			if (b == 0 && a == keyPoints.Count - 1) {
				//Extrude between the first and last points
				keyPoints.Add(polyMesh.keyPoints[a]);
				keyPoints.Add(polyMesh.keyPoints[b]);
				curvePoints.Add(Vector3.zero);
				curvePoints.Add(Vector3.zero);
				isCurve.Add(false);
				isCurve.Add(false);
				
				selectedIndices.Clear();
				selectedIndices.Add(keyPoints.Count - 2);
				selectedIndices.Add(keyPoints.Count - 1);
			} else {
				//Extrude between two inner points
				var pointA = keyPoints[a];
				var pointB = keyPoints[b];
				keyPoints.Insert(a + 1, pointA);
				keyPoints.Insert(a + 2, pointB);
				curvePoints.Insert(a + 1, Vector3.zero);
				curvePoints.Insert(a + 2, Vector3.zero);
				isCurve.Insert(a + 1, false);
				isCurve.Insert(a + 2, false);
				
				selectedIndices.Clear();
				selectedIndices.Add(a + 1);
				selectedIndices.Add(a + 2);
			}
			isCurve[nearestLine] = false;
			clickPosition = mousePosition;
			doExtrudeUpdate = true;
			UpdatePoly(true, true);
			return true;
		}
		return false;
	}
	bool TryDeleteSelected() {
		if (KeyPressed(KeyCode.Backspace) || KeyPressed(KeyCode.Delete)) {
			if (selectedIndices.Count > 0) {
				if (keyPoints.Count - selectedIndices.Count >= 3) {
					for (int i = selectedIndices.Count - 1; i >= 0; i--) {
						var index = selectedIndices[i];
						keyPoints.RemoveAt(index);
						curvePoints.RemoveAt(index);
						isCurve.RemoveAt(index);
					}
					selectedIndices.Clear();
					UpdatePoly(true, true);
					return true;
				}
			} else if (IsHovering(curvePoints[nearestLine])) {
				isCurve[nearestLine] = false;
				UpdatePoly(false, true);
			}
		}
		return false;
	}
	bool IsHovering(Vector3 point) {
		return Vector3.Distance(mousePosition, point) < HandleUtility.GetHandleSize(point) * clickRadius;
	}
	
	int NearestPoint(List points) {
		var near = -1;
		var nearDist = float.MaxValue;
		for (int i = 0; i < points.Count; i++) {
			var dist = Vector3.Distance(points[i], mousePosition);
			if (dist < nearDist) {
				nearDist = dist;
				near = i;
			}
		}
		return near;
	}
	
	int NearestLine(out Vector3 position) {
		var near = -1;
		var nearDist = float.MaxValue;
		position = keyPoints[0];
		var linePos = Vector3.zero;
		for (int i = 0; i < keyPoints.Count; i++) {
			var j = (i + 1) % keyPoints.Count;
			var line = keyPoints[j] - keyPoints[i];
			var offset = mousePosition - keyPoints[i];
			var dot = Vector3.Dot(line.normalized, offset);
			if (dot >= 0 && dot <= line.magnitude) {
				if (isCurve[i]) {
					linePos = Bezier.Curve(keyPoints[i], Bezier.Control(keyPoints[i], keyPoints[j], curvePoints[i]), keyPoints[j], dot / line.magnitude);
				} else {
					linePos = keyPoints[i] + line.normalized * dot;
				}
				var dist = Vector3.Distance(linePos, mousePosition);
				if (dist < nearDist) {
					nearDist = dist;
					position = linePos;
					near = i;
				}
			}
		}
		return near;
	}
	bool KeyPressed(KeyCode key) {
		return e.type == EventType.KeyDown && e.keyCode == key;
	}
	bool KeyReleased(KeyCode key) {
		return e.type == EventType.KeyUp && e.keyCode == key;
	}
	
	Vector3 Snap(Vector3 value) {
		value.x = Mathf.Round(value.x / snap) * snap;
		value.y = Mathf.Round(value.y / snap) * snap;
		return value;
	}
	Vector3 GetSelectionCenter() {
		var center = Vector3.zero;
		foreach (var i in selectedIndices)
			center += polyMesh.keyPoints[i];
		return center / selectedIndices.Count;
	}
	#endregion
	#region Properties
	PolyMesh polyMesh {
		get { return (PolyMesh)target; }
	}
	Event e {
		get { return Event.current; }
	}
	bool control {
		get { return Application.platform == RuntimePlatform.OSXEditor ? e.command : e.control; }
	}
	bool doSnap {
		get { return autoSnap ? !control : control; }
	}
	static bool meshSettings {
		get { return EditorPrefs.GetBool("PolyMeshEditor_meshSettings", false); }
		set { EditorPrefs.SetBool("PolyMeshEditor_meshSettings", value); }
	}
	static bool colliderSettings {
		get { return EditorPrefs.GetBool("PolyMeshEditor_colliderSettings", false); }
		set { EditorPrefs.SetBool("PolyMeshEditor_colliderSettings", value); }
	}
	static bool uvSettings {
		get { return EditorPrefs.GetBool("PolyMeshEditor_uvSettings", false); }
		set { EditorPrefs.SetBool("PolyMeshEditor_uvSettings", value); }
	}
	static bool editorSettings {
		get { return EditorPrefs.GetBool("PolyMeshEditor_editorSettings", false); }
		set { EditorPrefs.SetBool("PolyMeshEditor_editorSettings", value); }
	}
	static bool autoSnap {
		get { return EditorPrefs.GetBool("PolyMeshEditor_autoSnap", false); }
		set { EditorPrefs.SetBool("PolyMeshEditor_autoSnap", value); }
	}
	static bool globalSnap {
		get { return EditorPrefs.GetBool("PolyMeshEditor_globalSnap", false); }
		set { EditorPrefs.SetBool("PolyMeshEditor_globalSnap", value); }
	}
	static float gridSnap {
		get { return EditorPrefs.GetFloat("PolyMeshEditor_gridSnap", 1); }
		set { EditorPrefs.SetFloat("PolyMeshEditor_gridSnap", value); }
	}
	static bool hideWireframe {
		get { return EditorPrefs.GetBool("PolyMeshEditor_hideWireframe", true); }
		set { EditorPrefs.SetBool("PolyMeshEditor_hideWireframe", value); }
	}
	public KeyCode editKey {
		get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_editKey", (int)KeyCode.Tab); }
		set { EditorPrefs.SetInt("PolyMeshEditor_editKey", (int)value); }
	}
	public KeyCode selectAllKey {
		get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_selectAllKey", (int)KeyCode.A); }
		set { EditorPrefs.SetInt("PolyMeshEditor_selectAllKey", (int)value); }
	}
	public KeyCode splitKey {
		get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_splitKey", (int)KeyCode.S); }
		set { EditorPrefs.SetInt("PolyMeshEditor_splitKey", (int)value); }
	}
	public KeyCode extrudeKey {
		get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_extrudeKey", (int)KeyCode.D); }
		set { EditorPrefs.SetInt("PolyMeshEditor_extrudeKey", (int)value); }
	}
	#endregion
	#region Menu Items
	[MenuItem("GameObject/Create Other/PolyMesh", false, 1000)]
	static void CreatePolyMesh() {
		var obj = new GameObject("PolyMesh", typeof(MeshFilter), typeof(MeshRenderer));
		var polyMesh = obj.AddComponent();
		CreateSquare(polyMesh, 0.5f);
	}
	static void CreateSquare(PolyMesh polyMesh, float size) {
		polyMesh.keyPoints.AddRange(new Vector3[] { new Vector3(size, size), new Vector3(size, -size), new Vector3(-size, -size), new Vector3(-size, size)} );
		polyMesh.curvePoints.AddRange(new Vector3[] { Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero } );
		polyMesh.isCurve.AddRange(new bool[] { false, false, false, false } );
		polyMesh.BuildMesh();
	}
	#endregion
}