import { lattices } from '../geometry/lattices';
import { pointsF }  from '../geometry/types';

const ops = pointsF;


const norm = (v, dot) => dot(v, v);
const distance = (p, q, dot) => norm(ops.minus(p, q), dot);
const sortByDist = (a, key) => a.sort((x, y) => x.dist - y.dist);

const withDistances = (pos, points, dot) =>
  points.map(p => ({point: p, dist: distance(pos, p.pos, dot) }));

const withClosestDistances = (pos, points, dot, limit) =>
  sortByDist(withDistances(pos, points, dot)).slice(0, limit);

const withRelevantDistances = (p, points, dot) => (
  withClosestDistances(p.pos, points.filter(q => q.id > p.id), dot, p.degree)
    .map(q => ({ base: p, neighbor: q.point, dist: q.dist }))
);

const allDistances = (points, nrSeeds, dot) => {
  const tmp = points.slice(0, nrSeeds)
        .map(p => withRelevantDistances(p, points, dot));
  return sortByDist([].concat(...tmp));
};

const induceEdges = (points, nrSeeds, graph, dot = ops.times) => {
  allDistances(points, nrSeeds, dot).forEach(
    ({ base: p, neighbor: q, dist: d }) => {
      if (graph.degree(p) < p.degree || graph.degree(q) < q.degree)
        graph.addEdge(p, q);
    });
};


const graph = () => {
  const neighbors = [];

  const degree = i => {
    const nbrs = neighbors[i] || [];
    return nbrs.length + nbrs.filter(([j, _]) => j == i).length;
  };

  const contains = (adjs, j, s) => {
    for (const [k, v] of adjs) {
      if (k == j && v <= s && v >= s)
        return true;
    }
    return false;
  };

  const addNeighbor = (i, j, s) => {
    if (neighbors[i] == null)
      neighbors[i] = [];
    if (!contains(neighbors[i], j, s))
      neighbors[i].push([j, s]);
  };

  const addEdge = (i, j, s) => {
    if (i == j)
      addNeighbor(i, j, s < ops.times(0, s) ? ops.negative(s) : s);
    else {
      addNeighbor(i, j, s);
      addNeighbor(j, i, ops.negative(s));
    }
  };

  const edges = () => {
    const result = [];
    neighbors.forEach((ws, i) => {
      ws.forEach(([j, s]) => {
        if (i <= j)
          result.push([i, j, s]);
      });
    });
    return result;
  };

  return { addEdge, degree, edges };
};


const pgraph = () => {
  const G = graph();

  const addEdge = (p, q) => {
    G.addEdge(p.originalId, q.originalId, ops.minus(q.shift, p.shift));
  };

  return {
    addEdge     : addEdge,
    addPlainEdge: ([i, j, s]) => G.addEdge(i, j, s),
    degree      : p  => G.degree(p.originalId),
    edges       : G.edges
  };
};


const flatMap   = (fn, xs) => xs.reduce((t, x) => t.concat(fn(x)), []);
const cartesian = (xs, ys) => flatMap(x => ys.map(y => [x, y]), xs);


const fromPointCloud = (rawPoints, explicitEdges, dot) => {
  const eps = Math.pow(2, -40);
  const { dirichletVectors, shiftIntoDirichletDomain } =
        lattices(ops, eps, dot);

  const basis  = ops.identityMatrix(ops.dimension(rawPoints[0].pos));
  const dvs    = dirichletVectors(basis);
  const dvs2   = ops.times(2, dvs);
  const origin = ops.times(0, dvs[0]);

  const points = cartesian([origin].concat(dvs), rawPoints).map(
    ([shift, { id, pos, degree }], i) => {
      const p = ops.plus(pos, shift);
      const s = shiftIntoDirichletDomain(ops.vector(p), dvs2);
      return {
        id: i,
        pos: ops.plus(p, s),
        degree,
        shift: ops.plus(shift, s),
        originalId: id,
        originalPosition: pos
      };
    });

  const G = pgraph();
  explicitEdges.forEach(G.addPlainEdge);
  induceEdges(points, rawPoints.length, G, dot);
  return G.edges();
};


export default fromPointCloud;

@@ -281,24 +281,32 @@
 %0A%0Aconst with
+Relevant
 Distances = 
@@ -299,34 +299,32 @@
 ntDistances = (p
-os
 , points, dot) =
@@ -324,16 +324,18 @@
  dot) =%3E
+ (
 %0A  point
@@ -339,28 +339,76 @@
 ints
+%0A    .filter(q =%3E q.id %3E p.id)%0A    
 .map(
-p
+q
  =%3E (%7B
-point: p
+ base: p, neighbor: q
 , di
@@ -420,22 +420,24 @@
 istance(
+p.
 pos, 
-p
+q
 .pos, do
@@ -446,323 +446,68 @@
  %7D))
-;%0A%0Aconst withClosestDistances = (pos, points, dot, limit) =%3E%0A  sortByDist(withDistances(pos, points, dot)).slice(0, limit);%0A%0Aconst withRelevantDistances = (p, points, dot) =%3E (%0A  withClosestDistances(p.pos, points.filter(q =%3E q.id %3E p.id), dot, p.degree)%0A    .map(q =%3E (%7B base: p, neighbor: q.point, dist: q.dist %7D)
+%0A    .sort((x, y) =%3E x.dist - y.dist)%0A    .slice(0, p.degree
 )%0A);

combine some helper functions