
import * as POLYTOPES from './polytopes.js';

// exploring more inscriptions of the 120-cell


function choice(a) {
    const r = Math.floor(Math.random() * a.length);
    return a[r];
}

export function nodes_links(links, nodeid) {
    return links.filter((l) => l.source === nodeid || l.target === nodeid);
}


export function linked(links, n1, n2) {
    const ls = nodes_links(nodes_links(links, n1), n2);
    if( ls.length ) {
        return ls[0]
    } else {
        return false;
    }
}


function fingerprint(ids) {
    const sids = [...ids];
    sids.sort();
    return sids.join(',');
}

export function dist(n1, n2) {
    return Math.sqrt((n1.x - n2.x) ** 2 + (n1.y - n2.y) ** 2 + (n1.z - n2.z) ** 2 + (n1.w - n2.w) ** 2);
}


export function make_120cell() {
    const nodes = POLYTOPES.make_120cell_vertices();
    const links = POLYTOPES.auto_detect_edges(nodes, 4);
    return {
    	nodes: nodes,
    	links: links
    }
}

function round_dist(raw) {
    return Math.floor(raw * 100000) / 100000;
}

export function distance_groups(cell120) {
    // get list of other nodes by distance
    // sort them and dump them out
    const dists = {};

    cell120.nodes.map((n) => {
        const draw = dist(cell120.nodes[0], n);
        const dtrunc = round_dist(draw);
        if( !(dtrunc in dists) ) {
            dists[dtrunc] = [];
        }
        dists[dtrunc].push(n);
    });
    return dists; 
}

function distance_group(cell120, n0, chord) {
    const nodes = []
    cell120.nodes.map((n) => {
        const d = round_dist(dist(n0, n));
        if( d == chord ) {
            nodes.push(n);
        }
    });
    // filter and return those whose chord is also the same
    const equidistant = [];
    for( const n1 of nodes ) {
        for( const n2 of nodes ) {
            if( n2.id > n1.id ) {
                if( round_dist(dist(n1, n2)) == chord ) {
                    equidistant.push([n1, n2]);
                }
            }
        }
    }
    return equidistant;
}


export function chord_survey() {
    const cell120 = POLYTOPES.cell120_inscribed();

    const dgroups = distance_groups(cell120);

    const dists = Object.keys(dgroups);

    dists.sort();

    for( const d of dists ) {
        const g0 = dgroups[d][0];
        dgroups[d].map((g) => {
            console.log(`${g0.id}-${g.id}: ${round_dist(dist(g0, g))}`);
        });
    }
}


function overlap(c1, c2) {
    for( const l in c1 ) {
        if( c1[l] === c2[l] ) {
            return true;
        }
    }
    return false;
}

function c5match(c1, c2) {
    for( const l in c1 ) {
        if( c1[l] != c2[l] ) {
            return false;
        }
    }
    return true;
}


export function gather_5cells(cell120) {
    const CHORD5 = round_dist(Math.sqrt(2.5));
    const bins = [];
    const all = [];
    cell120.nodes.filter((n) => n.label === 1).map((n) => {
        const cells = [ ];
        const g = distance_group(cell120, n, CHORD5);
        for( const pair of g ) {
            let seen = false;
            for( const cell of cells ) {
                const c = Object.values(cell);
                if( c.includes(pair[0].id) || c.includes(pair[1].id) ) {
                    if( !c.includes(pair[0].id) ) { 
                        cell[pair[0].label] = pair[0].id;
                    }
                    if( !c.includes(pair[1].id) ) {
                        cell[pair[1].label] = pair[1].id;
                    }
                    seen = true;
                    break;
                }
            }
            if( !seen ) {
                const cell = {};
                cell[1]= n.id;
                cell[pair[0].label] = pair[0].id;
                cell[pair[1].label] = pair[1].id;
                cells.push(cell);
           }
        }
        all.push(...cells);
   });
    return all;
}

function audit_5cells(cells) {
    // this verifies that for each label (a 600-cell set), each of its
    // vertices is in exactly 7 5-cells. It checks out.

    ['1','2','3','4','5'].map((l) => {
        const sets = {};
        for( const cell of cells ) {
            const lv = cell[l];
            if( !(lv in sets) ) {
                sets[lv] = [];
            }
            sets[lv].push(cell);
        }
        for( const lv in sets ) {
            const ok = ( sets[lv].length === 7 ) ? 'ok' : 'miss';
            console.log(`${l},${lv},${sets[lv].length},${ok}`);
        }
    });
}

function try_120_5_cells_fails(cell120, cells, l) {
    // iterate over every vertex in the 600-cell defined by label l,
    // get all 7 5-cells including that vertex, and add them if they are
    // disjoint with what we already have

    // this always runs out of disjoint nodes early

    const vertices = cell120.nodes.filter((n) => n.label === l);

    const cellset = [];
    for( const v of vertices ) {
        console.log(`Vertex ${v.id}`);
        const vcells = cells.filter((c) => c[l] === v.id);
        const overlap_any = (cs, c) => {
            for( const seen of cs ) {
                if( overlap(seen, c) ) {
                    console.log("overlap");
                    console.log(c);
                    return true;
                }
            }
            return false;
        }
        const disjoint = vcells.filter((c) => ! overlap_any(cellset, c));
        console.log(`Found ${disjoint.length} disjoint cells`);
        if( disjoint.length > 0 ) {
            cellset.push(choice(disjoint));
        }
    }
    console.log(`Found total of ${cellset.length} disjoint cells`);
    //console.log(cellset);
}

function overlap_any(cs, c) {
    for( const seen of cs ) {
        if( overlap(seen, c) ) {
            return true;
        }
    }
    return false;
}


function explore_disjoint(cell120, all5, l) {
    const a = all5[0];

    const overlaps = all5.filter((c) => overlap(c, a));

    console.log(a);

    console.log(overlaps.length);
    console.log(overlaps);
}

// select a five-cell from a starting vertex v
// find a neighbor of v vn on its 600 cell, find all of the 5-cells which include
// vn. Then see if we can find any from that set which are similiar neighbours to 
// the other four vertices in the first 5-cell 

// the idea is that the 600-cells are a guide to finding the right subset of 
// 5-cells 

function neighbours600(cell120, vid) {
    const v = cell120.nodes.filter((node) => node.id === vid)[0];
    const label = v.label; 
    const links = cell120.links.filter((l) => {
        return l.label === v.label && (l.source === v.id || l.target == v.id );
    });
    const nodes = links.map((l) => {
        if( l.source === v.id ) {
            return l.target;
        } else {
            return l.source;
        }
    });
    return nodes;
}

function cell120node(cell120, nid) {
    return cell120.nodes.filter((n) => n.id === nid)[0];
}

function node_dist(cell120, aid, bid) {
    const a = cell120node(cell120, aid);
    const b = cell120node(cell120, bid);
    return dist(a, b);
}

function print_row(v1, v2, p, v5) {
    console.log(`${v1.id},${v2.id},${p},${v5[1]},${v5[2]},${v5[3]},${v5[4]},${v5[5]}`);
}

// for a pair of vertices which are on the same inscribed 600 cell,
// this returns all 7 pairs of 5-cells which contain v1 and v2 and
// which are also evenly spaced (ie every pair of vertices on the
// same 600-cell  is one edge apart)


function find_adjoining_5cells(cell120, all5, v1, v2) {
    const DIST600 = round_dist(node_dist(cell120, v1.id, v2.id));
    const v15s = all5.filter((c5) => c5[v1.label] === v1.id);
    const v25s = all5.filter((c5) => c5[v2.label] === v2.id);
    let p = 0;
    const c5pairs = [];
    for( const v5a of v15s ) {
        for( const v5b of v25s ) {
            let match = true;
            const d = {};
            for( const label in v5a ) {
                d[label] = round_dist(node_dist(cell120, v5a[label], v5b[label]));
                if( d[label] != DIST600 ) {
                    match = false;
                }
            }
            if( match ) {
                c5pairs.push([ v5a, v5b ]);
            }
        }
    }
    return c5pairs;
}

function tetras(cell120, v) {
    // given a vertex v, find all of the 600-cell tetras it's on

    const n600s = neighbours600(cell120, v.id);
    // need to find all sets of three neighbours which are neighbours: there
    // should be 20 of these because they're faces of an icosahedron
    const tetras = new Set;
    for( const v2id of n600s ) {
        // find mutual neighbours of the first two
        const n2600s = neighbours600(cell120, v2id);
        const mutuals = n2600s.filter((nid) => {
            return nid != v2id && nid != v.id && n600s.includes(nid)
        });
        for( const nm of mutuals ) {
            const nnms = neighbours600(cell120, nm);
            const mutuals2 = nnms.filter((nid) => {
                return nid != nm && nid != v2id && nid != v.id && mutuals.includes(nid)
            });
            for( const m2 of mutuals2 ) {
                const t = [ v.id, v2id, nm, m2 ];
                t.sort((a, b) => a - b);
                const tstr = t.join(',');
                tetras.add(tstr);
            }
        }
    }
    const tarray = [];
    for( const t of tetras ) {
        const ta = t.split(',').map((v) => Number(v));
        tarray.push(ta);
    }
    return tarray;
}

function vertices(hedra) {
    const v = new Set;
    for ( const h of hedra) {
        for( const p of h ) {
            v.add(p);
        }
    }
    return Array.from(v);
}

function str5cell(c5) {
    return ["1","2","3","4","5"].map((l) => String(c5[l]).padStart(3, '0')).join('-');
}

function tetra_sets(cell120, all5, tetra) {
    // given a tetrahedron on a 600-cell, find the sets of adjacent 5-cells on
    // all of the pairs
    //  this is ass-backwards. Need to find tetras on the other 4 vertices of a 5-cell

    const vs = tetra.map((tid) => cell120node(cell120, tid));
    const pairs = [[0,1], [0,2], [0, 3], [1, 2], [1, 3], [2, 3]];
    for( const p of pairs ) {
        const v1 = vs[p[0]];
        const v2 = vs[p[1]];
        const c5pairs = find_adjoining_5cells(cell120, all5, v1, v2);
        console.log(v1.id, v2.id);
        console.log(c5pairs.map((p) => str5cell(p[0]) + "    " + str5cell(p[1])));
    }
}

function cell5_neighbourhoods(cell120, all5, c5) {
    const neighbours = {}

    for( const l in c5 ) {
        const v = cell120node(cell120, c5[l]);
        neighbours[l] = vertices(tetras(cell120, v));
    }

   // now take the set of all 5-cells and filter it to only those whose vertices
    // are in the neighour sets. On first inspection there are 13?

    const n5cells = all5.filter((c5) => {
        for( const l in c5 ) {
            if( ! neighbours[l].includes(c5[l]) ) {
                return false;
            }
        }
        return true;
    });
    return n5cells;
}


function cell5_tetras(cell120, all5, c5) {
    const nb = cell5_neighbourhoods(cell120, all5, c5);
    const v1 = cell120node(cell120, c5["1"]);
    const ts = tetras(cell120, v1);

    const c5s = [];
    for( const t of ts ) {
        const nt = nb.filter((n) => {
            for( const l in n ) {
                if( t.includes(n[l]) ) {
                    return true;
                }
            }
            return false
        });
        for( const nc5 of nt ) {
            const exact = c5s.filter((c) => c5match(c, nc5));
            if( exact.length === 0 ) {
                const o = c5s.filter((c) => overlap(c, nc5));
                if( o.length > 0 ) {
                    console.log("Overlap", c5, o);
                } else {
                    c5s.push(nc5);
                }
            }
        }
    }
    return c5s;
}


function coherent_5cells_r(cell120, all5, c5s, c50) {
    // Find next set of c5s, see if there are any we haven't seen, 
    // recurse into those ones
    const c5ns = cell5_tetras(cell120, all5, c50);
    const c5unseen = c5ns.filter((c5) => {
        const matched = c5s.filter((c5b) => c5match(c5b, c5));
        return matched.length === 0;
    });
    for( const c5u of c5unseen ) {
        c5s.push(c5u);
    }
    for( const c5u of c5unseen ) {
        coherent_5cells_r(cell120, all5, c5s, c5u);
    }
}



function coherent_5cells(cell120, all5d) {
    // pick a starting point, collect coherent 5_cells, continue till
    // there aren't any new ones

    const c5set = [];
    let c5 = all5[0];

    const c5s = [];
    coherent_5cells_r(cell120, all5, c5s, c5);
    return c5s;
}




function coherent_all() {


    const cell120 = POLYTOPES.cell120_inscribed();
    const all5 = gather_5cells(cell120);

    const c5s = coherent_5cells(cell120, all5);

    const celli = c5s.map((c5) => [ "1", "2", "3", "4", "5" ].map((l) => c5[l]));


    // check it because I don't believe it yet

    const vertex_check = {};

    for( const c5 of celli ) {
        for( const l in c5 ) {
            const v = c5[l];
            if( v in vertex_check ) {
                console.log(`Double count vertex ${v}`);
            }
            vertex_check[v] = 1;
        }
    }

    for( let i = 1; i < 601; i++ ) {
        if( !vertex_check[i] ) {
           console.log(`v ${i} missing`);
        }
    }


    const idict = {};
    for( let i = 1; i < 121; i++ ) {
        idict[i] = celli[i - 1];
    }

    console.log(JSON.stringify(idict, null, 2));
}



function coherent_one_set() {


    const cell120 = POLYTOPES.cell120_inscribed();
    const all5 = gather_5cells(cell120);

    const c5ns = cell5_tetras(cell120, all5, all5[0]);


    const celli = c5ns.map((c5) => [ "1", "2", "3", "4", "5" ].map((l) => c5[l]));


    const idict = {};

    for( let i = 0; i < celli.length; i++ ) {
       idict[i + 1] = celli[i];
    }

    console.log(JSON.stringify(idict, null, 2));
}


coherent_one_set();
