#include <iostream>
#include "SingularityJudger.h"
#include "fstream"
#include "QString"
#include "QList"
#include "QRegularExpression"
#include "gauss_map.h"
#include "bvh.h"
#include "utils.h"
#include "unordered_set"
#include "unordered_map"

array2<glm::vec3> getPtsFromStr(QString srfData);
void printCtrPtsAsQuadruples(const RationalSurface<float> &s);
void printCtrPtsAsQuadruples(const Surface<double> &s);

void sampleTimeTest(const RationalSurface<float> &s_, int sampleLevel) {
    // 由于ParaSolid那边曲面参数为double类型,这里也要保证是double类型(控制变量)
    RationalSurface<double> s;
    vector<double> knots_u(s_.knots_u.size());
    vector<double> knots_v(s_.knots_v.size());
    array2<double> weights(s_.weights.rows(), s_.weights.cols());
    array2<glm::vec<3, double>> control_points(s_.control_points.rows(), s_.control_points.cols());

    for(int i = 0; i < s_.knots_u.size(); i++) knots_u[i] = (double)s_.knots_u[i];
    for(int i = 0; i < s_.knots_v.size(); i++) knots_v[i] = (double)s_.knots_v[i];
    for(int i = 0; i < s_.weights.rows(); i++) {
        for(int j = 0; j < s_.weights.cols(); j++) {
            weights(i, j) = (double)s_.weights(i, j);
        }
    }
    for(int i = 0; i < s_.control_points.rows(); i++) {
        for(int j = 0; j < s_.control_points.cols(); j++) {
            control_points(i, j).x = (double)s_.control_points(i, j).x;
            control_points(i, j).y = (double)s_.control_points(i, j).y;
            control_points(i, j).z = (double)s_.control_points(i, j).z;
        }
    }

    s.knots_u = knots_u;
    s.knots_v = knots_v;
    s.weights = weights;
    s.control_points = control_points;
    s.degree_u = s_.degree_u;
    s.degree_v = s_.degree_v;
    auto sampleCnt = int(pow(2, sampleLevel - 1) + 1);
    auto s_first_u = *(s.knots_u.begin());
    auto s_first_v = *(s.knots_v.begin());
    auto s_step_u = (*(s.knots_u.end() - 1) - s_first_u) / double (sampleCnt - 1);
    auto s_step_v = (*(s.knots_v.end() - 1) - s_first_v) / double (sampleCnt - 1);
    // 为了分别测试赋值和求梯度的时间,这里把它们分开写了

    auto startMomEval = std::chrono::steady_clock::now();
    for (int i = 0; i < sampleCnt; i++) {
        auto u = s_first_u + s_step_u * float(i);
        for (int j = 0; j < sampleCnt; j++) {
            auto v = s_first_v + s_step_v * float(j);
            auto eval = tinynurbs::surfacePoint(s, u, v);
//            printf("(%d, %d) --> (%g, %g, %g)\n", i, j, eval.x, eval.y, eval.z);
        }
    }
    auto endMomEval = std::chrono::steady_clock::now();
    printf("time cost of evaluation: %lf ms\n", std::chrono::duration<double, std::milli>(endMomEval - startMomEval).count());

    auto startMomDer = std::chrono::steady_clock::now();
    for (int i = 0; i < sampleCnt; i++) {
        auto u = s_first_u + s_step_u * float(i);
        for (int j = 0; j < sampleCnt; j++) {
            auto v = s_first_v + s_step_v * float(j);
            auto der = tinynurbs::surfaceDerivatives(s, 1, u, v);
//            if(der(0, 0) == res.evaluation[i][j]) cout<<"amazing"<<endl;
//            else cout<<"what??? ("<<res.evaluation[i][j].x<<" "<<res.evaluation[i][j].y<<" "<<res.evaluation[i][j].z<<") | ("<<der(0, 0).x<<" "<<der(0, 0).y<<" "<<der(0, 0).z<<")"<<endl;
//            res.tangent_u[i][j] = der(1, 0);
//            res.tangent_v[i][j] = der(0, 1);
        }
    }
    auto endMomDer = std::chrono::steady_clock::now();
    printf("time cost of derivatives: %lf ms\n", std::chrono::duration<double, std::milli>(endMomDer - startMomDer).count());

   auto startMomScdDer = std::chrono::steady_clock::now();
    for (int i = 0; i < sampleCnt; i++) {
        auto u = s_first_u + s_step_u * float(i);
        for (int j = 0; j < sampleCnt; j++) {
            auto v = s_first_v + s_step_v * float(j);
            auto der = tinynurbs::surfaceDerivatives(s, 2, u, v);
        }
    }
    auto endMomScdDer = std::chrono::steady_clock::now();
    printf("time cost of second derivatives: %lf ms\n", std::chrono::duration<double, std::milli>(endMomScdDer - startMomScdDer).count());

}

void sampleTimeTestNonRational(const RationalSurface<float> &s_, int sampleLevel) {
    // 由于ParaSolid那边曲面参数为double类型,这里也要保证是double类型(控制变量)
    Surface<double> s;
    vector<double> knots_u(s_.knots_u.size());
    vector<double> knots_v(s_.knots_v.size());
    array2<glm::vec<3, double>> control_points(s_.control_points.rows(), s_.control_points.cols());

    for(int i = 0; i < s_.knots_u.size(); i++) knots_u[i] = (double)s_.knots_u[i];
    for(int i = 0; i < s_.knots_v.size(); i++) knots_v[i] = (double)s_.knots_v[i];
    for(int i = 0; i < s_.control_points.rows(); i++) {
        for(int j = 0; j < s_.control_points.cols(); j++) {
            control_points(i, j).x = (double)s_.control_points(i, j).x;
            control_points(i, j).y = (double)s_.control_points(i, j).y;
            control_points(i, j).z = (double)s_.control_points(i, j).z;
        }
    }

    s.knots_u = knots_u;
    s.knots_v = knots_v;
    s.control_points = control_points;
    s.degree_u = s_.degree_u;
    s.degree_v = s_.degree_v;
    auto sampleCnt = int(pow(2, sampleLevel - 1) + 1);
    auto s_first_u = *(s.knots_u.begin());
    auto s_first_v = *(s.knots_v.begin());
    auto s_step_u = (*(s.knots_u.end() - 1) - s_first_u) / double (sampleCnt - 1);
    auto s_step_v = (*(s.knots_v.end() - 1) - s_first_v) / double (sampleCnt - 1);
    // 为了分别测试赋值和求梯度的时间,这里把它们分开写了

    printCtrPtsAsQuadruples(s);

    auto startMomEval = std::chrono::steady_clock::now();
    for (int i = 0; i < sampleCnt; i++) {
        auto u = s_first_u + s_step_u * float(i);
        for (int j = 0; j < sampleCnt; j++) {
            auto v = s_first_v + s_step_v * float(j);
            auto eval = tinynurbs::surfacePoint(s, u, v);
//            res.evaluation[i][j] = tinynurbs::surfacePoint(s, u, v);
        }
    }
    auto endMomEval = std::chrono::steady_clock::now();
    printf("time cost of evaluation: %lf ms\n", std::chrono::duration<double, std::milli>(endMomEval - startMomEval).count());

    auto startMomDer = std::chrono::steady_clock::now();
    for (int i = 0; i < sampleCnt; i++) {
        auto u = s_first_u + s_step_u * float(i);
        for (int j = 0; j < sampleCnt; j++) {
            auto v = s_first_v + s_step_v * float(j);
            auto der = tinynurbs::surfaceDerivatives(s, 1, u, v);
//            if(der(0, 0) == res.evaluation[i][j]) cout<<"amazing"<<endl;
//            else cout<<"what??? ("<<res.evaluation[i][j].x<<" "<<res.evaluation[i][j].y<<" "<<res.evaluation[i][j].z<<") | ("<<der(0, 0).x<<" "<<der(0, 0).y<<" "<<der(0, 0).z<<")"<<endl;
//            res.tangent_u[i][j] = der(1, 0);
//            res.tangent_v[i][j] = der(0, 1);
        }
    }
    auto endMomDer = std::chrono::steady_clock::now();
    printf("time cost of derivatives: %lf ms\n", std::chrono::duration<double, std::milli>(endMomDer - startMomDer).count());


    auto startMomScdDer = std::chrono::steady_clock::now();
    for (int i = 0; i < sampleCnt; i++) {
        auto u = s_first_u + s_step_u * float(i);
        for (int j = 0; j < sampleCnt; j++) {
            auto v = s_first_v + s_step_v * float(j);
            auto der = tinynurbs::surfaceDerivatives(s, 2, u, v);
        }
    }
    auto endMomScdDer = std::chrono::steady_clock::now();
    printf("time cost of second derivatives: %lf ms\n", std::chrono::duration<double, std::milli>(endMomScdDer - startMomScdDer).count());

}

int dirs[4][2] = {{-1, 0},
                  {0,  1},
                  {1,  0},
                  {0,  -1}};

void dfs(const map<pair<int, int>, set<pair<int, int>>>& pairMap,
//         unordered_map<pair<int, int>, char> &book,
         set<pair<int, int>>& book,
//         vector<vector<pair<pair<int, int>, pair<int, int>>>> &boxGroups,
         vector<vector<pair<int, int>>> &boxGroups,
         int x, int y, int iOfGroup) {
//    book[{x, y}] = 1;
//    book.insert(pair<pair<int, int>, char>(pair<int, int>(x, y), 1));
    book.insert({x,y});
    boxGroups[iOfGroup].emplace_back(pair<int, int>(x, y));
    for (auto dir: dirs) {
        auto nx = x + dir[0], ny = y + dir[1];
        if (book.find({nx, ny}) != book.end() || pairMap.find({nx, ny}) == pairMap.end())continue;
        dfs(pairMap, book, boxGroups, nx, ny, iOfGroup);
    }
}

int main() {
    RationalSurface<float> s;
    RationalSurface<float> f;

    int level = 9;
    printf("level: %d, sample cnt: %d * %d\n", level, int(pow(2, level - 1)), int(pow(2, level - 1)));

    ifstream fin;
    fin.open(R"(intersectTest\casea1\surfaces.txt)");

    string str;
    string tmp;
    while (fin.good()) {
        getline(fin, tmp);
        str += tmp + "\n";
    }
//    cout << str << endl;

    auto infos = QString(str.c_str()).split(";");
    auto wData = infos[0];
    auto srf1Data = infos[1];
    auto srf2Data = infos[2];

    auto points1 = getPtsFromStr(srf1Data);
    auto points2 = getPtsFromStr(srf2Data);

    wData = wData.remove(0, wData.indexOf("Matrix") + 6).trimmed();
    wData.remove(0, 3);
    wData.remove(wData.size() - 3, 3);
    auto wInfos = wData.split(QRegularExpression("\\]( )*,( )*\\["));

    array2<float> weights;
    vector<float> wArray(points1.cols() * points1.rows());
    for (int i = 0; i < points1.cols(); i++) {
        auto wsInV = wInfos[i].split(QRegularExpression("( )*,( )*"));
        for (int j = 0; j < points1.rows(); j++) {
            wArray[i * points1.rows() + j] = wsInV[j].toFloat();
        }
    }
    weights = {points1.cols(), points2.rows(), wArray};

    // knot
    std::vector<float> knot_u(2 * points1.cols(), 1.);
    for (int i = 0; i < knot_u.size() / 2; i++)knot_u[i] = 0;
    std::vector<float> knot_v(2 * points1.rows(), 1.);
    for (int i = 0; i < knot_v.size() / 2; i++)knot_v[i] = 0;

    s.degree_u = knot_u.size() / 2 - 1;
    s.degree_v = knot_v.size() / 2 - 1;
    s.knots_u = knot_u;
    s.knots_v = knot_v;
    s.control_points = points1;
    s.weights = weights;

    f.degree_u = knot_u.size() / 2 - 1;
    f.degree_v = knot_v.size() / 2 - 1;
    f.knots_u = knot_u;
    f.knots_v = knot_v;
    f.control_points = points2;
    f.weights = weights;

    fin.close();

    printCtrPtsAsQuadruples(f);

    // ====================== 测试 =======================
    vector<vector<glm::vec3>> s_evaluation;
    vector<vector<glm::vec3>> f_evaluation;
    // 曲面s和f的切向量。zd*-sznmj
    vector<vector<glm::vec3>> s_tangent_v;
    vector<vector<glm::vec3>> f_tangent_u;
    const vector<vector<glm::vec3>> f_tangent_v;
    // 曲面s和f的法向量
    const vector<vector<glm::vec3>> s_normal;
    const vector<vector<glm::vec3>> f_normal;

//    sampleTimeTestNonRational(s, level);
    sampleTimeTest(s, level);

    auto mesh1 = SrfMesh(s, level);
    auto mesh2 = SrfMesh(f, level);

    BVH bvh1(mesh1.evaluation);
    BVH bvh2(mesh2.evaluation);
    bvh1.build();
    bvh2.build();
    auto intersectBoxPairs = getOverlapLeafNodes(bvh1, bvh2); // [{{u1, v1}, {u2, v2}}]
    printf("box pairs size: %lld\n", intersectBoxPairs.size());

    /**
     * 测试对bvh结果用dfs
     */
//    double timeClassify = utils::get_time();
//    map<pair<int, int>, set<pair<int, int>>> pairMap;
//    for (const auto &boxPair: intersectBoxPairs) {
//        pairMap[boxPair.first].insert(boxPair.second);
//    }
////    vector<vector<pair<pair<int, int>, pair<int, int>>>> groups;
//    vector<vector<pair<int, int>>> groups;
////    unordered_map<pair<int, int>, char> book;
//    set<pair<int, int>>book;
//    int groupNum = 0;
//    for(auto boxPair: intersectBoxPairs) {
//        if(book.find(boxPair.first) != book.end())continue;
//        groups.emplace_back();
//        dfs(pairMap, book, groups, boxPair.first.first, boxPair.first.second, groupNum++);
//    }
//    timeClassify = utils::get_time() - timeClassify;
//    printf("time cost to group boxes from BVH: %lf\n", timeClassify);
    /**
     * end of test
     */

    double time_cost = utils::get_time();
    SingularityJudger singularityJudger(s, f, mesh1, mesh2);

    pair<int, int> cellIdxFullRange = {0, pow(2, level - 1) - 1};
    auto cellsWithCriticalPts = singularityJudger.judge(intersectBoxPairs, cellIdxFullRange, cellIdxFullRange, cellIdxFullRange, cellIdxFullRange);

    time_cost = utils::get_time() - time_cost;
    printf("time cost of singularityJudger: %lf\n", time_cost);

    // ================== 测试对整个曲面,gauss能排除多少(或保留多少)==================
//    GaussMap gaussMap1(mesh1.normal);
//    GaussMap gaussMap2(mesh2.normal);
//    gaussMap1.build();
//    gaussMap2.build();
//    auto pairs = getOverlapLeafNodes(gaussMap1, gaussMap2);
//    printf("Gauss Map: keep %lld samples in totally %lld boxes\n", pairs.size(),
//           mesh1.normal.size() * mesh1.normal[0].size() * mesh2.normal.size() * mesh2.normal[0].size());

    return 0;
}

array2<glm::vec3> getPtsFromStr(QString srfData) {
    srfData = srfData.remove(0, srfData.indexOf("Matrix") + 6).trimmed();
    // 去掉首尾的括号
    srfData.remove(0, 1);
    srfData.remove(srfData.size() - 1, 1);
    auto srfInfos = srfData.split("{");
    auto srfDimInfos = srfInfos[0].split(QRegularExpression(",( )*"));
    auto dimU = srfDimInfos[0].toInt();
    auto dimV = srfDimInfos[1].toInt();
    auto srfPtInfo = srfInfos[1].remove(srfInfos[1].indexOf("}"), 1);
    auto srfPtsInfos = srfPtInfo.split(QRegularExpression(",( )*\\("));
    std::vector<glm::vec3> points(dimU * dimV, glm::vec3());
    for (int i = 0; i < srfPtsInfos.size(); i++) {
        auto pt = srfPtsInfos[i].split("=")[1].trimmed();
        // 去掉首尾的方括号
        pt.remove(0, 1);
        pt.remove(pt.size() - 1, 1);
//        int iu = i / dimU;
//        int iv = i % dimU;
        auto coords = pt.split(QRegularExpression(",( )*"));
        for (int k = 0; k < 3; k++) {
            float num;
            if (coords[k].contains("/")) {
                auto nums = coords[k].split("/");
                num = nums[0].trimmed().toFloat() / nums[1].trimmed().toFloat();
            } else {
                num = coords[k].toFloat();
            }
            if (k == 0) points[i].x = num;
            else if (k == 1) points[i].y = num;
            else points[i].z = num;
        }
    }
    array2<glm::vec3> res = {(size_t) dimU, (size_t) dimV, points};
    return res;
}

void printCtrPtsAsQuadruples(const RationalSurface<float> &s) {
    cout<<endl;
    for(int i = 0; i < s.control_points.rows(); i++) {
        for(int j = 0; j < s.control_points.cols(); j++) {
            auto pt = s.control_points(i, j);
            auto w = s.weights(i, j);
            cout<<pt.x * w<<", "<<pt.y * w<<", "<<pt.z * w<<", "<<s.weights(i, j)<<", "<<endl;
        }
    }
    cout<<s.control_points.rows() * s.control_points.cols() * 4 << endl;
}

void printCtrPtsAsQuadruples(const Surface<double> &s) {
    cout<<endl;
    for(int i = 0; i < s.control_points.rows(); i++) {
        for(int j = 0; j < s.control_points.cols(); j++) {
            auto pt = s.control_points(i, j);
            cout<<pt.x<<", "<<pt.y<<", "<<pt.z<<", "<<endl;
        }
    }
    cout<<s.control_points.rows() * s.control_points.cols() * 4 << endl;
}