Time complexity of each and every algorithm #include <opencv2/opencv.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <utility>
#include <map>
#include <iomanip>

using Point2D = cv::Point2d;
using Path = std::vector<Point2D>;
using Paths = std::vector<Path>;

// Class to handle CSV reading and writing
class CSVHandler {
public:
    static Paths readCSV(const std::string& csvPath, double& yMaxGlobal);
    static void writeCSV(const Paths& paths, const std::string& csvPath);
};

// Class to represent and process shapes
class Shape {
public:
    Path points;
    Path regularizedPoints;
    std::string shapeType;
    std::vector<std::pair<Point2D, Point2D>> symmetryLines;

    Shape(const Path& pts);

    void detectShape();
    void regularizeShape();
    void calculateSymmetryLines();

private:
    // Detection helper functions
    static double calculateAngle(const Point2D& p1, const Point2D& p2, const Point2D& p3);
    static bool isStraightLine(const Path& pts, double tolerance = 0.05);
    static bool isSquare(const std::vector<cv::Point>& contour, double tolerance = 0.05);
    static bool isStarShape(const std::vector<cv::Point>& approx);

    // Regularization helper functions
    static bool kasaCircleFitting(const Path& pts, double& xc, double& yc, double& radius);
    Path regularizeCircle() const;
    Path regularizeEllipse() const;
    Path regularizeRectangle() const;
    Path regularizeSquare() const;
    Path regularizeStar(int numPoints = 5) const;
    Path regularizeStraightLine() const;
};

// Class to handle plotting of shapes
class Plotter {
public:
    static void plotShapes(const Paths& originalPaths, const Paths& regularizedPaths,
                           const std::vector<std::string>& shapeTypes,
                           double xMin, double yMin, double xMax, double yMax,
                           double scale, int padding, double scaleFactor);
private:
    static void drawSymmetryLines(cv::Mat& img, const std::vector<std::pair<Point2D, Point2D>>& lines,
                                  const cv::Scalar& color = cv::Scalar(0, 0, 0));
};

// Implementation of CSVHandler methods
Paths CSVHandler::readCSV(const std::string& csvPath, double& yMaxGlobal) {
    Paths paths;
    std::map<int, std::map<int, Path>> pathsMap;

    std::ifstream file(csvPath);
    if (!file.is_open()) {
        std::cerr << "Failed to open " << csvPath << std::endl;
        return paths;
    }

    std::string line;
    int lineNum = 0;
    while (std::getline(file, line)) {
        lineNum++;
        if (line.empty()) {
            std::cerr << "Empty line at line number " << lineNum << ". Skipping.\n";
            continue;
        }

        std::stringstream ss(line);
        std::string item;
        std::vector<std::string> tokens;
        while (std::getline(ss, item, ',')) {
            tokens.emplace_back(item);
        }

        if (tokens.size() >= 4) {
            int pathId, subpathId;
            try {
                pathId = std::stoi(tokens[0]);
                subpathId = std::stoi(tokens[1]);
            } catch (const std::invalid_argument& e) {
                std::cerr << "Invalid Path/SubPath ID at line " << lineNum << ": \""
                          << tokens[0] << "\", \"" << tokens[1] << "\". Skipping line.\n";
                continue;
            }

            double x, y;
            try {
                x = std::stod(tokens[2]);
                y = std::stod(tokens[3]);
            } catch (const std::invalid_argument& e) {
                std::cerr << "Invalid coordinate data at line " << lineNum << ". Skipping point.\n";
                continue;
            }

            pathsMap[pathId][subpathId].emplace_back(Point2D(x, y));
            if (y > yMaxGlobal) yMaxGlobal = y;
        } else {
            std::cerr << "Insufficient data at line " << lineNum
                      << ". Expected at least 4 values, got " << tokens.size()
                      << ". Skipping.\n";
        }
    }

    double yMaxCurrent = yMaxGlobal;
    for (const auto& [pathId, subpaths] : pathsMap) {
        for (const auto& [subpathId, path] : subpaths) {
            if (!path.empty()) {
                Path invertedPath;
                for (const auto& pt : path) {
                    invertedPath.emplace_back(Point2D(pt.x, yMaxCurrent - pt.y));
                }
                paths.emplace_back(invertedPath);
                std::cout << "Loaded Path ID " << pathId << ", SubPath ID " << subpathId
                          << " with " << path.size() << " points.\n";
            }
        }
    }

    if (paths.empty()) {
        std::cerr << "No valid paths found in the CSV file.\n";
    }

    return paths;
}

void CSVHandler::writeCSV(const Paths& paths, const std::string& csvPath) {
    std::ofstream file(csvPath);
    if (!file.is_open()) {
        std::cerr << "Failed to open " << csvPath << " for writing." << std::endl;
        return;
    }

    file << std::fixed << std::setprecision(6);

    for (size_t i = 0; i < paths.size(); ++i) {
        const Path& path = paths[i];
        for (size_t j = 0; j < path.size(); ++j) {
            file << i << "," << j << "," << path[j].x << "," << path[j].y << "\n";
        }
    }
}

// Implementation of Shape methods
Shape::Shape(const Path& pts) : points(pts) {}

void Shape::detectShape() {
    if (isStraightLine(points)) {
        shapeType = "Straight Line";
    } else {
        std::vector<cv::Point2f> contourFloats;
        for (const auto& pt : points) {
            contourFloats.emplace_back(cv::Point2f(static_cast<float>(pt.x), static_cast<float>(pt.y)));
        }

        double perimeter = cv::arcLength(contourFloats, true);
        std::vector<cv::Point2f> approxCurve;
        double epsilon = 0.04 * perimeter;
        cv::approxPolyDP(contourFloats, approxCurve, static_cast<float>(epsilon), true);
        double area = cv::contourArea(contourFloats);

        if (area < 100.0) {
            shapeType = "Unknown";
        } else if (approxCurve.size() == 4) {
            std::vector<cv::Point> approxInt;
            for (const auto& pt : approxCurve) {
                approxInt.emplace_back(cv::Point(static_cast<int>(pt.x), static_cast<int>(pt.y)));
            }

            if (isSquare(approxInt, 0.05)) {
                shapeType = "Square";
            } else {
                shapeType = "Rectangle";
            }
        } else if (approxCurve.size() > 5) {
            std::vector<cv::Point> approxInt;
            for (const auto& pt : approxCurve) {
                approxInt.emplace_back(cv::Point(static_cast<int>(pt.x), static_cast<int>(pt.y)));
            }

            if (isStarShape(approxInt)) {
                shapeType = "Star";
            } else {
                double circularity = 4 * CV_PI * area / (perimeter * perimeter);

                if (circularity > 0.95) {
                    shapeType = "Circle";
                } else if (circularity >= 0.8 && circularity < 0.95) {
                    if (contourFloats.size() < 5) {
                        shapeType = "Unknown";
                    } else {
                        cv::RotatedRect ellipse;
                        try {
                            ellipse = cv::fitEllipse(contourFloats);
                            double majorAxis = std::max(ellipse.size.width, ellipse.size.height);
                            double minorAxis = std::min(ellipse.size.width, ellipse.size.height);
                            if ((majorAxis / minorAxis) > 1.05) {
                                shapeType = "Ellipse";
                            } else {
                                shapeType = "Circle";
                            }
                        } catch (const cv::Exception& e) {
                            std::cerr << "Ellipse fitting failed: " << e.what() << std::endl;
                            shapeType = "Unknown";
                        }
                    }
                } else {
                    shapeType = "Unknown";
                }
            }
        } else {
            shapeType = "Unknown";
        }
    }
}

void Shape::regularizeShape() {
    if (shapeType == "Straight Line") {
        regularizedPoints = regularizeStraightLine();
    } else if (shapeType == "Circle") {
        regularizedPoints = regularizeCircle();
    } else if (shapeType == "Ellipse") {
        regularizedPoints = regularizeEllipse();
    } else if (shapeType == "Rectangle") {
        regularizedPoints = regularizeRectangle();
    } else if (shapeType == "Square") {
        regularizedPoints = regularizeSquare();
    } else if (shapeType == "Star") {
        regularizedPoints = regularizeStar();
    } else {
        regularizedPoints = points;
    }
}

void Shape::calculateSymmetryLines() {
    if (shapeType == "Circle" || shapeType == "Square") {
        cv::Point2d centroid(0.0, 0.0);
        for (const auto& pt : regularizedPoints) {
            centroid += pt;
        }
        centroid *= (1.0 / regularizedPoints.size());

        if (shapeType == "Circle") {
            symmetryLines.emplace_back(std::make_pair(
                cv::Point2d(centroid.x, 0),
                cv::Point2d(centroid.x, centroid.y * 2)
            ));
            symmetryLines.emplace_back(std::make_pair(
                cv::Point2d(0, centroid.y),
                cv::Point2d(centroid.x * 2, centroid.y)
            ));
        } else if (shapeType == "Square") {
            symmetryLines.emplace_back(std::make_pair(
                cv::Point2d(centroid.x, 0),
                cv::Point2d(centroid.x, centroid.y * 2)
            ));
            symmetryLines.emplace_back(std::make_pair(
                cv::Point2d(0, centroid.y),
                cv::Point2d(centroid.x * 2, centroid.y)
            ));

            symmetryLines.emplace_back(std::make_pair(
                cv::Point2d(0, 0),
                cv::Point2d(centroid.x * 2, centroid.y * 2)
            ));
            symmetryLines.emplace_back(std::make_pair(
                cv::Point2d(centroid.x * 2, 0),
                cv::Point2d(0, centroid.y * 2)
            ));
        }
    } else if (shapeType == "Ellipse" || shapeType == "Rectangle") {
        cv::Point2d centroid(0.0, 0.0);
        for (const auto& pt : regularizedPoints) {
            centroid += pt;
        }
        centroid *= (1.0 / regularizedPoints.size());

        if (shapeType == "Ellipse") {
            cv::RotatedRect ellipse = cv::fitEllipse(cv::Mat(regularizedPoints).reshape(1));
            double angleDeg = ellipse.angle;
            double angleRad = angleDeg * CV_PI / 180.0;

            double dx = std::cos(angleRad);
            double dy = std::sin(angleRad);
            double majorAxisLength = ellipse.size.width;

            cv::Point2d majorStart = centroid + cv::Point2d(dx * majorAxisLength / 2.0, dy * majorAxisLength / 2.0);
            cv::Point2d majorEnd = centroid - cv::Point2d(dx * majorAxisLength / 2.0, dy * majorAxisLength / 2.0);
            symmetryLines.emplace_back(std::make_pair(majorStart, majorEnd));

            double angleRadMinor = angleRad + CV_PI / 2.0;
            double dxMinor = std::cos(angleRadMinor);
            double dyMinor = std::sin(angleRadMinor);
            double minorAxisLength = ellipse.size.height;

            cv::Point2d minorStart = centroid + cv::Point2d(dxMinor * minorAxisLength / 2.0, dyMinor * minorAxisLength / 2.0);
            cv::Point2d minorEnd = centroid - cv::Point2d(dxMinor * minorAxisLength / 2.0, dyMinor * minorAxisLength / 2.0);
            symmetryLines.emplace_back(std::make_pair(minorStart, minorEnd));
        } else if (shapeType == "Rectangle") {
            symmetryLines.emplace_back(std::make_pair(
                cv::Point2d(centroid.x, 0),
                cv::Point2d(centroid.x, centroid.y * 2)
            ));
            symmetryLines.emplace_back(std::make_pair(
                cv::Point2d(0, centroid.y),
                cv::Point2d(centroid.x * 2, centroid.y)
            ));
        }
    } else if (shapeType == "Star") {
        cv::Point2d centroid(0.0, 0.0);
        for (const auto& pt : regularizedPoints) {
            centroid += pt;
        }
        centroid *= (1.0 / regularizedPoints.size());

        int numPoints = (regularizedPoints.size() - 1) / 2;
        for (int i = 0; i < numPoints; ++i) {
            symmetryLines.emplace_back(std::make_pair(
                centroid,
                regularizedPoints[2 * i]
            ));
        }
    } else if (shapeType == "Straight Line") {
        symmetryLines.emplace_back(std::make_pair(
            regularizedPoints.front(),
            regularizedPoints.back()
        ));
    }
}

// Helper functions for detection
double Shape::calculateAngle(const Point2D& p1, const Point2D& p2, const Point2D& p3) {
    cv::Point2d a = p1 - p2;
    cv::Point2d b = p3 - p2;

    double dotProduct = a.dot(b);
    double crossProduct = a.x * b.y - a.y * b.x;
    double angle = std::atan2(crossProduct, dotProduct) * 180.0 / CV_PI;
    if (angle < 0) angle += 360.0;
    return angle;
}

bool Shape::isStraightLine(const Path& pts, double tolerance) {
    if (pts.size() < 3) return true;

    double totalLength = 0.0;
    for (size_t i = 1; i < pts.size(); ++i) {
        double dx = pts[i].x - pts[i - 1].x;
        double dy = pts[i].y - pts[i - 1].y;
        totalLength += std::sqrt(dx * dx + dy * dy);
    }
    double startToEnd = cv::norm(pts.back() - pts.front());
    return ((std::abs(totalLength - startToEnd)) / totalLength) < tolerance;
}

bool Shape::isSquare(const std::vector<cv::Point>& contour, double tolerance) {
    if (contour.size() != 5 && contour.size() != 4) return false; // Ensure closure

    cv::RotatedRect rect = cv::minAreaRect(contour);
    double width = rect.size.width;
    double height = rect.size.height;
    double aspectRatio = std::min(width, height) / std::max(width, height);

    double contourArea = cv::contourArea(contour);
    double rectArea = width * height;
    double areaRatio = contourArea / rectArea;

    bool isSquareShape = std::abs(1.0 - aspectRatio) < tolerance;
    bool isFilled = areaRatio > (1.0 - tolerance);

    return isSquareShape && isFilled;
}

bool Shape::isStarShape(const std::vector<cv::Point>& approx) {
    int numVertices = approx.size();
    if (numVertices > 8) {
        std::vector<double> angles;
        for (int i = 0; i < numVertices; ++i) {
            cv::Point2d p1 = cv::Point2d(approx[i].x, approx[i].y);
            cv::Point2d p2 = cv::Point2d(approx[(i + 1) % numVertices].x, approx[(i + 1) % numVertices].y);
            cv::Point2d p3 = cv::Point2d(approx[(i + 2) % numVertices].x, approx[(i + 2) % numVertices].y);
            double angle = calculateAngle(p1, p2, p3);
            angles.push_back(angle);
        }

        int sharpAngles = 0;
        for (double angle : angles) {
            if (angle < 60.0 || angle > 300.0) {
                sharpAngles++;
            }
        }
        if (sharpAngles >= 5) {
            return true;
        }
    }
    return false;
}

// Helper functions for regularization
bool Shape::kasaCircleFitting(const Path& pts, double& xc, double& yc, double& radius) {
    int n = pts.size();
    if (n < 3) return false;

    cv::Mat A(n, 3, CV_64F);
    cv::Mat B(n, 1, CV_64F);
    for (int i = 0; i < n; ++i) {
        A.at<double>(i, 0) = pts[i].x;
        A.at<double>(i, 1) = pts[i].y;
        A.at<double>(i, 2) = 1.0;
        B.at<double>(i, 0) = pts[i].x * pts[i].x + pts[i].y * pts[i].y;
    }

    cv::Mat C;
    if (!cv::solve(A, B, C, cv::DECOMP_SVD)) {
        std::cerr << "Circle fitting failed." << std::endl;
        return false;
    }
    xc = C.at<double>(0, 0) / 2.0;
    yc = C.at<double>(1, 0) / 2.0;
    radius = std::sqrt(C.at<double>(2, 0) + xc * xc + yc * yc);

    return true;
}

Path Shape::regularizeCircle() const {
    double xc, yc, radius;
    if (!kasaCircleFitting(points, xc, yc, radius)) {
        return points;
    }

    Path regularized;
    int numPoints = 100; // Higher number for smoother circle
    for (int i = 0; i < numPoints; ++i) {
        double theta = 2 * CV_PI * i / numPoints;
        double x = xc + radius * std::cos(theta);
        double y = yc + radius * std::sin(theta);
        regularized.emplace_back(cv::Point2d(x, y));
    }
    regularized.emplace_back(regularized.front());
    return regularized;
}

Path Shape::regularizeEllipse() const {
    if (points.size() < 5) {
        std::cerr << "Not enough points to fit an ellipse." << std::endl;
        return points;
    }

    cv::Mat pointsMat(points.size(), 1, CV_32FC2);
    for (size_t i = 0; i < points.size(); ++i) {
        pointsMat.at<cv::Point2f>(i, 0) = cv::Point2f(static_cast<float>(points[i].x), static_cast<float>(points[i].y));
    }

    cv::RotatedRect ellipse;
    try {
        ellipse = cv::fitEllipse(pointsMat);
    } catch (const cv::Exception& e) {
        std::cerr << "Ellipse fitting failed: " << e.what() << std::endl;
        return points;
    }

    Path regularized;
    int numPoints = 200; // Higher number for smoother ellipse
    for (int i = 0; i < numPoints; ++i) {
        double theta = 2 * CV_PI * i / numPoints;
        double x = ellipse.center.x + (ellipse.size.width / 2.0) * std::cos(theta);
        double y = ellipse.center.y + (ellipse.size.height / 2.0) * std::sin(theta);
        regularized.emplace_back(cv::Point2d(x, y));
    }
    regularized.emplace_back(regularized.front());
    return regularized;
}

Path Shape::regularizeRectangle() const {
    double xMin = points[0].x, yMin = points[0].y;
    double xMax = points[0].x, yMax = points[0].y;

    for (const auto& pt : points) {
        if (pt.x < xMin) xMin = pt.x;
        if (pt.x > xMax) xMax = pt.x;
        if (pt.y < yMin) yMin = pt.y;
        if (pt.y > yMax) yMax = pt.y;
    }

    Path regularized = {
        cv::Point2d(xMin, yMin),
        cv::Point2d(xMax, yMin),
        cv::Point2d(xMax, yMax),
        cv::Point2d(xMin, yMax),
        cv::Point2d(xMin, yMin)
    };
    return regularized;
}

Path Shape::regularizeSquare() const {
    double xMin = points[0].x, yMin = points[0].y;
    double xMax = points[0].x, yMax = points[0].y;

    for (const auto& pt : points) {
        if (pt.x < xMin) xMin = pt.x;
        if (pt.x > xMax) xMax = pt.x;
        if (pt.y < yMin) yMin = pt.y;
        if (pt.y > yMax) yMax = pt.y;
    }

    double sideLength = std::min(xMax - xMin, yMax - yMin);

    Path regularized = {
        cv::Point2d(xMin, yMin),
        cv::Point2d(xMin + sideLength, yMin),
        cv::Point2d(xMin + sideLength, yMin + sideLength),
        cv::Point2d(xMin, yMin + sideLength),
        cv::Point2d(xMin, yMin)
    };
    return regularized;
}

Path Shape::regularizeStar(int numPoints) const {
    cv::Point2d centroid(0.0, 0.0);
    for (const auto& pt : points) {
        centroid += pt;
    }
    centroid *= (1.0 / points.size());

    double outerRadius = 0.0;
    double innerRadius = 0.0;
    for (const auto& pt : points) {
        double dist = cv::norm(pt - centroid);
        if (dist > outerRadius) outerRadius = dist;
        if (dist < innerRadius || innerRadius == 0.0) innerRadius = dist;
    }
    // Set innerRadius proportionally, e.g., 50% of outerRadius
    innerRadius = outerRadius * 0.5;

    Path regularized;
    double angleStep = CV_PI / numPoints;
    for (int i = 0; i < 2 * numPoints; ++i) {
        double angle = i * angleStep;
        double r = (i % 2 == 0) ? outerRadius : innerRadius;
        double x = centroid.x + r * std::cos(angle);
        double y = centroid.y + r * std::sin(angle);
        regularized.emplace_back(cv::Point2d(x, y));
    }
    regularized.emplace_back(regularized.front()); // Close the star
    return regularized;
}

Path Shape::regularizeStraightLine() const {
    return { points.front(), points.back() };
}

// Implementation of Plotter methods
void Plotter::plotShapes(const Paths& originalPaths, const Paths& regularizedPaths,
                         const std::vector<std::string>& shapeTypes,
                         double xMin, double yMin, double xMax, double yMax,
                         double scale, int padding, double scaleFactor) {

    double width = xMax - xMin;
    double height = yMax - yMin;

    std::cout << "\nCoordinate Ranges:\n";
    std::cout << "X: [" << xMin << ", " << xMax << "]\n";
    std::cout << "Y: [" << yMin << ", " << yMax << "]\n";

    double scaleX = (1000.0 - 2.0 * padding) / width;
    double scaleY = (1000.0 - 2.0 * padding) / height;
    double finalScale = std::min(scaleX, scaleY);

    int finalWidth = static_cast<int>(std::ceil(width * finalScale)) + 2 * padding;
    int finalHeight = static_cast<int>(std::ceil(height * finalScale)) + 2 * padding;

    const int minSize = 500;
    if (finalWidth < minSize) finalWidth = minSize;
    if (finalHeight < minSize) finalHeight = minSize;

    std::cout << "Image Size: " << finalWidth << "x" << finalHeight << "\n";

    // Create separate images for original and regularized shapes
    cv::Mat originalImg(finalHeight * scaleFactor, finalWidth * scaleFactor, CV_8UC3, cv::Scalar(255, 255, 255));
    cv::Mat regularizedImg(finalHeight * scaleFactor, finalWidth * scaleFactor, CV_8UC3, cv::Scalar(255, 255, 255));

    // Define colors for different shapes
    std::vector<cv::Scalar> colors = { cv::Scalar(0,0,255), cv::Scalar(0,255,0),
                                      cv::Scalar(255,0,0), cv::Scalar(255,255,0),
                                      cv::Scalar(255,0,255), cv::Scalar(0,255,255),
                                      cv::Scalar(0,0,0) };

    std::cout << "\033[1mShapes Detected and Regularized:\033[0m\n";

    // Lambda function to transform points based on scaling and padding
    auto transformPoint = [&](const cv::Point2d& pt) -> cv::Point2d {
        double x = (pt.x - xMin) * finalScale * scaleFactor + padding * scaleFactor;
        double y = (pt.y - yMin) * finalScale * scaleFactor + padding * scaleFactor;
        return cv::Point2d(x, y);
    };

    for (size_t i = 0; i < originalPaths.size(); ++i) {
        const Path& origPath = originalPaths[i];
        const Path& regPath = regularizedPaths[i];
        const std::string& shapeType = shapeTypes[i];
        cv::Scalar color = colors[i % colors.size()];

        // Draw Original Shape
        std::vector<cv::Point> origContour;
        for (const auto& pt : origPath) {
            cv::Point2d transformedPt = transformPoint(pt);
            origContour.emplace_back(cv::Point(static_cast<int>(std::round(transformedPt.x)),
                                               static_cast<int>(std::round(transformedPt.y))));
        }
        if (origContour.size() > 1) {
            cv::polylines(originalImg, origContour, false, color, 2 * scaleFactor, cv::LINE_AA);
            for (const auto& pt : origContour) {
                cv::circle(originalImg, pt, 3 * scaleFactor, color, -1, cv::LINE_AA);
            }
        }

        // Draw Regularized Shape
        std::vector<cv::Point> regContour;
        for (const auto& pt : regPath) {
            cv::Point2d transformedPt = transformPoint(pt);
            regContour.emplace_back(cv::Point(static_cast<int>(std::round(transformedPt.x)),
                                              static_cast<int>(std::round(transformedPt.y))));
        }
        if (regContour.size() > 1) {
            cv::polylines(regularizedImg, regContour, false, color, 2 * scaleFactor, cv::LINE_AA);
            for (const auto& pt : regContour) {
                cv::circle(regularizedImg, pt, 3 * scaleFactor, color, -1, cv::LINE_AA);
            }
        }

        std::cout << "- " << (shapeType.empty() || shapeType == " " ? "Unknown" : shapeType) << "\n";

        if (shapeType == "Ellipse" || shapeType == "Rectangle" ||
            shapeType == "Circle" || shapeType == "Square" ||
            shapeType == "Star" || shapeType == "Straight Line") {

            Shape tempShape(regPath);
            tempShape.shapeType = shapeType;
            tempShape.calculateSymmetryLines();

            std::vector<std::pair<cv::Point2d, cv::Point2d>> transformedLines;

            for (auto& line : tempShape.symmetryLines) {
                cv::Point2d transformedStart = transformPoint(line.first);
                cv::Point2d transformedEnd = transformPoint(line.second);
                transformedLines.emplace_back(std::make_pair(transformedStart, transformedEnd));
            }
            // Draw symmetry lines on the regularized image
            drawSymmetryLines(regularizedImg, transformedLines, cv::Scalar(0, 0, 0));

            if (shapeType == "Ellipse" || shapeType == "Rectangle") {
                std::cout << "  - " << shapeType << " has " << (shapeType == "Ellipse" ? "2" : "2") << " line(s) of symmetry.\n";
            } else if (shapeType == "Circle" || shapeType == "Square") {
                if (shapeType == "Square") {
                    std::cout << "  - " << shapeType << " has 4 lines of symmetry.\n";
                } else {
                    std::cout << "  - " << shapeType << " has infinite lines of symmetry.\n";
                }
            } else if (shapeType == "Star") {
                std::cout << "  - " << shapeType << " has 5 lines of symmetry.\n";
            } else if (shapeType == "Straight Line") {
                std::cout << "  - " << shapeType << " has infinite lines of symmetry.\n";
            } else {
                std::cout << "  - No specific symmetry lines defined for "
                          << (shapeType.empty() || shapeType == " " ? "Unknown" : shapeType)
                          << ".\n";
            }

            std::cout << "  Regularized Shape Points:\n";
            for (const auto& pt : regPath) {
                std::cout << "(" << pt.x << ", " << pt.y << ") ";
            }
            std::cout << "\n";
        }
    }

    // Combine Original and Regularized Images Side by Side
    cv::Mat combinedImg;
    cv::hconcat(originalImg, regularizedImg, combinedImg);

    // Annotate the combined image for clarity
    int annotationHeight = 50 * scaleFactor; // Height for annotations
    cv::Mat annotationOriginal(annotationHeight, combinedImg.cols / 2, CV_8UC3, cv::Scalar(255, 255, 255));
    cv::Mat annotationRegularized(annotationHeight, combinedImg.cols / 2, CV_8UC3, cv::Scalar(255, 255, 255));

    // Add text labels
    cv::putText(annotationOriginal, "Original Shapes", cv::Point(50, 30),
                cv::FONT_HERSHEY_SIMPLEX, 1.0 * scaleFactor, cv::Scalar(0, 0, 0), 2 * scaleFactor);
    cv::putText(annotationRegularized, "Regularized Shapes", cv::Point(30, 30),
                cv::FONT_HERSHEY_SIMPLEX, 1.0 * scaleFactor, cv::Scalar(0, 0, 0), 2 * scaleFactor);

    // Combine annotations with the images
    cv::Mat topCombined;
    cv::hconcat(annotationOriginal, annotationRegularized, topCombined);
    cv::vconcat(topCombined, combinedImg, combinedImg);

    // Save the combined image
    bool success = cv::imwrite("combined_shapes.png", combinedImg);
    if (success) {
        std::cout << "Combined image saved successfully as 'combined_shapes.png'.\n";
    } else {
        std::cerr << "Failed to save the combined image.\n";
    }

    // Display the combined image
    cv::imshow("Original and Regularized Shapes", combinedImg);
    cv::waitKey(0);
}

void Plotter::drawSymmetryLines(cv::Mat& img, const std::vector<std::pair<Point2D, Point2D>>& lines,
                                const cv::Scalar& color) {
    for (const auto& line : lines) {
        cv::line(img,
                 cv::Point(static_cast<int>(std::round(line.first.x)),
                           static_cast<int>(std::round(line.first.y))),
                 cv::Point(static_cast<int>(std::round(line.second.x)),
                           static_cast<int>(std::round(line.second.y))),
                 color, 2, cv::LINE_AA);
    }
}

// Main Function
int main(int argc, char* argv[]) {
    std::string inputCSV = "/Users/kartik/Desktop/CLG/MINOR/regularisation/test_cases/isolated.csv";
    std::string outputCSV = "regularized_shapes.csv";

    if (argc >= 2) {
        inputCSV = argv[1];
    }
    if (argc >= 3) {
        outputCSV = argv[2];
    }

    double yMaxGlobal = std::numeric_limits<double>::lowest();
    Paths paths = CSVHandler::readCSV(inputCSV, yMaxGlobal);
    if (paths.empty()) {
        std::cerr << "No paths found in the CSV file." << std::endl;
        return -1;
    }

    double xMin = std::numeric_limits<double>::max();
    double yMin = std::numeric_limits<double>::max();
    double xMax = std::numeric_limits<double>::lowest();
    double yMax = std::numeric_limits<double>::lowest();

    for (const auto& path : paths) {
        for (const auto& pt : path) {
            if (pt.x < xMin) xMin = pt.x;
            if (pt.y < yMin) yMin = pt.y;
            if (pt.x > xMax) xMax = pt.x;
            if (pt.y > yMax) yMax = pt.y;
        }
    }

    Paths regularizedPaths;
    std::vector<std::string> detectedShapes;

    for (const auto& path : paths) {
        Shape shape(path);
        shape.detectShape();
        shape.regularizeShape();

        regularizedPaths.push_back(shape.regularizedPoints);
        detectedShapes.push_back(shape.shapeType);
    }

    double scaleX = (1000.0 - 2.0 * 50) / (xMax - xMin);
    double scaleY = (1000.0 - 2.0 * 50) / (yMax - yMin);
    double scale = std::min(scaleX, scaleY);

    int padding = 50;
    double scaleFactor = 4.0;

    Plotter::plotShapes(paths, regularizedPaths, detectedShapes, xMin, yMin, xMax, yMax, scale, padding, scaleFactor);

    CSVHandler::writeCSV(regularizedPaths, outputCSV);
    std::cout << "Regularized shapes have been written to " << outputCSV << std::endl;

    return 0;
}

//g++ -std=c++17 -o shapee refff.cpp `pkg-config --cflags --libs opencv4`