// CONVENTIONS:
// - scalars are: A, B, C, ...
// - vectors are: V, W, U, ...
// - points are: P, Q, R
// - numbers are: a, b, c, ..., s, t,..., u, v, w,..., x, y, z, ...

export type Scalar = {
  val: Float64Array,
}

export type Vector = {
  x: Float64Array,
  y: Float64Array,
}

export type Config = {
  capacity: number,
  count: number,
}

export function Arithmetic({ capacity: CAPACITY, count: COUNT }: Config) {

  const Scalar = {
    allocate(): Scalar {
      return {
        val: new Float64Array(CAPACITY),
      };
    },

    // Out := 0
    zero(Out: Scalar) {
      for (let i = 0; i < COUNT; i++) {
        Out.val[i] = 0;
      }
    },

    // Out := A + B
    add(Out: Scalar, A: Scalar, B: Scalar) {
      for (let i = 0; i < COUNT; i++) {
        Out.val[i] = A.val[i] + B.val[i];
      }
    },

    // Out := A * B
    mul(Out: Scalar, A: Scalar, B: Scalar) {
      for (let i = 0; i < COUNT; i++) {
        Out.val[i] = A.val[i] * B.val[i];
      }
    },

    // Out := A + s*B
    addScaledByConstant(Out: Scalar, A: Scalar, s: number, B: Scalar) {
      for (let i = 0; i < COUNT; i++) {
        Out.val[i] = A.val[i] + s*B.val[i];
      }
    },

    // Out := A / B
    div(Out: Scalar, A: Scalar, B: Scalar) {
      for (let i = 0; i < COUNT; i++) {
        Out.val[i] = A.val[i] / B.val[i];
      }
    },

    // Out := 1/A
    reciprocal(Out: Scalar, A: Scalar) {
      for (let i = 0; i < COUNT; i++) {
        Out.val[i] = 1/A.val[i];
      }
    },

    // Out := A*s
    scaleByNumber(Out: Scalar, A: Scalar, s: number) {
      for (let i = 0; i < COUNT; i++) {
        Out.val[i] = A.val[i] * s;
      }
    },

    sum(A: Scalar): number { 
      let sum = 0;
      for (let i = 0; i < COUNT; i++) {
        sum += A.val[i];
      }
      return sum;
    },

    average(A: Scalar): number { 
      // TODO: Perhaps not the best method... might cause overflows...?
      return this.sum(A)/COUNT;
    },

    // Do I need a general `map`? Mapping one `number => number` over each scalar value in the scalar field
    // It will not be performant. Better to write the loop by hand in JS.
  };

  const Vector = {
    allocate(): Vector {
      return {
        x: new Float64Array(CAPACITY),
        y: new Float64Array(CAPACITY),
      };
    },

    // Out := 0
    zero(Out: Vector) {
      for (let i = 0; i < COUNT; i++) {
        Out.x[i] = 0;
        Out.y[i] = 0;
      }
    },

    // Out := V + W
    add(Out: Vector, V: Vector, W: Vector) {
      // in js a single loop wins (bounds checking, dealing with `i` etc). But in something like C or WASM two loops might actually be better (better patterns of access to memory)
      for (let i = 0; i < COUNT; i++) {
        Out.x[i] = V.x[i] + W.x[i];
        Out.y[i] = V.y[i] + W.y[i];
      }
    },

    // Out := V + s*W
    addScaledByConstant(Out: Vector, V: Vector, s: number, W: Vector) {
      for (let i = 0; i < COUNT; i++) {
        Out.x[i] = V.x[i] + s*W.x[i];
        Out.y[i] = V.y[i] + s*W.y[i];
      }
    },

    // Out := V*s
    scaleByConstant(Out: Vector, V: Vector, s: number) {
      for (let i = 0; i < COUNT; i++) {
        Out.x[i] = V.x[i] * s;
        Out.y[i] = V.y[i] * s;
      }
    },

    // Out := V*S
    scaleByScalar(Out: Vector, V: Vector, S: Scalar) {
      for (let i = 0; i < COUNT; i++) {
        Out.x[i] = V.x[i] * S.val[i];
        Out.y[i] = V.y[i] * S.val[i];
      }
    },

    // Out := V / S
    divideByScalar(Out: Vector, V: Vector, S: Scalar) {
      for (let i = 0; i < COUNT; i++) {
        Out.x[i] = V.x[i] / S.val[i];
        Out.y[i] = V.y[i] / S.val[i];
      }
    },

    // Out := <V, W>
    innerProduct(Out: Scalar, V: Vector, W: Vector) {
      for (let i = 0; i < COUNT; i++) {
        Out.val[i] = V.x[i]*W.x[i] + V.y[i]*W.y[i];
      }
    },

    // Out := |V|
    magnitude(Out: Scalar, V: Vector) {
      for (let i = 0; i < COUNT; i++) {
        const x = V.x[i];
        const y = V.y[i];
        Out.val[i] = Math.sqrt(x*x + y*y);
      }
    },

    // Out := V/|V|
    normalize(Out: Vector, V: Vector) {
      for (let i = 0; i < COUNT; i++) {
        const x = V.x[i];
        const y = V.y[i];
        const s = Math.sqrt(x*x + y*y);

        Out.x[i] = x / s;
        Out.y[i] = y / s;
      }
    },

    sum(V: Vector): { x: number, y: number } { 
      let x: number = 0;
      let y: number = 0;
      for (let i = 0; i < COUNT; i++) {
        x += V.x[i];
        y += V.y[i];
      }
      return { x, y };
    },

    average(V: Vector): { x: number, y: number } { 
      const v = this.sum(V);
      v.x = v.x/COUNT;
      v.y = v.y/COUNT;
      return v;
    },

    // TODO: Later we can consider other things like wedge product, or rotation by 90... or general rotation etc.
  }

  function setCount(newCount: number) {
    COUNT = newCount;
  }

  function getCount(): number {
    return COUNT;
  }

  // TODO: Later also implement resizing capabilities that change Capacity.
  return {
    Scalar,
    Vector,
    setCount,
    getCount,
  }
}

