#ifndef LATTICE_H
#define LATTICE_H

#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>


using namespace std;

class Random;

struct Node {
    vector<int> neighbors; //include vneighbors and hneighbors
    vector<double> thresholds;
    // vector<int> broken;
    Node() {}
    //Node(vector<int> &n) {neighbors = n;}
    int degree() {return neighbors.size();}
    //bool connected() {return neighbors.size();}
};

class Lattice  {
    public:
        Random &ra;
        char type;
        uint64_t seed;
        uint32_t steps;
        uint32_t Nx;
        uint32_t Ny;
        uint32_t NxNy;
        uint32_t Nz;
        uint32_t N;
        uint32_t nremove = 0;
        double weik;
        uint32_t precr;
        uint32_t nbroken = 0;
        uint64_t edges = 0;
        uint64_t edges0 = 0;
        double pbroken_unit = 0;
        vector<Node> nodelist;
        vector<uint32_t> brokenlist_sorted_fr;
        vector<uint32_t> brokenlist_sorted_to;

        Lattice(Random &ra,Lattice &lb, Lattice &lt) : ra(ra) {
            steps = lt.steps;
            Nx = pow(2, steps);
            Ny = Nx;
            NxNy = Nx*Ny;
            Nz = lb.Nz + lt.Nz - 2;
            

            weik = lt.weik;
            precr = lt.precr;
            type = lt.type;
            nremove = lt.nremove;
            cout << "\n 3D composite+adhesion v2025.1.omp" << endl;
            N = Nx*Ny*Nz;
            cout <<"N="<<N<<"  ("<<Nx<<"x"<<Ny<<"x"<<Nz<<")" << endl;
            cout << "Weibull k=" << weik << endl;
            cout << "Precrack length=" << precr << endl;
            

            
            //new edgelist
            vector<uint32_t> fr;
            vector<uint32_t> to;
            vector<double> th;
            uint32_t eub = 2*lt.edges; //loose upper bound
            fr.reserve(eub);
            to.reserve(eub);
            th.reserve(eub);
            
            
            //Nodes in bottom slab are indexed 0:lb.N-NxNy
            //Nodes in bottom slab in lb.N-NxNy:lb.N are discarded
            //Nodes in top slab are indexed lb.N-2*NxNy to N
            //Nodes in top slab formerly 0:NxNy become lb.N-2*NxNy:lb.N-NxNy
            uint32_t bottomN = lb.N-NxNy;
            uint32_t topzero = lb.N-2*NxNy;
            
            for(uint32_t i=0; i<bottomN; ++i) {
                for(uint32_t m=0; m<lb.nodelist[i].neighbors.size(); ++m) {
                    auto j = lb.nodelist[i].neighbors[m];
                    auto t = lb.nodelist[i].thresholds[m];
                    if((i<j) and (j<bottomN)) {
                        fr.push_back(i);
                        to.push_back(j);
                        th.push_back(t);
                    }   
                }
            }

            for(uint32_t i=0; i<lt.N; ++i) {
                for(uint32_t m=0; m<lt.nodelist[i].neighbors.size(); ++m) {
                    auto j = lt.nodelist[i].neighbors[m];
                    auto t = lt.nodelist[i].thresholds[m];
                    if(i<j) {
                        fr.push_back(i+topzero);
                        to.push_back(j+topzero);
                        th.push_back(t);
                    }   
                }
            }

            edges0 = fr.size();
            edges = edges0;
            nodelist.reserve(N);
            allocate_nodes(N,nodelist);
            for(uint32_t e=0; e<edges0; ++e) {
                uint32_t i = fr[e];
                uint32_t j = to[e];
                double t = th[e];
                nodelist[i].neighbors.push_back(j);
                nodelist[j].neighbors.push_back(i);
                nodelist[i].thresholds.push_back(t);
                nodelist[j].thresholds.push_back(t);
            }
            
        }

        Lattice(Random &ra, int steps, char type, int nremove, double weik, uint32_t precr)
            : ra(ra), steps(steps), type(type), nremove(nremove), weik(weik), precr(precr) {
                //ra = Random(1);
                if(steps<2) steps = 2;
                if(steps>8) steps = 8;
                Nx = pow(2, steps);
                Ny = Nx;
                NxNy = Nx*Ny;
                Nz = steps+2;
                cout << "\n 3D adhesion v2025.1.omp" << endl;
                N = Nx*Ny*Nz;
                cout <<"N="<<N<<"  ("<<Nx<<"x"<<Ny<<"x"<<Nz<<")" << endl;
                cout << "Weibull k=" << weik << endl;
                cout << "Precrack length=" << precr << endl;
                nodelist.reserve(N);
                allocate_nodes(N,nodelist);
                
                if(type=='r') {
                    cout << "Reference system" << endl;
                    vector<uint32_t> ns_xyedges {0,0,48,288,1600,8320,41216,197120,918528};
                    add_sq_xylinks(nodelist, ns_xyedges[steps]);
                    add_sq_zlinks(nodelist);
                }
                else { //catches all hierarchical cases: shuffled (s), or deterministic (h or d or anything else)
                    add_hi_xylinks(nodelist);
                    add_sq_zlinks(nodelist);
                    if(type=='s') {
                        shuffle_nodelist();
                        cout << "Shuffled ";
                    }
                    else if(type=='n') {
                        shuffle_nodelist_nonhierarchical();
                        cout << "Shuffled non-";
                    }
                    else {
                        cout << "Deterministic ";
                    }
                    cout << "hierarchical system" << endl;
                }
                if(precr) {
                    generate_precrack_v1(precr); //at interface
                    //generate_precrack_v2(precr); //mid-height
                }
                cout << "Edges = " << edges <<
                    " (not including edges in the buses)" << endl;
                edges0 = edges;
                if(edges0) pbroken_unit = 1.0/static_cast<double>(edges0);
                //tilt_interactions(10);
                brokenlist_sorted_fr.reserve(2*NxNy);
                brokenlist_sorted_to.reserve(2*NxNy);
            }

        Lattice(Random& ra, uint32_t steps, const char* filename, uint32_t nb) : ra(ra), steps(steps), nbroken(nb)
        {
            if(steps<2) steps = 2;
            if(steps>8) steps = 8;
            Nx = pow(2, steps);
            Ny = Nx;
            NxNy = Nx*Ny;
            Nz = steps+2;
            cout << "\n 3D adhesion v2023.6.omp" << endl;
            N = Nx*Ny*Nz;
            cout <<"N="<<N<<"  ("<<Nx<<"x"<<Ny<<"x"<<Nz<<")" << endl;
            //cout << "Weibull k=" << weik << endl;
            cout << "RESTARTED at nbroken=" << nbroken << endl;
            nodelist.reserve(N);
            allocate_nodes(N,nodelist);

            string line;
            ifstream in(filename);
            in.precision(17);
            while(getline(in,line)) {
                double val[3]; //signed int to catch possible errors in data file (negative nodes)
                bool valid_triplet = true;
                stringstream iss(line);
                for(uint32_t col=0; col<3; ++col) {
                    if(!(iss>>val[col])) {
                        valid_triplet = false;
                        break; 
                    }
                }
                if(valid_triplet) {   
                    nodelist[(uint32_t)val[0]].neighbors.push_back((uint32_t)val[1]);
                    nodelist[(uint32_t)val[1]].neighbors.push_back((uint32_t)val[0]); 
                    nodelist[(uint32_t)val[0]].thresholds.push_back(val[2]);
                    nodelist[(uint32_t)val[1]].thresholds.push_back(val[2]); 
                    ++edges;
                }
                else {
                    cout << "WELP!" << endl;
                }
            }
            cout << "Edges = " << edges <<
                " (not including edges in the buses)" << endl;
            edges0 = edges;
            if(edges0) pbroken_unit = 1.0/static_cast<double>(edges0);
            brokenlist_sorted_fr.reserve(2*NxNy);
            brokenlist_sorted_to.reserve(2*NxNy);
            //cout << "done here" << endl;
        }


        void export_state(const char* filename) {
            ofstream f;
            f.precision(17);
            f.open(filename);
            for(uint32_t i=0; i<nodelist.size(); ++i) {
                for(uint32_t m=0; m<nodelist[i].neighbors.size(); ++m) {
                    uint32_t j = nodelist[i].neighbors[m];
                    if(i<j) {
                        double t = nodelist[i].thresholds[m];
                        f << i << " " << j << " " << t << "\n";
                    }
                }
            }
            f.close();
        }



        size_t size() {return nodelist.size();}
        double pbroken() {
            //return static_cast<double>(nbroken)*pbroken_unit;
            return static_cast<double>(nbroken);

        }
        uint64_t count_edges()
        {
            uint64_t counter = 0;
            for(auto& node: nodelist) counter += node.degree();
            return counter/2;
        }
        
        bool are_neighbors(uint32_t i, uint32_t j)
        {
            bool foundit = false;
            for(auto& m : nodelist[i].neighbors) {
                if(m==j) {
                    foundit = true;
                    break;
                }
            }
            return foundit;
        }
        

        void allocate_nodes(int N, vector<Node> &n) {
            for(uint32_t i=0; i<N; ++i) n.emplace_back();
        }
        



        //Build functions
        double wei(double r, double b) {
            double rb = 1.0/b;
            double rg = 1.0/tgamma(1.0+rb);
            double rl = pow(log(1.0/r), rb);
            return  rg*rl;
        }
        void create_edge(int i1, int i2, vector<Node> &n) {
            n[i1].neighbors.push_back(i2);
            n[i2].neighbors.push_back(i1);
            //double t = ra.doub(); //uniform
            double t = wei(ra.doub(), weik); //Weibull
            n[i1].thresholds.push_back(t);
            n[i2].thresholds.push_back(t);
            ++edges;
        }
        void remove_directed_link(int i1, int i2, vector<Node> &n) {
            int id;
            for(id=0; id<n[i1].neighbors.size(); ++id) {
                if(n[i1].neighbors[id]==i2) {
                    n[i1].neighbors.erase(n[i1].neighbors.begin()+id);
                    n[i1].thresholds.erase(n[i1].thresholds.begin()+id);
                    break;
                }
            }
        }
        void remove_edge(int i1, int i2, vector<Node> &n) {
            remove_directed_link(i1, i2, n);
            remove_directed_link(i2, i1, n);
            --edges;
        }
        void burn_edge(int i1, int i2, vector<Node> &n) {
            remove_edge(i1,i2,n);
            // nodelist[i1].broken.push_back(i2);
            // nodelist[i2].broken.push_back(i1);
            ++nbroken;
            brokenlist_sorted_fr.push_back(i1);
            brokenlist_sorted_to.push_back(i2);
        }
        void generate_precrack_v1(uint32_t precr) {
            cout << "Precrack at the interface" << endl;
            uint32_t nr = precr*Nx;
            for(uint32_t i=0; i<nr; ++i) {
                uint32_t j=i+NxNy;
                if(are_neighbors(i,j)) {
                    remove_edge(i,i+NxNy,nodelist);
                }
                else {
                    cout << "ERROR GENERATING PRECRACK" << endl;
                }
            }
        }
        void generate_precrack_v2(uint32_t precr) {
            cout << "Precrack at mid-height" << endl;
            uint32_t nr = precr*Nx;
            for(uint32_t ii=0; ii<nr; ++ii) {
                uint32_t i = ii + NxNy*(Nz/2);
                uint32_t j=i+NxNy;
                if(are_neighbors(i,j)) {
                    remove_edge(i,i+NxNy,nodelist);
                }
                else {
                    cout << "ERROR GENERATING PRECRACK" << endl;
                }
            }
        }
        
        void add_sq_zlinks(vector<Node> &n)
        {
            for(uint32_t i=0; i<N-NxNy; ++i) { //exclude bus
                uint32_t ne = i+NxNy;
                create_edge(i,ne,n);
            }
        }
        void add_sq_xylinks(vector<Node> &n, uint32_t nxyedges_target)
        {
            uint32_t nxyedges_full = 2*NxNy*(Nz-2); //full square lattice, number of xy horizontal edges
            vector<uint32_t> efr;
            vector<uint32_t> eto;
            efr.reserve(nxyedges_full);
            eto.reserve(nxyedges_full);
            for(size_t level_id=1; level_id<Nz-1; ++level_id) {
                int level_begin = level_id*NxNy;
                for(uint32_t row_id=0; row_id<Ny; ++row_id) {
                    for(uint32_t col_id=0; col_id<Nx; ++col_id) {
                        int fr = level_begin + row_id*Nx + col_id;
                        efr.push_back(fr);
                        eto.push_back(x_neighbor_pbc(fr));
                        efr.push_back(fr);
                        eto.push_back(y_neighbor_pbc(fr));
                    }
                }
            }
            uint32_t nremove = nxyedges_full - nxyedges_target;
            uint32_t ne = nxyedges_full;
            for(auto rr=0; rr<nremove; ++rr) {
                uint32_t rid = floor(ra.doub()*ne);
                efr[rid] = efr.back();
                eto[rid] = eto.back();
                --ne;
                efr.pop_back();
                eto.pop_back();
            }
            uint32_t count = 0;
            for(uint32_t e=0; e<ne; ++e) {
                create_edge(efr[e],eto[e],nodelist);
                ++count;
            }
            // cout << count << " xyedges" << endl;
        }
        void add_hi_xylinks(vector<Node> &n)
        {
            vector<int> hccs(Ny,0); //sizes of h-crosslinked clusters at each value of y
            size_t maxvalue = Nx; //spanning entire size in h direction
            size_t minvalue = 2; //relevant for steps>=1
            hccs.front() = 1; //bus (links not explicit because unbreakable)
            hccs.back() = 1; //bus (same as above)
            size_t curvalue = minvalue;
            for(size_t i=1; i<hccs.size()-1; ++i) { //avoids buses
                //does nothing if Ny=2 (bus-only system)
                hccs[i] = curvalue;
                curvalue = curvalue*2;
            }
            //for(auto& i : hccs) cout << i << endl;
            for(size_t level_id=0; level_id<Nz; ++level_id) {
                int csize = hccs[level_id];
                if(csize>1) { //excludes the buses 
                    int cnumx = Nx/csize;
                    int cnumy = Ny/csize;
                    

                    int level_begin = level_id*NxNy;
                    for(uint32_t row_id=0; row_id<Ny; ++row_id) {
                        for(int i=0; i<cnumx; ++i) {
                            for(int j=0; j<csize-1; ++j) {
                                int fr = level_begin + row_id*Nx + csize*i + j;
                                create_edge(fr,fr+1,nodelist);
                            }
                        }
                    }
                    for(uint32_t col_id=0; col_id<Ny; ++col_id) {
                        for(int i=0; i<cnumy; ++i) {
                            for(int j=0; j<csize-1; ++j) {
                                int fr = level_begin + col_id + (csize*i + j)*Nx;
                                create_edge(fr,fr+Nx,nodelist);
                            }
                        }
                    }
                    
                }
            }
            //Now the periodic boundary conditions:
            //uint32_t dd = NxNy * Nz / 2 - NxNy/2 ; //pbc in the middle (bulk system)
            uint32_t dd = N - 2*NxNy; //pbc at top non boundary layer (adhesion system)
            uint32_t xjump = Nx-1;
            uint32_t yjump = Nx*(Ny-1);
                for(uint32_t x=0; x<Nx; ++x) {
                    uint32_t i = x + dd;
                    uint32_t j = i + yjump;
                    create_edge(i,j,nodelist);
                }
                for(uint32_t y=0; y<Ny; ++y) {
                    uint32_t i = y*Nx + dd;
                    uint32_t j = i + xjump;
                    create_edge(i,j,nodelist);
                }
        }
        void tilt_interactions(double t) {
            cout << "Cohesion/adhesion " << t << endl;
            for(uint32_t i=NxNy; i<N; ++i) {
                uint32_t k = nodelist[i].neighbors.size(); 
                for(uint32_t m=0; m<k; ++m ) {
                    uint32_t j = nodelist[i].neighbors[m];
                    if(j>=NxNy) {
                        nodelist[i].thresholds[m] *= t;
                    }
                }
            }
        }
        ///////////////////////////////////////////////////////////
        //SHUFFLE FUNCTIONS////////////////////////////////////////
        ///////////////////////////////////////////////////////////
        uint32_t x_neighbor_pbc(uint32_t i) { //east neightbor
            return i - (i%Nx) + (i+1)%Nx;
        }
        uint32_t y_neighbor_pbc(uint32_t i) { //north neighbor
            return i - (i%NxNy) + (i+Nx)%NxNy;
        }
        uint32_t z_neighbor(uint32_t i) { //no pbc
            return i + NxNy;
        }
        vector<uint32_t> shuffled_sequence(uint32_t fr, uint32_t to){
            uint32_t n = to-fr;
            vector<uint32_t> seq(n);
            for(uint32_t i=0; i<n; ++i) seq[i] = fr+i;
            vector<uint32_t> sseq;
            sseq.reserve(n);
            while(seq.size()){
                uint32_t i = floor(seq.size()*ra.doub());
                sseq.push_back(seq[i]);
                seq[i] = seq.back();
                seq.pop_back();
            }
            return sseq; 
        }
        vector<uint32_t> shuffled_sequence_odd_only(uint32_t fr, uint32_t to){
            uint32_t n = to-fr;
            
            vector<uint32_t> seq;
            seq.reserve(n/2);
            for(uint32_t i=1; i<n; i+=2) {
                seq.push_back(fr+i); //only add the ones in odd positions
            }
            
            vector<uint32_t> sseq;
            sseq.reserve(n);
            for(uint32_t i=0; i<n; ++i) {
                uint32_t v = fr+i;
                if(i%2) { //if odd
                    uint32_t i = floor(seq.size()*ra.doub());
                    v = seq[i];
                    seq[i] = seq.back();
                    seq.pop_back();
                }
                sseq.push_back(v);
            }
            if(seq.size()) cout << "ERROR: shuffling" << endl;
            return sseq; 
        }
        uint32_t xc(uint32_t i){
            return (i%NxNy)%Nx;
        }
        uint32_t yc(uint32_t i){
            return (i%NxNy)/Nx;
        }
        uint32_t zc(uint32_t i){
            return i/NxNy;
        }
        void create_edge_w_threshold(vector<Node>& nn, uint32_t i, uint32_t j, double th){
            nn[i].neighbors.push_back(j);
            nn[i].thresholds.push_back(th);
            nn[j].neighbors.push_back(i);
            nn[j].thresholds.push_back(th);
        }

        void shuffle_nodelist() {
            // auto xdict = shuffled_sequence(0,Nx);
            // auto ydict = shuffled_sequence(0,Ny);
            auto xdict = shuffled_sequence_odd_only(0,Nx);
            auto ydict = shuffled_sequence_odd_only(0,Ny);
            auto tempdict = shuffled_sequence(1,Nz-1);
            vector<uint32_t> zdict; //must not shuffle first and last position
            zdict.reserve(Nz);
            zdict.push_back(0);
            for(auto a: tempdict) {
                zdict.push_back(a);
            }
            zdict.push_back(Nz-1);


            vector<Node> n;
            n.reserve(N);
            allocate_nodes(N,n);
            for(uint32_t i=0; i<N-NxNy; ++i){ 
                uint32_t ki = nodelist[i].neighbors.size();
                for(uint32_t m=0; m<ki; ++m){
                    uint32_t j = nodelist[i].neighbors[m];
                    if(i<j){
                        //cout << i << " " << j << " becomes ";
                        double th = nodelist[i].thresholds[m];    
                        uint32_t zi = zc(i);
                        uint32_t zj = zc(j);
                        if(zi<zj){ //zlink will not be shuffled
                            create_edge_w_threshold(n,i,j,th);
                            //cout << i << " " << j << endl;
                        } 
                        else {
                            uint32_t xi = xc(i);
                            uint32_t xj = xc(j);
                            uint32_t yi = yc(i);
                            uint32_t yj = yc(j);
                            
                            //uint32_t new_zi = zdict[zi]; //this one shuffles along z too
                            uint32_t new_zi = zi; //this one doesn't
                            uint32_t new_i;
                            uint32_t new_j;
                            if(yi==yj) { //link along x, its x and z will change
                                if(xj!=xi+1){ //captures x-pbc case
                                    swap(xi,xj);
                                }
                                uint32_t new_xi = xdict[xi];    
                                new_i = new_zi*NxNy + yi*Nx + new_xi;
                                new_j = x_neighbor_pbc(new_i);
                            }
                            else { //must be link along y, its y and z will change
                                if(xi!=xj) cout << "ERROR" << endl;
                                if(yj!=yi+1){ //captures y-pbc case
                                    swap(yi,yj);
                                }
                                uint32_t new_yi = ydict[yi];
                                new_i = new_zi*NxNy + new_yi*Nx + xi;
                                new_j = y_neighbor_pbc(new_i);
                            }
                            create_edge_w_threshold(n,new_i,new_j,th);
                            //cout << new_i << " " << new_j << endl;
                        }
                    }
                }   
            }
            //cout << "done shuffling" << endl;
            swap(n,nodelist);
        }
        void shuffle_nodelist_nonhierarchical() {
            //cout << "Non-hierarchical shuffling" << endl;
            //does NOT preserve threshold sequence
            edges = 0; //reset, because add_sq_links() and create_edge() increment "edges"
            vector<Node> n;
            n.reserve(N);
            allocate_nodes(N,n);
            add_sq_zlinks(n);
            for(uint32_t level_id=1; level_id<Nz-1; ++level_id) {
                uint32_t nxyedges_full = 2*NxNy; //full number of xy horizontal edges per layer
                vector<uint32_t> efr;
                vector<uint32_t> eto;
                efr.reserve(nxyedges_full);
                eto.reserve(nxyedges_full);
                uint32_t nxyedges_target = 0;
                for(uint32_t ii=0; ii<NxNy; ++ii) { 
                    //create full square lattice 
                    uint32_t i = level_id*NxNy + ii;
                    efr.push_back(i);
                    eto.push_back(x_neighbor_pbc(i));
                    efr.push_back(i);
                    eto.push_back(y_neighbor_pbc(i));
                    //count how many xy edges to keep
                    for(auto j: nodelist[i].neighbors) {
                        if( (i<j) and (j!=i+NxNy) ) {
                            ++nxyedges_target;
                        } 
                    }
                }
                if(nxyedges_target > nxyedges_full) {
                    cout << "ERROR counting xy edges" << endl;
                }
                uint32_t nremove = nxyedges_full - nxyedges_target;
                uint32_t ne = nxyedges_full;
                for(auto rr=0; rr<nremove; ++rr) {
                    uint32_t rid = floor(ra.doub()*ne);
                    efr[rid] = efr.back();
                    eto[rid] = eto.back();
                    --ne;
                    efr.pop_back();
                    eto.pop_back();
                }
                //uint32_t count = 0;
                for(uint32_t e=0; e<ne; ++e) {
                    create_edge(efr[e],eto[e],n);
                    //++count;
                }
                //cout << count << " xyedges" << endl;
            }
            //cout << "done shuffling" << endl;
            // for(uint32_t level_id=0; level_id<Nz; ++level_id) {
            //     uint32_t nxyedges_target = 0;
            //     for(uint32_t ii=0; ii<NxNy; ++ii) { 
            //         //create full square lattice 
            //         uint32_t i = level_id*NxNy + ii;
            //         for(auto j: n[i].neighbors) {
            //             if( (i<j) and (j!=i+NxNy) ) {
            //                 ++nxyedges_target;
            //             } 
            //         }
            //     }
            //     cout << "level=" << level_id << "  nxy=" << nxyedges_target << endl; 
            // }
            swap(n,nodelist);
        }
        void shuffle_nodelist_nonhierarchical_wrong() {
            //cout << "Non-hierarchical shuffling" << endl;
            //auto xdict = shuffled_sequence(0,Nx);
            //auto ydict = shuffled_sequence(0,Ny);
            auto tempdict = shuffled_sequence(1,Nz-1);
            vector<uint32_t> zdict; //must not shuffle first and last position
            zdict.reserve(Nz);
            zdict.push_back(0);
            for(auto a: tempdict) {
                zdict.push_back(a);
            }
            zdict.push_back(Nz-1);


            vector<Node> n;
            n.reserve(N);
            allocate_nodes(N,n);
            for(uint32_t z=0; z<Nz-1; ++z) {    
                auto xdict = shuffled_sequence(0,Nx);
                auto ydict = shuffled_sequence(0,Ny);

                for(uint32_t ii=0; ii<NxNy; ++ii){ 
                    uint32_t i = z*NxNy + ii;
                    if(z!=zc(i)) {
                        cout << "ERROR identifying layers" << endl;
                    }

                    uint32_t ki = nodelist[i].neighbors.size();
                    for(uint32_t m=0; m<ki; ++m){
                        uint32_t j = nodelist[i].neighbors[m];
                        if(i<j){
                            //cout << i << " " << j << " becomes ";
                            double th = nodelist[i].thresholds[m];    
                            uint32_t zi = zc(i);
                            uint32_t zj = zc(j);
                            if(zi<zj){ //zlink will not be shuffled
                                create_edge_w_threshold(n,i,j,th);
                                //cout << i << " " << j << endl;
                            } 
                            else {
                                uint32_t xi = xc(i);
                                uint32_t xj = xc(j);
                                uint32_t yi = yc(i);
                                uint32_t yj = yc(j);
                                
                                //uint32_t new_zi = zdict[zi]; //this one shuffles along z too
                                uint32_t new_zi = zi; //this one doesn't
                                uint32_t new_i;
                                uint32_t new_j;
                                if(yi==yj) { //link along x, its x and z will change
                                    if(xj!=xi+1){ //captures x-pbc case
                                        swap(xi,xj);
                                    }
                                    uint32_t new_xi = xdict[xi];    
                                    new_i = new_zi*NxNy + yi*Nx + new_xi;
                                    new_j = x_neighbor_pbc(new_i);
                                }
                                else { //must be link along y, its y and z will change
                                    if(xi!=xj) cout << "ERROR" << endl;
                                    if(yj!=yi+1){ //captures y-pbc case
                                        swap(yi,yj);
                                    }
                                    uint32_t new_yi = ydict[yi];
                                    new_i = new_zi*NxNy + new_yi*Nx + xi;
                                    new_j = y_neighbor_pbc(new_i);
                                }
                                create_edge_w_threshold(n,new_i,new_j,th);
                                //cout << new_i << " " << new_j << endl;
                            }
                        }
                    }   
                }
            }
            //cout << "done shuffling" << endl;
            swap(n,nodelist);
        }
        

        template<typename T>
        vector<T> sorted_edgelist() {
            vector<T> el0;
            el0.reserve(2*edges);
            for(T i=0; i<N; ++i) {
                sort(nodelist[i].neighbors.begin(),nodelist[i].neighbors.end());
                for(auto j:nodelist[i].neighbors) {
                    if(i<j) {
                        el0.push_back(i);
                        el0.push_back(j);
                    }
                }
            }
            return el0;
        }



        ///////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////

        //Output functions
       
        void export_adjlists(const char* filename)
        {
            ofstream f;
            f.open(filename);
            for(auto const& node : nodelist) {
                for(auto& m : node.neighbors) f << m << " ";
                f << "\n";
            }
            f.close();
        }
      
        void export_edgelist_nobus(const char* filename)
        {
            ofstream f;
            f.open(filename);
            for(int i=0; i<nodelist.size(); ++i) {
                for(auto& j: nodelist[i].neighbors) {
                    if(i<j) {f << i << " " << j << "\n";}
                }
            }
            f.close();
        }
        void export_brokenlist_sorted_nobus(const char* filename)
        {
            ofstream f;
            f.open(filename);
            for(int i=0; i<brokenlist_sorted_fr.size(); ++i) {
                f << brokenlist_sorted_fr[i] << " " << brokenlist_sorted_to[i] << "\n";
            }
            f.close();
        }
        

};

#endif
