#pragma once
#include "real.h"
#include "tinynurbs/util/array2.h"
#include <QFile>
#include <QList>
#include <QRegularExpression>
#include <QString>
#include <array>
#include <glm/glm.hpp>
#include <iostream>
#include <qcontainerfwd.h>
#include <tinynurbs/tinynurbs.h>

using namespace std;
using namespace tinynurbs;

class Reader {
public:
  static std::array<tinynurbs::RationalSurface<real>, 2>
  readSurfaces(const string &surfacesPath) {
    tinynurbs::RationalSurface<real> s;
    tinynurbs::RationalSurface<real> f;

    ifstream fin;
    fin.open(surfacesPath);

    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<real> weights;
    vector<real> 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] = real(wsInV[j].toDouble());
      }
    }
    weights = {points1.cols(), points2.rows(), wArray};

    // knot
    std::vector<real> knot_u(2 * points1.cols(), 1.);
    for (int i = 0; i < knot_u.size() / 2; i++)
      knot_u[i] = 0;
    std::vector<real> 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);
    return {s, f};
  }

  static void printCtrPtsAsQuadruples(const Surface<real> &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;
  }

  static tinynurbs::RationalSurface<real> readSurface(const QString &path) {
    QFile file(path);
    file.open(QIODevice::ReadOnly | QIODevice::Text);
    QByteArray byteArray = file.readAll();
    auto infos = QString(byteArray).split("\n");

    auto degreeInfos = infos[0].split(QRegularExpression("( )+"));
    auto degreeU = degreeInfos[0].toInt(), degreeV = degreeInfos[1].toInt();
    auto dimInfos = infos[1].split(QRegularExpression("( )+"));
    auto dimU = dimInfos[0].toInt(), dimV = dimInfos[1].toInt();
    auto knotInfos1 = infos[2].split(QRegularExpression("( )+"));
    auto knotInfos2 = infos[3].split(QRegularExpression("( )+"));
    auto knotU = std::vector<float>(knotInfos1.size());
    auto knotV = std::vector<float>(knotInfos2.size());
    for (int i = 0; i < knotInfos1.size(); i++) {
      knotU[i] = knotInfos1[i].toFloat();
    }
    for (int i = 0; i < knotInfos2.size(); i++) {
      knotV[i] = knotInfos2[i].toFloat();
    }
    array2<glm::vec3> points(dimU, dimV);
    auto pts = infos[4].split(QRegularExpression("( )+"));
    for (int i = 0; i < dimU; i++) {
      for (int j = 0; j < dimV; j++) {
        auto pt_coords = pts[i * dimV + j].split(QRegularExpression(",( )*"));
        for (int k = 0; k < 3; k++) {
          points(i, j)[k] = pt_coords[k].toFloat();
        }
      }
    }

    tinynurbs::RationalSurface<real> s;
    s.degree_u = degreeU;
    s.degree_v = degreeV;
    s.knots_u = knotU;
    s.knots_v = knotV;
    s.control_points = points;
    s.weights = array2<real>(dimU, dimV, 1.0);

    file.close();

    return s;
  }

private:
  static 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++) {
        real num;
        if (coords[k].contains("/")) {
          auto nums = coords[k].split("/");
          num =
              real(nums[0].trimmed().toDouble() / nums[1].trimmed().toDouble());
        } else {

          num = real(coords[k].toDouble());
        }
        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;
  }
};