// https://github.com/CedricGuillemet/ImGuizmo
// v 1.89 WIP
//
// The MIT License(MIT)
//
// Copyright(c) 2021 Cedric Guillemet
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include "pch.h"
#include "ImCurveEdit.h"
#include "imgui.h"
#include "imgui_internal.h"
#include <stdint.h>
#include <set>
#include <vector>

#include "engine/EditorNew.h"
#include "engine/IO.h"

#if defined(_MSC_VER) || defined(__MINGW32__)
#include <malloc.h>
#endif
#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR)
#define _malloca(x) alloca(x)
#define _freea(x)
#endif

namespace ImCurveEdit
{

#ifndef IMGUI_DEFINE_MATH_OPERATORS
   static ImVec2 operator+(const ImVec2& a, const ImVec2& b) {
      return ImVec2(a.x + b.x, a.y + b.y);
   }

   static ImVec2 operator-(const ImVec2& a, const ImVec2& b) {
      return ImVec2(a.x - b.x, a.y - b.y);
   }

   static ImVec2 operator*(const ImVec2& a, const ImVec2& b) {
      return ImVec2(a.x * b.x, a.y * b.y);
   }

   static ImVec2 operator/(const ImVec2& a, const ImVec2& b) {
      return ImVec2(a.x / b.x, a.y / b.y);
   }

   static ImVec2 operator*(const ImVec2& a, const float b) {
      return ImVec2(a.x * b, a.y * b);
   }
#endif

   static float smoothstep(float edge0, float edge1, float x)
   {
      x = ImClamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
      return x * x * (3 - 2 * x);
   }

   static float distance(float x, float y, float x1, float y1, float x2, float y2)
   {
      float A = x - x1;
      float B = y - y1;
      float C = x2 - x1;
      float D = y2 - y1;

      float dot = A * C + B * D;
      float len_sq = C * C + D * D;
      float param = -1.f;
      if (len_sq > FLT_EPSILON)
         param = dot / len_sq;

      float xx, yy;

      if (param < 0.f) {
         xx = x1;
         yy = y1;
      }
      else if (param > 1.f) {
         xx = x2;
         yy = y2;
      }
      else {
         xx = x1 + param * C;
         yy = y1 + param * D;
      }

      float dx = x - xx;
      float dy = y - yy;
      return sqrtf(dx * dx + dy * dy);
   }

   static int DrawPoint(ImDrawList* draw_list, ImVec2 pos, const ImVec2 size, const ImVec2 offset, bool edited, uint32_t col, uint8_t opacity = 255) {
      if(opacity != 255) {
         col = col & 0x00FFFFFF;
         col = col | (uint32_t(opacity) << 24);
      }
      int ret = 0;
      ImGuiIO& io = ImGui::GetIO();

      static const ImVec2 localOffsets[4] = { ImVec2(1,0), ImVec2(0,1), ImVec2(-1,0), ImVec2(0,-1) };
      ImVec2 offsets[4];
      for (int i = 0; i < 4; i++)
      {
         offsets[i] = pos * size + localOffsets[i] * 4.5f + offset;
      }

      const ImVec2 center = pos * size + offset;
      const ImRect anchor(center - ImVec2(5, 5), center + ImVec2(5, 5));
      draw_list->AddConvexPolyFilled(offsets, 4, 0xFF000000);
      if (anchor.Contains(io.MousePos))
      {
         ret = 1;
         if (io.MouseDown[0])
            ret = 2;
      }
      if (edited)
         draw_list->AddPolyline(offsets, 4, 0xFFFFFFFF, true, 3.0f);
      else if (ret) {
         // uint32_t r = (col & 0x00FF0000) >> 4;
         // r += 20;
         // if(r > 255) {
         //    r = 255;
         // }
         // col = col & 0xFF00FFFF;
         // col = col | r;


         for(int i = 0; i < 3; i++) {
            uint32_t offs = 0x000000FF;
            // offs = 255 - offs;
            offs = offs << (i*8);
            uint32_t c = col & offs;
            c = c >> (i*8);
            // c = 255 - c;
            c = (0x000000FF) - c;
            c += 20;
            
            if(c < 150) {
               // c = 254;
               c += 70;
            }
            
            if(c > 254) {
               c = 254;
            }
            
            
            c = c << (i*8);
            
            col = col & (~offs);
            col = col | c;
         }

         
         // r = uint32_t()
         // r = min(r,)
         // r = r >> 4;
         // uint32_t g = col & 0x0000FF00;
         // g = g >> 4;
         // uint32_t b = col & 0x000000FF;
         
         // draw_list->AddPolyline(offsets, 4, 0xFF80B0FF, true, 2.0f);
         draw_list->AddPolyline(offsets, 4, col, true, 2.0f);
      }
      else
         draw_list->AddPolyline(offsets, 4, col, true, 2.0f);
      

      return ret;
   }

   ImVec2 Delegate::GetCurvePointAtTime(int curve_idx, float time) {
      time *= 20;
      const int pt_count = this->GetPointCount(curve_idx);

      double tolerance = 1e-6;
      int max_iterations = 20;

      
      if(pt_count > 0) {
         CurvePoint* points = this->GetPoints(curve_idx);
         int chosen_pt_idx = 0;
         ImVec2 pt = points[chosen_pt_idx].point;
         for(int pt_idx = 0; pt_idx < pt_count; pt_idx++) {
            ImVec2 curr_pt = points[pt_idx].point;
            if(time < curr_pt.x) {
               break;
            }
            chosen_pt_idx = pt_idx;
            pt = curr_pt;
         }

         if(chosen_pt_idx >= pt_count - 1) {
            return pt;
         } else {
            ImVec2 next_pt = points[chosen_pt_idx + 1].point;

            // ImVec4 handles_me = WEngine->editor->timeline.rampEdit.curve_point_handles[curve_idx][chosen_pt_idx];
            ImVec4 handles_me = WEngine->editor->timeline.rampEdit.curve_points[curve_idx][chosen_pt_idx].handles;
            ImVec4 handles_other = WEngine->editor->timeline.rampEdit.curve_points[curve_idx][chosen_pt_idx+1].handles;
            
            ImVec2 handle_a = ImVec2(handles_me.z, handles_me.w);
            ImVec2 handle_b = ImVec2(handles_other.x, handles_other.y);

            glm::vec2 a = glm::vec2(pt.x, pt.y);
            glm::vec2 b = glm::vec2(next_pt.x, next_pt.y);
            glm::vec2 ca = glm::vec2(handles_me.z, handles_me.w);
            glm::vec2 cb = glm::vec2(handles_other.x, handles_other.y);
            ca += a;
            cb += b;
            
      // Lambda for computing the x-coordinate of the cubic Bezier curve
      auto cubicBezierX = [&](double t) -> double {
         double u = 1.0 - t;
         return u*u*u*a.x + 3.0*u*u*t*ca.x + 3.0*u*t*t*cb.x + t*t*t*b.x;
      };

      // Lambda for finding t using the bisection method
      auto findTForX = [&](double k_x) -> double {
         double lower = 0.0;
         double upper = 1.0;
         int iterations = 0;

         while (iterations < max_iterations) {
            double t = (lower + upper) / 2.0;
            double x = cubicBezierX(t);

            if (x > k_x) {
               upper = t;
            } else if (x < k_x) {
               lower = t;
            } else {
               return t; // Found exact match
            }

            if ((upper - lower) < tolerance) {
               return t; // Found approximate match
            }

            iterations++;
         }

         return -1.0; // Indicate failure to find t within given tolerance
      };

            // auto get_curve_val_at_pt_and_t = [&](
            //    ImVec2 p1,
            //    ImVec2 p2,
            //    float t
            // ) -> ImVec2 {
            //    // const ImVec2 sp1 = ImLerp(p1, p2, t);
            //    // const float rt1 = smoothstep(p1.x, p2.x, sp1.x); 
            //    // return ImVec2(sp1.x, ImLerp(p1.y, p2.y, rt1));
            //    
            // };
            
            float x = (time - pt.x)/(next_pt.x - pt.x);

            // float iter_idx = 0.5;
            // for(float i =0.; i < 1000.; i++) {
            //    float t = iter_idx;
            //    // return cubicBezierX(t);
            //    glm::vec2 g = a * (1.0f - t) * (1.0f - t) * (1.0f - t) + 3.0f * (1.0f - t) * (1.0f - t) * t * ca +
            //       3.0f * (1.0f - t) * t * t * cb + t * t * t * b;
            //    if()
            //    
            // }
            
            float t = findTForX(time);
            // return cubicBezierX(t);
            glm::vec2 g = a * (1.0f - t) * (1.0f - t) * (1.0f - t) + 3.0f * (1.0f - t) * (1.0f - t) * t * ca +
               3.0f * (1.0f - t) * t * t * cb + t * t * t * b;

            return ImVec2(g.x,g.y);
            // return g;
            // return get_curve_val_at_pt_and_t(pt, next_pt, t);
         }
         
      } else {
         return ImVec2(0,0);
      }
   }

   int EditAndDisplayCurve(
      Delegate& delegate,
      const ImVec2& size,
      unsigned int id,
      const ImRect* clippingRect,
      ImVector<EditPoint>* selectedPoints,
      Lane* lane
   ) {
      static bool selectingQuad = false;
      static ImVec2 quadSelection;
      static int overCurve = -1;
      static int movingCurve = -1;
      static bool scrollingV = false;
      static std::set<EditPoint> selection;

      struct SelectedHandle {
         int handle_idx = 0;
         ImVec4* selected_handle = nullptr;
         Curve* selected_handle_curve = nullptr;
      };
      static SelectedHandle selected_handle;


      // static bool handle_is_selected = false;
      // static Curve* selected_handle_curve = nullptr;
      // static ImVec4* selected_handle = nullptr;
      // static ImVec4* selected_handle = nullptr;
      
      static bool overSelectedPoint = false;

      int ret = 0;

      ImGuiIO& io = ImGui::GetIO();
      if(!io.MouseDown[0]) {
         selected_handle.selected_handle = nullptr;
      }
         
      ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
      ImGui::PushStyleColor(ImGuiCol_Border, 0);
      ImGui::BeginChildFrame(id, size * ImVec2(2,1));
      delegate.focused = ImGui::IsWindowFocused();
      ImDrawList* draw_list = ImGui::GetWindowDrawList();
      if (clippingRect)
         draw_list->PushClipRect(clippingRect->Min, clippingRect->Max, true);

      const ImVec2 draw_offset_screenspace = ImGui::GetCursorScreenPos() + ImVec2(-6.f, size.y);
      const ImVec2 draw_size_screenspace(size.x, -size.y);
      const ImRect rect_container(draw_offset_screenspace + ImVec2(0.f, draw_size_screenspace.y), draw_offset_screenspace + ImVec2(draw_size_screenspace.x, 0.f));
      ImVec2& min = delegate.GetMin();
      ImVec2& max = delegate.GetMax();

      // handle zoom and VScroll
      if (rect_container.Contains(io.MousePos)) {
         if (fabsf(io.MouseWheel) > FLT_EPSILON) {
            // const float r = (io.MousePos.y - draw_offset_screenspace.y) / draw_size_screenspace.y;
            // float ratioY = ImLerp(min.y, max.y, r);
            // auto scaleValue = [&](float v) {
            //    v -= ratioY;
            //    v *= (1.f - io.MouseWheel * 0.05f);
            //    v += ratioY;
            //    return v;
            // };
            // min.y = scaleValue(min.y);
            // max.y = scaleValue(max.y);
         }
         if (!scrollingV && ImGui::IsMouseDown(2)) {
            scrollingV = true;
         }
      }
      ImVec2 range = max - min + ImVec2(1.f, 0.f);  // +1 because of inclusive last frame

      const ImVec2 viewSize(size.x, -size.y);
      const ImVec2 sizeOfPixel = ImVec2(1.f, 1.f) / viewSize;
      // const size_t curveCount = delegate.GetCurveCount();
      const size_t curveCount = lane->curves.size();

      if (scrollingV) {
         float deltaH = io.MouseDelta.y * range.y * sizeOfPixel.y;
         min.y -= deltaH;
         max.y -= deltaH;
         if (!ImGui::IsMouseDown(2))
            scrollingV = false;
      }

      draw_list->AddRectFilled(draw_offset_screenspace, draw_offset_screenspace + draw_size_screenspace, delegate.GetBackgroundColor());

      auto pointToRange = [&](ImVec2 pt) { return (pt - min) / range; };
      auto rangeToPoint = [&](ImVec2 pt) { return (pt * range) + min; };

      draw_list->AddLine(ImVec2(-1.f, -min.y / range.y) * viewSize + draw_offset_screenspace, ImVec2(1.f, -min.y / range.y) * viewSize + draw_offset_screenspace, 0xFF000000, 1.5f);
      bool overCurveOrPoint = false;

      int localOverCurve = -1;
      // make sure highlighted curve is rendered last
      int* curvesIndex = (int*)_malloca(sizeof(int) * curveCount);
      for (size_t c = 0; c < curveCount; c++)
         curvesIndex[c] = lane->curves[c].curve_idx;
      int highLightedCurveIndex = -1;
      if (overCurve != -1 && curveCount && curveCount > 1) {
         ImSwap(curvesIndex[overCurve], curvesIndex[curveCount - 1]);
         highLightedCurveIndex = overCurve;
      }

      
      // --------- DRAW CURVE --------- //
      for (size_t cur = 0; cur < curveCount; cur++) {
         Curve& curve = lane->curves[cur];
         // int c = curvesIndex[cur];
         int c = curve.curve_idx;

         if(c > 100) {
            wlog_err("too many curve");
         }
         if (!*curve.curve_is_visible)
            continue;

         const size_t ptCount = *curve.curve_point_count;
         if (ptCount < 1)
            continue;
         CurveType curveType = delegate.GetCurveType(c);
         if (curveType == CurveNone)
            continue;

         const CurvePoint* pts = curve.curve_points;
         // uint32_t curveColor = delegate.GetCurveColor(c);
         uint32_t curveColor = curve.colour;
         if ((c == highLightedCurveIndex && selection.empty() && !selectingQuad && selected_handle.selected_handle == nullptr) || movingCurve == c)
            curveColor = 0xFFFFFFFF;

         for (size_t pt_idx = 0; pt_idx < ptCount - 1; pt_idx++) {
            const ImVec2 pt_1 = pointToRange(pts[pt_idx].point);
            const ImVec2 pt_2 = pointToRange(pts[pt_idx + 1].point);

            if (curveType == CurveSmooth || curveType == CurveLinear) {
               size_t subStepCount = (curveType == CurveSmooth) ? 40 : 2;
               float step = 1.f / float(subStepCount - 1);
               for (size_t substep = 0; substep < subStepCount - 1; substep++) {
                  float t = float(substep) * step;
                  auto get_curve_val_at_pt_and_t = [&](int curve_idx, int point_idx, float t) -> ImVec2 {
                     ImVec4 handles_me = curve.curve_points[point_idx].handles;
                     ImVec4 handles_other = curve.curve_points[point_idx + 1].handles;

                     ImVec2 handle_a = pointToRange(ImVec2(handles_me.z, handles_me.w));
                     ImVec2 handle_b = pointToRange(ImVec2(handles_other.x, handles_other.y));

                     const ImVec2 p1 = pointToRange(pts[point_idx].point);
                     const ImVec2 p2 = pointToRange(pts[point_idx + 1].point);
                     // glm::vec2 a = glm::vec2(pts[point_idx].x, pts[point_idx].y);
                     // glm::vec2 b = glm::vec2(pts[point_idx+1].x, pts[point_idx+1].y);
                     glm::vec2 a = glm::vec2(p1.x, p1.y);
                     glm::vec2 b = glm::vec2(p2.x, p2.y);
                     
                     // glm::vec2 ca = a + glm::vec2(handles_me.z, handles_me.w);
                     // glm::vec2 cb = b + glm::vec2(handles_other.x, handles_other.y);
                     glm::vec2 ca = a + glm::vec2(handle_a.x, handle_a.y);
                     glm::vec2 cb = b + glm::vec2(handle_b.x, handle_b.y);
                     
                     glm::vec2 k = t*t*(a-ca-cb+b) + t*(ca+cb-a*2.0f) + a;
                     
                     glm::vec2 q = glm::mix(a,ca,t);
                     glm::vec2 j = glm::mix(cb,b,t);
                     // k = glm::mix(q,j,t*1.0);
                     
                     k =  a * (1.0f-t)*(1.0f-t)*(1.0f-t) + 3.0f*(1.0f-t)*(1.0f-t)*t*ca + 3.0f*(1.0f-t)*t*t*cb + t*t*t*b;
                     
                     return ImVec2(k.x, k.y);
                  };
                  auto curve_to_gui_space = [&](ImVec2 pt) {
                     return pt * viewSize + draw_offset_screenspace;
                  };

                  ImVec2 curve_pt_1 = get_curve_val_at_pt_and_t(cur, pt_idx, t);
                  ImVec2 curve_pt_2 = get_curve_val_at_pt_and_t(cur, pt_idx, t + step);

                  ImVec2 curve_pt_1_guispace = curve_to_gui_space(curve_pt_1);
                  ImVec2 curve_pt_2_guispace = curve_to_gui_space(curve_pt_2);

                  if (distance(io.MousePos.x, io.MousePos.y, curve_pt_1_guispace.x, curve_pt_1_guispace.y, curve_pt_2_guispace.x, curve_pt_2_guispace.y) < 8.f && !scrollingV) {
                     localOverCurve = int(c);
                     overCurve = int(c);
                     overCurveOrPoint = true;
                  }

                  draw_list->AddLine(curve_pt_1_guispace, curve_pt_2_guispace, curveColor, 1.3f);
               } // substep
            } else if (curveType == CurveDiscrete) {
               ImVec2 dp1 = pt_1 * viewSize + draw_offset_screenspace;
               ImVec2 dp2 = ImVec2(pt_2.x, pt_1.y) * viewSize + draw_offset_screenspace;
               ImVec2 dp3 = pt_2 * viewSize + draw_offset_screenspace;
               draw_list->AddLine(dp1, dp2, curveColor, 1.3f);
               draw_list->AddLine(dp2, dp3, curveColor, 1.3f);

               if ((distance(io.MousePos.x, io.MousePos.y, dp1.x, dp1.y, dp3.x, dp1.y) < 8.f ||
                  distance(io.MousePos.x, io.MousePos.y, dp3.x, dp1.y, dp3.x, dp3.y) < 8.f)
                  /*&& localOverCurve == -1*/)
               {
                  localOverCurve = int(c);
                  overCurve = int(c);
                  overCurveOrPoint = true;
               }
            }
         } // point loop

         // --------- DRAW POINTS --------- //
         for (size_t p = 0; p < ptCount; p++) {
            const int drawState = DrawPoint(
               draw_list,
               pointToRange(pts[p].point), 
               viewSize,
               draw_offset_screenspace,
               (selection.find({ int(c), int(p) }) != selection.end() && movingCurve == -1 && !scrollingV),
               curve.colour
            );

            ImVec2 handle_a(curve.curve_points[p].handles.x, curve.curve_points[p].handles.y);
            ImVec2 handle_b(curve.curve_points[p].handles.z, curve.curve_points[p].handles.w);

            
            draw_list->AddLine(
               pointToRange(handle_a + pts[p].point)*viewSize + draw_offset_screenspace,
               pointToRange(pts[p].point)*viewSize + draw_offset_screenspace, curveColor,
               -0.0f
            );
            draw_list->AddLine(
               pointToRange(handle_b + pts[p].point)*viewSize + draw_offset_screenspace,
               pointToRange(pts[p].point)*viewSize + draw_offset_screenspace, curveColor,
               -0.0f
            );
            // draw_list->AddLine(dp2, dp3, curveColor, 1.3f);
            const int drawState_handle_a = DrawPoint(
               draw_list,
               pointToRange(handle_a + pts[p].point), 
               viewSize,
               draw_offset_screenspace,
               // (selection.find({ int(c), int(p) }) != selection.end() && movingCurve == -1 && !scrollingV),
               false,
               curve.colour,
               100
            );
            const int drawState_handle_b = DrawPoint(
               draw_list,
               pointToRange(handle_b+ pts[p].point), 
               viewSize,
               draw_offset_screenspace,
               // (selection.find({ int(c), int(p) }) != selection.end() && movingCurve == -1 && !scrollingV),
               false,
               curve.colour,
               100
            );
            int selected_handles[] = {drawState, drawState_handle_a, drawState_handle_b};
            for(int i = 0; i < 3; i++) {
               int drawState = selected_handles[i];
               if(drawState && movingCurve == -1 && !selectingQuad ) {
                  overCurveOrPoint = true;
                  overSelectedPoint = true;
                  overCurve = -1;
                  if( WEngine->io->mouse_lmb_just_pressed) {
                     if(i == 0) {
                        if (drawState == 2) {
                           if (!io.KeyShift && selection.find({ int(c), int(p) }) == selection.end())
                              selection.clear();
                           selection.insert({ int(c), int(p) });
                        }
                     } else {
                        if (drawState == 2) {
                           // if (!io.KeyShift && selection.find({ int(c), int(p) }) == selection.end())
                           //    selection.clear();
                           // selection.insert({ int(c), int(p) });
                           selected_handle.handle_idx = i - 1;
                           selected_handle.selected_handle = &curve.curve_points[p].handles;
                           selected_handle.selected_handle_curve = &curve;
                           selection.clear();
                           wlog_warn("potato");
                        }
                     }
                  }
                  break;
               }
            }
            
            // if (drawState && movingCurve == -1 && !selectingQuad) {
            //    overCurveOrPoint = true;
            //    overSelectedPoint = true;
            //    overCurve = -1;
            //    if (drawState == 2) {
            //       if (!io.KeyShift && selection.find({ int(c), int(p) }) == selection.end())
            //          selection.clear();
            //       selection.insert({ int(c), int(p) });
            //    }
            // }
         }
      } // curves loop

      if (localOverCurve == -1)
         overCurve = -1;

      // --------- MOVE SELECTION --------- //
      static bool pointsMoved = false;
      static ImVec2 mousePosOrigin;
      static std::vector<ImVec2> originalPoints;
      static ImVec2 originalHandle;
      if (overSelectedPoint && io.MouseDown[0]) {
         if (
            (fabsf(io.MouseDelta.x) > 0.f || fabsf(io.MouseDelta.y) > 0.f) 
            && (
               !selection.empty()
               || selected_handle.selected_handle != nullptr
            )
         ) {
            if(selected_handle.selected_handle != nullptr) {
               // ----------------- MOVE HANDLES ------------------ //
               glm::vec2 selected_handle_vec;
               ImVec2 p;
               if(selected_handle.handle_idx == 0) {
                  p = ImVec2(selected_handle.selected_handle->x, selected_handle.selected_handle->y);
                  selected_handle_vec = glm::vec2(p.x,p.y);
               } else {
                  p = ImVec2(selected_handle.selected_handle->z, selected_handle.selected_handle->w);
                  selected_handle_vec = glm::vec2(p.x,p.y);
               }
               
               if (!pointsMoved) {
                  delegate.BeginEdit(0);
                  mousePosOrigin = io.MousePos;
                  originalHandle = p;
               }
               pointsMoved = true;
               
               p = rangeToPoint(pointToRange(originalHandle) + (io.MousePos - mousePosOrigin) * sizeOfPixel);
               
               if(selected_handle.handle_idx == 0) {
                  selected_handle.selected_handle->x = p.x;
                  selected_handle.selected_handle->y = p.y;
               } else {
                  selected_handle.selected_handle->z = p.x;
                  selected_handle.selected_handle->w = p.y;
               }
               if(WEngine->io->get_key(Key::LShift).down) {
                  glm::vec2 other_handle;
                  if(selected_handle.handle_idx == 0) {
                     other_handle = glm::vec2(selected_handle.selected_handle->z, selected_handle.selected_handle->w);
                  } else{
                     other_handle = glm::vec2(selected_handle.selected_handle->x, selected_handle.selected_handle->y);
                  }
                  other_handle = glm::length(other_handle) * glm::normalize(selected_handle_vec) * -1.0f;
                  
                  if(selected_handle.handle_idx == 0) {
                     selected_handle.selected_handle->z = other_handle.x;
                     selected_handle.selected_handle->w = other_handle.y;
                  } else{
                     selected_handle.selected_handle->x = other_handle.x;
                     selected_handle.selected_handle->y = other_handle.y;
                  }
               // other_
               }
            } else {
               // ----------------- MOVE POINTS ------------------ //
               if (!pointsMoved) {
                  delegate.BeginEdit(0);
                  mousePosOrigin = io.MousePos;
                  originalPoints.resize(selection.size());
                  int index = 0;
                  for (auto& sel : selection) {
                     const CurvePoint* pts = delegate.GetPoints(sel.curveIndex);
                     originalPoints[index++] = pts[sel.pointIndex].point;
                  }
               }
               pointsMoved = true;
               ret = 1;
               auto prevSelection = selection;
               int originalIndex = 0;
               for (auto& sel : prevSelection) {
                  ImVec2 p = rangeToPoint(pointToRange(originalPoints[originalIndex]) + (io.MousePos - mousePosOrigin) * sizeOfPixel);
                  if(WEngine->io->get_key(Key::LShift).down) {
                     float md = 5.0/4;
                     p.x = glm::floor(p.x/md)*md;
                  }
                  const int newIndex = delegate.EditPoint(sel.curveIndex, sel.pointIndex, p);
                  if (newIndex != sel.pointIndex) {
                     selection.erase(sel);
                     selection.insert({ sel.curveIndex, newIndex });
                  }
                  originalIndex++;
               }
            }
         }
      } else if (overSelectedPoint && io.MouseDown[1] && !selected_handle.selected_handle) {
         // selection.insert({ sel.curveIndex, newIndex });
         for(const EditPoint& s : selection) {
            delegate.DeletePoint(s.curveIndex, s.pointIndex);
            break;
         }
         selection.clear();
      }

      if (overSelectedPoint && !io.MouseDown[0]) {
         // ----------------- DELETE POINTS ------------------ //
         overSelectedPoint = false;
         if (pointsMoved) {
            pointsMoved = false;
            delegate.EndEdit();
         }
      }

      // --------- ADD POINT --------- //
      if (overCurve != -1 && io.MouseDoubleClicked[0]) {
         const ImVec2 np = rangeToPoint((io.MousePos - draw_offset_screenspace) / viewSize);
         delegate.BeginEdit(overCurve);
         delegate.AddPoint(overCurve, np);
         delegate.EndEdit();
         ret = 1;
      }

      // --------- MOVE CURVE --------- //
      if (movingCurve != -1 && false) {
         // const size_t ptCount = delegate.GetPointCount(movingCurve);
         // const ImVec2* pts = delegate.GetPoints(movingCurve);
         // if (!pointsMoved)
         // {
         //    mousePosOrigin = io.MousePos;
         //    pointsMoved = true;
         //    originalPoints.resize(ptCount);
         //    for (size_t index = 0; index < ptCount; index++)
         //    {
         //       originalPoints[index] = pts[index];
         //    }
         // }
         // if (ptCount >= 1) {
         //    for (size_t p = 0; p < ptCount; p++) {
         //       delegate.EditPoint(movingCurve, int(p), rangeToPoint(pointToRange(originalPoints[p]) + (io.MousePos - mousePosOrigin) * sizeOfPixel));
         //    }
         //    ret = 1;
         // }
         // if (!io.MouseDown[0]) {
         //    movingCurve = -1;
         //    pointsMoved = false;
         //    delegate.EndEdit();
         // }
      }
      
      // if (movingCurve == -1 && overCurve != -1 && ImGui::IsMouseClicked(0) && selection.empty() && !selectingQuad) {
      //    movingCurve = overCurve;
      //    delegate.BeginEdit(overCurve);
      // }

      // --------- QUAD SELECTION --------- //
      if (selectingQuad) {
         const ImVec2 bmin = ImMin(quadSelection, io.MousePos);
         const ImVec2 bmax = ImMax(quadSelection, io.MousePos);
         draw_list->AddRectFilled(bmin, bmax, 0x40FF0000, 1.f);
         draw_list->AddRect(bmin, bmax, 0xFFFF0000, 1.f);
         const ImRect selectionQuad(bmin, bmax);
         if (!io.MouseDown[0]) {
            if (!io.KeyShift)
               selection.clear();
            // select everythnig is quad
            for (size_t cur = 0; cur < curveCount; cur++) {
               Curve& curve = lane->curves[cur];
               // size_t c = curvesIndex[cur];
               if (!*curve.curve_is_visible)
                  continue;

               const size_t ptCount = *curve.curve_point_count;
               if (ptCount < 1)
                  continue;
               
               const CurvePoint* pts = curve.curve_points;
               for (size_t p = 0; p < ptCount; p++) {
                  const ImVec2 center = pointToRange(pts[p].point) * viewSize + draw_offset_screenspace;
                  if (selectionQuad.Contains(center))
                     selection.insert({ int(curve.curve_idx), int(p) });
               }
            }
            // done
            selectingQuad = false;
         }
      }
      // selectingQuad = true;
      if (
         !overCurveOrPoint && 
         ImGui::IsMouseClicked(0) && 
         !selectingQuad && 
         movingCurve == -1 && 
         !overSelectedPoint && 
         rect_container.Contains(io.MousePos)
      ) {
         selectingQuad = true;
         quadSelection = io.MousePos;
      }
      if (clippingRect)
         draw_list->PopClipRect();

      ImGui::EndChildFrame();
      ImGui::PopStyleVar();
      ImGui::PopStyleColor(1);

      if (selectedPoints) {
         selectedPoints->resize(int(selection.size()));
         int index = 0;
         for (auto& point : selection)
            (*selectedPoints)[index++] = point;
      }
      return ret;
   }
}
