import "./PhysicsSimulationBox.scss";
import { FieldView, FieldViewProps } from './FieldView';
import React = require('react');
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { observer } from 'mobx-react';
import "./PhysicsSimulationBox.scss";
import Weight from "./PhysicsSimulationWeight";
import Wall  from "./PhysicsSimulationWall"
import Wedge from "./PhysicsSimulationWedge"

export interface IForce {
  description: string;
  magnitude: number;
  directionInDegrees: number;
}
export interface IWallProps {
  length: number;
  xPos: number;
  yPos: number;
  angleInDegrees: number;
}

interface PhysicsVectorTemplate {
  top: number;
  left: number;
  width: number;
  height: number;
  x1: number;
  y1: number;
  x2: number;
  y2: number;
  weightX: number;
  weightY: number;
}

@observer
export default class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {

  public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PhysicsSimulationBox, fieldKey); }

  // Constants
  gravityMagnitude = 9.81;
  forceOfGravity: IForce = {
    description: "Gravity",
    magnitude: this.gravityMagnitude,
    directionInDegrees: 270,
  };
  xMin = 0;
  yMin = 0;
  xMax = 300;
  yMax = 300;
  color = `rgba(0,0,0,0.5)`;
  radius = 0.1*this.yMax

  constructor(props: any) {
    super(props);
    this.dataDoc.accelerationXDisplay = 0;
    this.dataDoc.accelerationYDisplay = 0;
    this.dataDoc.adjustPendulumAngle = true;
    this.dataDoc.coefficientOfKineticFriction = 0;
    this.dataDoc.coefficientOfStaticFriction = 0;
    this.dataDoc.currentForceSketch = [];
    this.dataDoc.deleteMode = false;
    this.dataDoc.updateDisplay = false;
    this.dataDoc.elasticCollisions = false;
    this.dataDoc.forceSketches = [];
    this.dataDoc.pendulum = false;
    this.dataDoc.pendulumAngle = 0;
    this.dataDoc.pendulumLength = 300;
    this.dataDoc.positionXDisplay = 0;
    this.dataDoc.positionYDisplay = 0;
    this.dataDoc.showAcceleration = false;
    this.dataDoc.showForceMagnitudes = false;
    this.dataDoc.showForces = false;
    this.dataDoc.showVelocity = false;
    this.dataDoc.simulationPaused = true;
    this.dataDoc.simulationReset = false;
    this.dataDoc.simulationType = "Inclined Plane";
    this.dataDoc.sketching = false;
    this.dataDoc.startForces = [this.forceOfGravity];
    this.dataDoc.startPendulumAngle = 0;
    this.dataDoc.startPosX = 0;
    this.dataDoc.startPosY = 0;
    this.dataDoc.stepNumber = 0;
    this.dataDoc.timer = 0;
    this.dataDoc.updatedForces = [this.forceOfGravity];
    this.dataDoc.velocityXDisplay = 0;
    this.dataDoc.velocityYDisplay = 0;
    this.dataDoc.wallPositions = [];
    this.dataDoc.wedge = false;
    this.dataDoc.wedgeAngle = 26;
    this.dataDoc.wedgeHeight = Math.tan((26 * Math.PI) / 180) * this.xMax*0.6;
    this.dataDoc.wedgeWidth = this.xMax*0.6;
    this.dataDoc.weight = false;
  }

  // Add one weight to the simulation
  addWeight () {
    this.dataDoc.weight = true;
    this.dataDoc.wedge = false;
    this.dataDoc.pendulum = false;
    this.addWalls();
    this.dataDoc.simulationReset = !this.dataDoc.simulationReset;
  };

  // Set weight defaults
  setToWeightDefault () {
    this.dataDoc.startPosY = this.yMin+this.radius;
    this.dataDoc.startPosX = (this.xMax+this.xMin-this.radius)/2;
    this.dataDoc.updatedForces = [this.forceOfGravity];
    this.dataDoc.startForces = [this.forceOfGravity];
  }

  // Add a wedge with a One Weight to the simulation
  addWedge () {
    this.dataDoc.weight = true;
    this.dataDoc.wedge = true;
    this.dataDoc.pendulum = false;
    this.addWalls();
  };

  // Set wedge defaults
  setToWedgeDefault () {
    this.changeWedgeBasedOnNewAngle(26);
    this.updateForcesWithFriction(this.dataDoc.coefficientOfStaticFriction);
    this.dataDoc.startForces = [this.forceOfGravity];
  }

  // Add a simple pendulum to the simulation
  addPendulum = () => {
    this.dataDoc.weight = true;
    this.dataDoc.wedge = false;
    this.dataDoc.pendulum = true;
    this.removeWalls();
    let angle = this.dataDoc.pendulumAngle;
    let mag = 9.81 * Math.cos((angle * Math.PI) / 180);
    let forceOfTension: IForce = {
      description: "Tension",
      magnitude: mag,
      directionInDegrees: 90 - angle,
    };
    this.dataDoc.updatedForces = [this.forceOfGravity, forceOfTension];
    this.dataDoc.startForces = [this.forceOfGravity, forceOfTension];
  };

  // Set pendulum defaults
  setToPendulumDefault () {
    let length = this.xMax*0.7;
    let angle = 35;
    let x = length * Math.cos(((90 - angle) * Math.PI) / 180);
    let y = length * Math.sin(((90 - angle) * Math.PI) / 180);
    let xPos = this.xMax / 2 - x - this.radius;
    let yPos = y - this.radius;
    this.dataDoc.startPosX = xPos;
    this.dataDoc.startPosY = yPos;
    let mag = 9.81 * Math.cos((angle * Math.PI) / 180);
    this.dataDoc.pendulumAngle = angle;
    this.dataDoc.pendulumLength = length;
    this.dataDoc.adjustPendulumAngle = !this.dataDoc.adjustPendulumAngle;
  }

  // Update forces when coefficient of static friction changes in freeform mode
  updateForcesWithFriction (
    coefficient: number,
    width: number = this.dataDoc.wedgeWidth,
    height: number = this.dataDoc.wedgeHeight
  ) {
    let normalForce = {
      description: "Normal Force",
      magnitude: this.forceOfGravity.magnitude * Math.cos(Math.atan(height / width)),
      directionInDegrees:
        180 - 90 - (Math.atan(height / width) * 180) / Math.PI,
    };
    let frictionForce: IForce = {
      description: "Static Friction Force",
      magnitude:
        coefficient *
        this.forceOfGravity.magnitude *
        Math.cos(Math.atan(height / width)),
      directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI,
    };
    // reduce magnitude of friction force if necessary such that block cannot slide up plane
    let yForce = -this.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) +
          this.forceOfGravity.magnitude) /
        Math.sin((frictionForce.directionInDegrees * Math.PI) / 180);
    }
    if (coefficient != 0) {
      this.dataDoc.startForces = [this.forceOfGravity, normalForce, frictionForce];
      this.dataDoc.updatedForces = [this.forceOfGravity, normalForce, frictionForce];
    } else {
      this.dataDoc.startForces = [this.forceOfGravity, normalForce];
      this.dataDoc.updatedForces = [this.forceOfGravity, normalForce];
    }
  };

  // Change wedge height and width and weight position to match new wedge angle
  changeWedgeBasedOnNewAngle = (angle: number) => {
    let width = 0;
    let height = 0;
    if (angle < 50) {
      width = this.xMax*0.6;
      height = Math.tan((angle * Math.PI) / 180) * width;
      this.dataDoc.wedgeWidth = width;
      this.dataDoc.wedgeHeight = height;
    } else if (angle < 70) {
      width = this.xMax*0.3;
      height = Math.tan((angle * Math.PI) / 180) * width;
      this.dataDoc.wedgeWidth = width;
      this.dataDoc.wedgeHeight = height;
    } else {
      width = this.xMax*0.15;
      height = Math.tan((angle * Math.PI) / 180) * width;
      this.dataDoc.wedgeWidth =  width;
      this.dataDoc.wedgeHeight = height;
    }

    // update weight position based on updated wedge width/height
    let xPos = (this.xMax * 0.2)-this.radius;
    let yPos = width * Math.tan((angle * Math.PI) / 180) - this.radius;

    this.dataDoc.startPosX = xPos;
    this.dataDoc.startPosY = this.getDisplayYPos(yPos);
    this.updateForcesWithFriction(
      Number(this.dataDoc.coefficientOfStaticFriction),
      width,
      height
    );
  };

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

  // In review mode, edit force arrow sketch on mouse movement
  editForce = (element: PhysicsVectorTemplate) => {
    if (!this.dataDoc.sketching) {
      let sketches = this.dataDoc.forceSketches.filter((sketch: PhysicsVectorTemplate) => sketch != element);
      this.dataDoc.forceSketches = sketches;
      this.dataDoc.currentForceSketch = element;
      this.dataDoc.sketching = true;
    }
  };

  // In review mode, used to delete force arrow sketch on SHIFT+click
  deleteForce = (element: PhysicsVectorTemplate) => {
    if (!this.dataDoc.sketching) {
      let sketches = this.dataDoc.forceSketches.filter((sketch: PhysicsVectorTemplate) => sketch != element);
      this.dataDoc.forceSketches = sketches;
    }
  };

  // Remove floor and walls from simulation
  removeWalls = () => {
    this.dataDoc.wallPositions = []
  };

  // Add floor and walls to simulation
  addWalls = () => {
    if (this.dataDoc.wallPositions.length == 0) {
      let walls = [];
      walls.push({ length: 100, xPos: 0, yPos: 97, angleInDegrees: 0 });
      walls.push({ length: 100, xPos: 0, yPos: 0, angleInDegrees: 90 });
      walls.push({ length: 100, xPos: 97, yPos: 0, angleInDegrees: 90 });
      this.dataDoc.wallPositions = walls
    }
  };


  componentDidMount() {
    // Add weight
    if (this.dataDoc['simulationType'] == "Inclined Plane") {
      this.addWedge()
    } else if (this.dataDoc['simulationType'] == "Pendulum") {
      this.addPendulum()
    } else if (this.dataDoc['simulationType'] == "Free Weight") {
      this.addWeight()
    }

    // Add listener for SHIFT key, which determines if sketch force arrow will be edited or deleted on click
    document.addEventListener("keydown", (e) => {
      if (e.shiftKey) {
        this.dataDoc.deleteMode = true;
      }
    });
    document.addEventListener("keyup", (e) => {
      if (e.shiftKey) {
        this.dataDoc.deleteMode = false;
      }
    });

    this.dataDoc['simulationPaused'] = true;
  }

 render () { 
  return (
    <div>
      <div className="mechanicsSimulationContainer">
        <div className="mechanicsSimulationContentContainer">
          <div className="mechanicsSimulationButtonsAndElements">
            <div className="mechanicsSimulationElements">
              {this.dataDoc.weight && (
                <Weight
                  color={"red"}
                  dataDoc={this.dataDoc}
                  mass={1}
                  radius={this.radius}
                  timestepSize={0.002}
                  xMax={this.xMax}
                  yMax={this.yMax}
                  xMin={this.xMin}
                  yMin={this.yMin}
                />
              )}
              {this.dataDoc.wedge && (
                <Wedge
                  startWidth={this.dataDoc.wedgeWidth}
                  startHeight={this.dataDoc.wedgeHeight}
                  startLeft={this.xMax * 0.2}
                  xMax={this.xMax}
                  yMax={this.yMax}
                />
              )}
            </div>
            <div>
              {this.dataDoc.wallPositions.map((element: { length: number; xPos: number; yPos: number; angleInDegrees: number; }, index: React.Key | null | undefined) => {
                return (
                  <div key={index}>
                    <Wall
                      length={element.length}
                      xPos={element.xPos}
                      yPos={element.yPos}
                      angleInDegrees={element.angleInDegrees}
                    />
                  </div>
                );
              })}
            </div>
          </div>
        </div>
        <div className="mechanicsSimulationEquationContainer">
          <div className="mechanicsSimulationControls">
           <div>
              {this.dataDoc.simulationPaused && (
               <button onClick={() => {
                console.log('start sim')
                this.dataDoc.simulationPaused = false}
              } >START</button>
              )}
              {!this.dataDoc.simulationPaused && (
               <button onClick={() => {
                console.log('pause sim')
                this.dataDoc.simulationPaused = true}
              } >PAUSE</button>
              )}
              {this.dataDoc.simulationPaused && (
               <button onClick={() => {
                this.dataDoc.simulationReset = !this.dataDoc.simulationReset}
              } >RESET</button>
              )}
              {this.dataDoc.simulationPaused && ( <button onClick={() => {
                if (!this.dataDoc.pendulum && !this.dataDoc.wedge) {
                  this.addWedge()
                  this.setToWedgeDefault()
                  this.dataDoc['simulationType'] = "Inclined Plane"
                }
                else if (!this.dataDoc.pendulum && this.dataDoc.wedge) {
                  this.setToPendulumDefault()
                  this.addPendulum()
                  this.dataDoc['simulationType'] = "Pendulum"
                }
                else {
                  this.addWeight()
                  this.setToWeightDefault()
                  this.dataDoc['simulationType'] = "Free Weight"
                }
              }} >TYPE</button>)}
              </div>
          </div>
        </div>
      </div>
    </div>
    );
  }
}