import React from "react";
import { useEffect, useState } from "react";
import { render } from "react-dom";
import { IWallProps } from "./PhysicsSimulationWall";
import Wedge from "./PhysicsSimulationWedge";

export interface IForce {
  description: string;
  magnitude: number;
  directionInDegrees: number;
}
export interface IWeightProps {
  adjustPendulumAngle: { angle: number; length: number };
  color: string;
  displayXPosition: number;
  displayYPosition: number;
  displayXVelocity: number;
  displayYVelocity: number;
  elasticCollisions: boolean;
  startForces: IForce[];
  incrementTime: number;
  mass: number;
  paused: boolean;
  pendulum: boolean;
  pendulumLength: number;
  wedge: boolean;
  radius: number;
  reset: boolean;
  setDisplayXAcceleration: (val: number) => any;
  setDisplayXPosition: (val: number) => any;
  setDisplayXVelocity: (val: number) => any;
  setDisplayYAcceleration: (val: number) => any;
  setDisplayYPosition: (val: number) => any;
  setDisplayYVelocity: (val: number) => any;
  setPaused: (bool: boolean) => any;
  setPendulumAngle: (val: number) => any;
  setPendulumLength: (val: number) => any;
  setStartPendulumAngle: (val: number) => any;
  showAcceleration: boolean;
  pendulumAngle: number;
  setSketching: (val: boolean) => any;
  showForces: boolean;
  showForceMagnitudes: boolean;
  showVelocity: boolean;
  startPosX: number;
  startPosY: number;
  startVelX?: number;
  startVelY?: number;
  timestepSize: number;
  updateDisplay: { xDisplay: number; yDisplay: number };
  updatedForces: IForce[];
  setUpdatedForces: (val: IForce[]) => any;
  walls: IWallProps[];
  coefficientOfKineticFriction: number;
  wedgeWidth: number;
  wedgeHeight: number;
}

interface IState {
  dragging: boolean,
  kineticFriction: boolean,
  updatedStartPosX: number,
  updatedStartPosY: number,
  xPosition: number,
  yPosition: number,
  xVelocity: number,
  yVelocity: number,
}
export default class Weight extends React.Component<IWeightProps, IState> {

  constructor(props: any) {
    super(props)
    this.state = {
      dragging: false,
      kineticFriction: false,
      updatedStartPosX: this.props.startPosX,
      updatedStartPosY: this.props.startPosY,
      xPosition: this.props.startPosX,
      yPosition: this.props.startPosY,
      xVelocity: this.props.startVelX ? this.props.startVelX: 0,
      yVelocity: this.props.startVelY ? this.props.startVelY: 0,
    }
  }

  // Constants
  draggable = !this.props.wedge;
  epsilon = 0.0001;
  forceOfGravity: IForce = {
    description: "Gravity",
    magnitude: this.props.mass * 9.81,
    directionInDegrees: 270,
  };
  xMax = window.innerWidth * 0.7;
  xMin = 0;
  yMax = window.innerHeight * 0.8;
  yMin = 0;

  // Helper function to go between display and real values
  const getDisplayYPos = (yPos: number) => {
    return this.yMax - yPos - 2 * this.props.radius + 5;
  };
  const getYPosFromDisplay = (yDisplay: number) => {
    return this.yMax - yDisplay - 2 * this.props.radius + 5;
  };

  // Set display values based on real values
  const setYPosDisplay = (yPos: number) => {
    const displayPos = this.getDisplayYPos(yPos);
    this.props.setDisplayYPosition(Math.round(displayPos * 100) / 100)
  };
  const setXPosDisplay = (xPos: number) => {
    this.props.setDisplayXPosition(Math.round(xPos * 100) / 100);
  };
  const setYVelDisplay = (yVel: number) => {
    this.props.setDisplayYVelocity((-1 * Math.round(yVel * 100)) / 100);
  };
  const setXVelDisplay = (xVel: number) => {
    this.props.setDisplayXVelocity(Math.round(xVel * 100) / 100);
  };

  const setDisplayValues = (
    xPos: number = this.state.xPosition,
    yPos: number = this.state.yPosition,
    xVel: number = this.state.xVelocity,
    yVel: number = this.state.yVelocity
  ) => {
    this.setYPosDisplay(yPos);
    this.setXPosDisplay(xPos);
    this.setYVelDisplay(yVel);
    this.setXVelDisplay(xVel);
    this.props.setDisplayYAcceleration(
      (-1 * Math.round(getNewAccelerationY(this.props.updatedForces) * 100)) / 100
    );
    this.props.setDisplayXAcceleration(
      Math.round(getNewAccelerationX(this.props.updatedForces) * 100) / 100
    );
  };

  // When display values updated by user, update real values
  componentDidUpdate(prevProps: Readonly<IWeightProps>, prevState: Readonly<IState>, snapshot?: any): void {
    if (this.props.updateDisplay != prevProps.updateDisplay) {
      if (this.props.updateDisplay.xDisplay != this.state.xPosition) {
        let x = this.props.updateDisplay.xDisplay;
        x = Math.max(0, x);
        x = Math.min(x, this.xMax - 2 * this.props.radius);
        this.setState({updatedStartPosX: x})
        this.setState({xPosition: x})
        this.props.setDisplayXPosition(x);
      }
  
      if (this.props.updateDisplay.yDisplay != this.getDisplayYPos(this.state.yPosition)) {
        let y = this.props.updateDisplay.yDisplay;
        y = Math.max(0, y);
        y = Math.min(y, this.yMax - 2 * this.props.radius);
        this.props.setDisplayYPosition(y);
        let coordinatePosition = this.getYPosFromDisplay(y);
        this.setState({updatedStartPosY: coordinatePosition})
        this.setState({yPosition: coordinatePosition})
      }
  
      if (this.props.displayXVelocity != this.state.xVelocity) {
        let x = this.props.displayXVelocity;
        this.setState({xVelocity: x})
        this.props.setDisplayXVelocity(x);
      }
  
      if (this.props.displayYVelocity != this.state.yVelocity) {
        let y = this.props.displayYVelocity;
        this.setState({yVelocity: -y})
        this.props.setDisplayXVelocity(y);
      }
    }
  }

  // Check for collisions and update
  useEffect(() => {
    if (!paused && !noMovement) {
      let collisions = false;
      if (!pendulum) {
        const collisionsWithGround = checkForCollisionsWithGround();
        const collisionsWithWalls = checkForCollisionsWithWall();
        collisions = collisionsWithGround || collisionsWithWalls;
      }
      if (!collisions) {
        update();
      }
      setDisplayValues();
    }
  }, [incrementTime]);

  useEffect(() => {
    resetEverything();
  }, [reset]);

  useEffect(() => {
    setXVelocity(startVelX ?? 0);
    setYVelocity(startVelY ?? 0);
    setDisplayValues();
  }, [startForces]);

  const resetEverything = () => {
    setKineticFriction(false);
    setXPosition(updatedStartPosX);
    setYPosition(updatedStartPosY);
    setXVelocity(startVelX ?? 0);
    setYVelocity(startVelY ?? 0);
    setUpdatedForces(startForces);
    setDisplayValues();
  };

  // Change pendulum angle based on input field
  useEffect(() => {
    let length = adjustPendulumAngle.length;
    const x =
      length * Math.cos(((90 - adjustPendulumAngle.angle) * Math.PI) / 180);
    const y =
      length * Math.sin(((90 - adjustPendulumAngle.angle) * Math.PI) / 180);
    const xPos = xMax / 2 - x - radius;
    const yPos = y - radius - 5;
    setXPosition(xPos);
    setYPosition(yPos);
    setUpdatedStartPosX(xPos);
    setUpdatedStartPosY(yPos);
    setPendulumAngle(adjustPendulumAngle.angle);
    setPendulumLength(adjustPendulumAngle.length);
  }, [adjustPendulumAngle]);

  const getNewAccelerationX = (forceList: IForce[]) => {
    let newXAcc = 0;
    forceList.forEach((force) => {
      newXAcc +=
        (force.magnitude *
          Math.cos((force.directionInDegrees * Math.PI) / 180)) /
        this.props.mass;
    });
    return newXAcc;
  };

  const getNewAccelerationY = (forceList: IForce[]) => {
    let newYAcc = 0;
    forceList.forEach((force) => {
      newYAcc +=
        (-1 *
          (force.magnitude *
            Math.sin((force.directionInDegrees * Math.PI) / 180))) /
          this.props.mass;
    });
    return newYAcc;
  };

  const getNewForces = (
    xPos: number,
    yPos: number,
    xVel: number,
    yVel: number
  ) => {
    if (!this.props.pendulum) {
      return this.state.updatedForces;
    }
    const x = this.xMax / 2 - xPos - this.props.radius;
    const y = yPos + this.props.radius + 5;
    let angle = (Math.atan(y / x) * 180) / Math.PI;
    if (angle < 0) {
      angle += 180;
    }
    let oppositeAngle = 90 - angle;
    if (oppositeAngle < 0) {
      oppositeAngle = 90 - (180 - angle);
    }

    const pendulumLength = Math.sqrt(x * x + y * y);
    setPendulumAngle(oppositeAngle);
    setPendulumLength(Math.sqrt(x * x + y * y));

    const mag =
      this.props.mass * 9.81 * Math.cos((oppositeAngle * Math.PI) / 180) +
      (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength;

    const forceOfTension: IForce = {
      description: "Tension",
      magnitude: mag,
      directionInDegrees: angle,
    };

    return [this.forceOfGravity, forceOfTension];
  };

  const getNewPosition = (pos: number, vel: number) => {
    return pos + vel * this.props.timestepSize;
  };

  const getNewVelocity = (vel: number, acc: number) => {
    return vel + acc * this.props.timestepSize;
  };

  const checkForCollisionsWithWall = () => {
    let collision = false;
    const minX = this.state.xPosition;
    const maxX = this.state.xPosition + 2 * this.props.radius;
    const containerWidth = window.innerWidth;
    if (this.state.xVelocity != 0) {
      this.props.walls.forEach((wall) => {
        if (wall.angleInDegrees == 90) {
          const wallX = (wall.xPos / 100) * window.innerWidth;
          if (wall.xPos < 0.35) {
            if (minX <= wallX) {
              if (elasticCollisions) {
                setXVelocity(-xVelocity);
              } else {
                setXVelocity(0);
                setXPosition(wallX + 5);
              }
              collision = true;
            }
          } else {
            if (maxX >= wallX) {
              if (elasticCollisions) {
                setXVelocity(-xVelocity);
              } else {
                setXVelocity(0);
                setXPosition(wallX - 2 * radius + 5);
              }
              collision = true;
            }
          }
        }
      });
    }
    return collision;
  };

  const checkForCollisionsWithGround = () => {
    let collision = false;
    const maxY = yPosition + 2 * radius;
    if (yVelocity > 0) {
      walls.forEach((wall) => {
        if (wall.angleInDegrees == 0) {
          const groundY = (wall.yPos / 100) * window.innerHeight;
          if (maxY >= groundY) {
            if (elasticCollisions) {
              setYVelocity(-yVelocity);
            } else {
              setYVelocity(0);
              setYPosition(groundY - 2 * radius + 5);
              const forceOfGravity: IForce = {
                description: "Gravity",
                magnitude: 9.81 * mass,
                directionInDegrees: 270,
              };
              const normalForce: IForce = {
                description: "Normal force",
                magnitude: 9.81 * mass,
                directionInDegrees: wall.angleInDegrees + 90,
              };
              setUpdatedForces([forceOfGravity, normalForce]);
            }
            collision = true;
          }
        }
      });
    }
    return collision;
  };

  useEffect(() => {
    if (wedge && xVelocity != 0 && mode != "Review" && !kineticFriction) {
      setKineticFriction(true);
      //switch from static to kinetic friction
      const normalForce: IForce = {
        description: "Normal Force",
        magnitude:
          forceOfGravity.magnitude *
          Math.cos(Math.atan(wedgeHeight / wedgeWidth)),
        directionInDegrees:
          180 - 90 - (Math.atan(wedgeHeight / wedgeWidth) * 180) / Math.PI,
      };
      let frictionForce: IForce = {
        description: "Kinetic Friction Force",
        magnitude:
          coefficientOfKineticFriction *
          forceOfGravity.magnitude *
          Math.cos(Math.atan(wedgeHeight / wedgeWidth)),
        directionInDegrees:
          180 - (Math.atan(wedgeHeight / wedgeWidth) * 180) / Math.PI,
      };
      // reduce magnitude of friction force if necessary such that block cannot slide up plane
      let yForce = -forceOfGravity.magnitude;
      yForce +=
        normalForce.magnitude *
        Math.sin((normalForce.directionInDegrees * Math.PI) / 180);
      yForce +=
        frictionForce.magnitude *
        Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
      if (yForce > 0) {
        frictionForce.magnitude =
          (-normalForce.magnitude *
            Math.sin((normalForce.directionInDegrees * Math.PI) / 180) +
            forceOfGravity.magnitude) /
          Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
      }
      if (coefficientOfKineticFriction != 0) {
        setUpdatedForces([forceOfGravity, normalForce, frictionForce]);
      } else {
        setUpdatedForces([forceOfGravity, normalForce]);
      }
    }
  }, [xVelocity]);

  const update = () => {
    // RK4 update
    let xPos = xPosition;
    let yPos = yPosition;
    let xVel = xVelocity;
    let yVel = yVelocity;
    for (let i = 0; i < 60; i++) {
      let forces1 = getNewForces(xPos, yPos, xVel, yVel);
      const xAcc1 = getNewAccelerationX(forces1);
      const yAcc1 = getNewAccelerationY(forces1);
      const xVel1 = getNewVelocity(xVel, xAcc1);
      const yVel1 = getNewVelocity(yVel, yAcc1);

      let xVel2 = getNewVelocity(xVel, xAcc1 / 2);
      let yVel2 = getNewVelocity(yVel, yAcc1 / 2);
      let xPos2 = getNewPosition(xPos, xVel1 / 2);
      let yPos2 = getNewPosition(yPos, yVel1 / 2);
      const forces2 = getNewForces(xPos2, yPos2, xVel2, yVel2);
      const xAcc2 = getNewAccelerationX(forces2);
      const yAcc2 = getNewAccelerationY(forces2);
      xVel2 = getNewVelocity(xVel2, xAcc2);
      yVel2 = getNewVelocity(yVel2, yAcc2);
      xPos2 = getNewPosition(xPos2, xVel2);
      yPos2 = getNewPosition(yPos2, yVel2);

      let xVel3 = getNewVelocity(xVel, xAcc2 / 2);
      let yVel3 = getNewVelocity(yVel, yAcc2 / 2);
      let xPos3 = getNewPosition(xPos, xVel2 / 2);
      let yPos3 = getNewPosition(yPos, yVel2 / 2);
      const forces3 = getNewForces(xPos3, yPos3, xVel3, yVel3);
      const xAcc3 = getNewAccelerationX(forces3);
      const yAcc3 = getNewAccelerationY(forces3);
      xVel3 = getNewVelocity(xVel3, xAcc3);
      yVel3 = getNewVelocity(yVel3, yAcc3);
      xPos3 = getNewPosition(xPos3, xVel3);
      yPos3 = getNewPosition(yPos3, yVel3);

      let xVel4 = getNewVelocity(xVel, xAcc3);
      let yVel4 = getNewVelocity(yVel, yAcc3);
      let xPos4 = getNewPosition(xPos, xVel3);
      let yPos4 = getNewPosition(yPos, yVel3);
      const forces4 = getNewForces(xPos4, yPos4, xVel4, yVel4);
      const xAcc4 = getNewAccelerationX(forces4);
      const yAcc4 = getNewAccelerationY(forces4);
      xVel4 = getNewVelocity(xVel4, xAcc4);
      yVel4 = getNewVelocity(yVel4, yAcc4);
      xPos4 = getNewPosition(xPos4, xVel4);
      yPos4 = getNewPosition(yPos4, yVel4);

      xVel +=
        timestepSize * (xAcc1 / 6.0 + xAcc2 / 3.0 + xAcc3 / 3.0 + xAcc4 / 6.0);
      yVel +=
        timestepSize * (yAcc1 / 6.0 + yAcc2 / 3.0 + yAcc3 / 3.0 + yAcc4 / 6.0);
      xPos +=
        timestepSize * (xVel1 / 6.0 + xVel2 / 3.0 + xVel3 / 3.0 + xVel4 / 6.0);
      yPos +=
        timestepSize * (yVel1 / 6.0 + yVel2 / 3.0 + yVel3 / 3.0 + yVel4 / 6.0);
    }

    setXVelocity(xVel);
    setYVelocity(yVel);
    setXPosition(xPos);
    setYPosition(yPos);
    setUpdatedForces(getNewForces(xPos, yPos, xVel, yVel));
  };

  let weightStyle = {
    backgroundColor: color,
    borderStyle: "solid",
    borderColor: "black",
    position: "absolute" as "absolute",
    left: xPosition + "px",
    top: yPosition + "px",
    width: 2 * radius + "px",
    height: 2 * radius + "px",
    borderRadius: 50 + "%",
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
    touchAction: "none",
  };
  if (dragging) {
    weightStyle.borderColor = "lightblue";
  }

  const [clickPositionX, setClickPositionX] = useState(0);
  const [clickPositionY, setClickPositionY] = useState(0);
  const labelBackgroundColor = `rgba(255,255,255,0.5)`;

  // Update x start position
  useEffect(() => {
    setUpdatedStartPosX(startPosX);
    setXPosition(startPosX);
    setXPosDisplay(startPosX);
  }, [startPosX]);

  // Update y start position
  useEffect(() => {
    setUpdatedStartPosY(startPosY);
    setYPosition(startPosY);
    setYPosDisplay(startPosY);
  }, [startPosY]);

  render () {
    return (
    <div style={{ zIndex: -1000 }}>
      <div
        className="weightContainer"
        onPointerDown={(e) => {
          if (this.props.draggable) {
            e.preventDefault();
            this.props.setPaused(true);
            this.setState({dragging: true});
            setClickPositionX(e.clientX);
            setClickPositionY(e.clientY);
          }
        }}
        onPointerMove={(e) => {
          e.preventDefault();
          if (this.state.dragging) {
            let newY = this.state.yPosition + e.clientY - clickPositionY;
            if (newY > this.yMax - 2 * this.props.radius) {
              newY = this.yMax - 2 * this.props.radius;
            }

            let newX = this.state.xPosition + e.clientX - clickPositionX;
            if (newX > this.xMax - 2 * this.props.radius) {
              newX = this.xMax - 2 * this.props.radius;
            } else if (newX < 0) {
              newX = 0;
            }
            this.setState({xPosition: newX})
            this.setState({yPosition: newY})
            this.setState({updatedStartPosX: newX})
            this.setState({updatedStartPosY: newY})
            this.setState({displayYPosition: Math.round((yMax - 2 * radius - newY + 5) * 100) / 100})
            setClickPositionX(e.clientX);
            setClickPositionY(e.clientY);
            setDisplayValues();
          }
        }}
        onPointerUp={(e) => {
          if (dragging) {
            e.preventDefault();
            if (!pendulum) {
              resetEverything();
            }
            setDragging(false);
            let newY = this.state.yPosition + e.clientY - clickPositionY;
            if (newY > this.yMax - 2 * this.props.radius) {
              newY = this.yMax - 2 * this.props.radius;
            }

            let newX = xPosition + e.clientX - clickPositionX;
            if (newX > this.xMax - 2 * this.props.radius) {
              newX = this.xMax - 2 * this.props.radius;
            } else if (newX < 0) {
              newX = 0;
            }
            if (this.state.pendulum) {
              const x = this.xMax / 2 - newX - this.props.radius;
              const y = newY + this.props.radius + 5;
              let angle = (Math.atan(y / x) * 180) / Math.PI;
              if (angle < 0) {
                angle += 180;
              }
              let oppositeAngle = 90 - angle;
              if (oppositeAngle < 0) {
                oppositeAngle = 90 - (180 - angle);
              }

              const pendulumLength = Math.sqrt(x * x + y * y);
              this.props.setPendulumAngle(oppositeAngle);
              this.props.setPendulumLength(Math.sqrt(x * x + y * y));
              const mag = 9.81 * Math.cos((oppositeAngle * Math.PI) / 180);
              const forceOfTension: IForce = {
                description: "Tension",
                magnitude: mag,
                directionInDegrees: angle,
              };
              this.setState({kineticFriction: false})
              this.setState({xVelocity: startVelX ?? 0})
              this.setState({yVelocity: startVelY ?? 0})
              setDisplayValues();
              this.setState({updatedForces :[forceOfGravity, forceOfTension]});
            }
          }
        }}
      >
        <div className="weight" style={weightStyle}>
          <p className="weightLabel">{mass} kg</p>
        </div>
      </div>
      {this.state.pendulum && (
        <div
          className="rod"
          style={{
            pointerEvents: "none",
            position: "absolute",
            left: 0,
            top: 0,
            zIndex: -2,
          }}
        >
          <svg width={this.xMax + "px"} height={window.innerHeight + "px"}>
            <line
              x1={this.state.xPosition + this.props.radius}
              y1={this.state.yPosition + this.props.radius}
              x2={this.xMax / 2}
              y2={-5}
              stroke={"#deb887"}
              strokeWidth="10"
            />
          </svg>
          {!this.state.dragging && (
            <div>
              <p
                style={{
                  position: "absolute",
                  zIndex: 5,
                  left: this.state.xPosition + "px",
                  top: this.state.yPosition - 70 + "px",
                  backgroundColor: labelBackgroundColor,
                }}
              >
                {Math.round(this.props.pendulumLength)} m
              </p>
              <p
                style={{
                  position: "absolute",
                  zIndex: -1,
                  left: this.xMax / 2 + "px",
                  top: 30 + "px",
                  backgroundColor: labelBackgroundColor,
                }}
              >
                {Math.round(this.props.pendulumAngle * 100) / 100}°
              </p>
            </div>
          )}
        </div>
      )}
      {!this.state.dragging && this.props.showAcceleration && (
        <div>
          <div
            style={{
              pointerEvents: "none",
              position: "absolute",
              zIndex: -1,
              left: 0,
              top: 0,
            }}
          >
            <svg width={this.xMax + "px"} height={window.innerHeight + "px"}>
              <defs>
                <marker
                  id="accArrow"
                  markerWidth="10"
                  markerHeight="10"
                  refX="0"
                  refY="3"
                  orient="auto"
                  markerUnits="strokeWidth"
                >
                  <path d="M0,0 L0,6 L9,3 z" fill="green" />
                </marker>
              </defs>
              <line
                x1={this.state.xPosition + this.props.radius}
                y1={this.state.yPosition + this.props.radius}
                x2={this.state.xPosition + this.props.radius + getNewAccelerationX(this.state.updatedForces) * 5}
                y2={this.state.yPosition + this.props.radius + getNewAccelerationY(this.state.updatedForces) * 5}
                stroke={"green"}
                strokeWidth="5"
                markerEnd="url(#accArrow)"
              />
            </svg>
            <div
              style={{
                pointerEvents: "none",
                position: "absolute",
                left:
                  this.state.xPosition +
                  this.props.radius +
                  getNewAccelerationX(this.state.updatedForces) * 5 +
                  25 +
                  "px",
                top:
                  this.state.yPosition +
                  this.props.radius +
                  getNewAccelerationY(this.state.updatedForces) * 5 +
                  25 +
                  "px",
                zIndex: -1,
                lineHeight: 0.5,
              }}
            >
              <p>
                {Math.round(
                  100 *
                    Math.sqrt(
                      Math.pow(getNewAccelerationX(this.state.updatedForces) * 3, 2) +
                        Math.pow(getNewAccelerationY(this.state.updatedForces) * 3, 2)
                    )
                ) / 100}{" "}
                m/s<sup>2</sup>
              </p>
            </div>
          </div>
        </div>
      )}
      {!this.state.dragging && this.props.showVelocity && (
        <div>
          <div
            style={{
              pointerEvents: "none",
              position: "absolute",
              zIndex: -1,
              left: 0,
              top: 0,
            }}
          >
            <svg width={this.xMax + "px"} height={window.innerHeight + "px"}>
              <defs>
                <marker
                  id="velArrow"
                  markerWidth="10"
                  markerHeight="10"
                  refX="0"
                  refY="3"
                  orient="auto"
                  markerUnits="strokeWidth"
                >
                  <path d="M0,0 L0,6 L9,3 z" fill="blue" />
                </marker>
              </defs>
              <line
                x1={this.state.xPosition + this.props.radius}
                y1={this.state.yPosition + this.props.radius}
                x2={this.state.xPosition + this.props.radius + this.state.xVelocity * 3}
                y2={this.state.yPosition + this.props.radius + this.state.yVelocity * 3}
                stroke={"blue"}
                strokeWidth="5"
                markerEnd="url(#velArrow)"
              />
            </svg>
            <div
              style={{
                pointerEvents: "none",
                position: "absolute",
                left: this.state.xPosition + this.props.radius + this.state.xVelocity * 3 + 25 + "px",
                top: this.state.yPosition + this.props.radius + this.state.yVelocity * 3 + "px",
                zIndex: -1,
                lineHeight: 0.5,
              }}
            >
              <p>
                {Math.round(
                  100 * Math.sqrt(this.state.xVelocity**2 + this.state.yVelocity**2)
                ) / 100}{" "}
                m/s
              </p>
            </div>
          </div>
        </div>
      )}
      {!this.state.dragging &&
        this.props.showForces &&
        this.props.updatedForces.map((force, index) => {
          if (force.magnitude < this.epsilon) {
            return;
          }
          let arrowStartY: number = this.state.yPosition + this.props.radius;
          const arrowStartX: number = this.state.xPosition + this.props.radius;
          let arrowEndY: number =
            arrowStartY -
            Math.abs(force.magnitude) *
              20 *
              Math.sin((force.directionInDegrees * Math.PI) / 180);
          const arrowEndX: number =
            arrowStartX +
            Math.abs(force.magnitude) *
              20 *
              Math.cos((force.directionInDegrees * Math.PI) / 180);

          let color = "#0d0d0d";

          let labelTop = arrowEndY;
          let labelLeft = arrowEndX;
          if (force.directionInDegrees > 90 && force.directionInDegrees < 270) {
            labelLeft -= 120;
          } else {
            labelLeft += 30;
          }
          if (force.directionInDegrees >= 0 && force.directionInDegrees < 180) {
            labelTop += 40;
          } else {
            labelTop -= 40;
          }
          labelTop = Math.min(labelTop, this.yMax + 50);
          labelTop = Math.max(labelTop, this.yMin);
          labelLeft = Math.min(labelLeft, this.xMax - 60);
          labelLeft = Math.max(labelLeft, this.xMin);

          return (
            <div key={index}>
              <div
                style={{
                  pointerEvents: "none",
                  position: "absolute",
                  zIndex: -1,
                  left: xMin,
                  top: yMin,
                }}
              >
                <svg
                  width={xMax - xMin + "px"}
                  height={window.innerHeight + "px"}
                >
                  <defs>
                    <marker
                      id="forceArrow"
                      markerWidth="10"
                      markerHeight="10"
                      refX="0"
                      refY="3"
                      orient="auto"
                      markerUnits="strokeWidth"
                    >
                      <path d="M0,0 L0,6 L9,3 z" fill={color} />
                    </marker>
                  </defs>
                  <line
                    x1={arrowStartX}
                    y1={arrowStartY}
                    x2={arrowEndX}
                    y2={arrowEndY}
                    stroke={color}
                    strokeWidth="5"
                    markerEnd="url(#forceArrow)"
                  />
                </svg>
              </div>
              <div
                style={{
                  pointerEvents: "none",
                  position: "absolute",
                  left: labelLeft + "px",
                  top: labelTop + "px",
                  // zIndex: -1,
                  lineHeight: 0.5,
                  backgroundColor: labelBackgroundColor,
                }}
              >
                {force.description && <p>{force.description}</p>}
                {!force.description && <p>Force</p>}
                {showForceMagnitudes && (
                  <p>{Math.round(100 * force.magnitude) / 100} N</p>
                )}
              </div>
            </div>
          );
        })}
    </div>
    );
  }
};
