/******************************************************************************!
 * \file stl2ngc.cpp
 * \author Sebastien Beaugrand
 * \sa http://beaugrand.chez.com/
 * \copyright GNU GNU GPLv3
 * \note Modified from :
 *       https://github.com/koppi/stl2ngc.git
 ******************************************************************************/
/*
 *  Copyright 2016-2023 Jakob Flierl (jakob.flierl "at" gmail.com)
 *
 *  stl2ngc is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  stl2ngc is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with stl2ngc.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <string>
#include <vector>
#include <iomanip>
#include <argp.h>

#include <opencamlib/stlsurf.hpp>
#include <opencamlib/stlreader.hpp>
#include <opencamlib/cylcutter.hpp>
#include <opencamlib/conecutter.hpp>
#include <opencamlib/adaptivepathdropcutter.hpp>

#ifdef _OPENMP
# include <omp.h>
#endif

using namespace std;
using namespace ocl;

/******************************************************************************!
 * argp
 ******************************************************************************/
const char* argp_program_version =
    "stl2ngc 1.0.0";
const char* argp_program_bug_address =
    "<sbeaugrand@toto.fr>";
static char doc[] =
    "stl2ngc -- "
    "convert a STL file to LinuxCNC compatible G-code";
static struct argp_option options[] = {
    { "diameter", 'd', "F", 0, "[default: 2 (mm)].", 0 },
    { "length", 'l', "F", 0, "[default: 6 (mm)].", 0 },
    { "angle", 'a', "F", 0, "ConeCutter angle [default: 0 (CylCutter)].", 0 },
    { "zigzag", 'z', "x|y", 0, "[default: x].", 0 },
    {}
};
struct arguments
{
    double diameter = 2;
    double length = 6;
    double angle = 0;
    bool zigzag_x = true;
};
static error_t
parse_opt(int key, char* arg, struct argp_state* state)
{
    struct arguments* arguments = static_cast<struct arguments*>(state->input);
    switch (key) {
    case 'd':
        arguments->diameter = ::atof(arg);
        break;
    case 'l':
        arguments->length = ::atof(arg);
        break;
    case 'a':
        arguments->angle = ::atof(arg);
        break;
    case 'z':
        if (*arg == 'y') {
            arguments->zigzag_x = false;
        }
        break;
    default:
        return ARGP_ERR_UNKNOWN;
    }
    return 0;
}
static struct argp argp = { options, parse_opt, 0, doc, 0, 0, 0 };

/******************************************************************************!
 * \fn wstr
 ******************************************************************************/
wstring
wstr(const char* str)
{
    wstring wstr(str, str + ::strlen(str));
    return wstr;
}

/******************************************************************************!
 * \class APDC
 ******************************************************************************/
class APDC : public AdaptivePathDropCutter
{
public:
    APDC() : AdaptivePathDropCutter() {}
    virtual ~APDC() {}
    vector<CLPoint> getPoints() {
        return clpoints;
    }
};

/******************************************************************************!
 * \class GCodeWriter
 ******************************************************************************/
class GCodeWriter
{
public:
    GCodeWriter() {};
    virtual ~GCodeWriter() {};
    void g1(const double x, const double y, const double z) {
        cout << "G1 X" << x << " Y" << y << " Z" << z << endl;
    }
    void g0(const double x,
            const double y,
            const double z) {
        cout << "G0 X" << x << " Y" << y << " Z" << z << endl;
    }
};

/******************************************************************************!
 * \fn zigzag_x
 ******************************************************************************/
Path
zigzag_x(double minx, double /*dx*/, double maxx,
         double miny, double dy, double maxy, double z)
{
    Path p;

    int rev = 0;
    for (double i = miny; i < maxy; i += dy) {
        if (rev == 0) {
            p.append(Line(Point(minx, i, z), Point(maxx, i, z)));
            rev = 1;
        } else {
            p.append(Line(Point(maxx, i, z), Point(minx, i, z)));
            rev = 0;
        }
    }

    return p;
}

/******************************************************************************!
 * \fn zigzag_y
 ******************************************************************************/
Path
zigzag_y(double minx, double dx, double maxx,
         double miny, double /*dy*/, double maxy, double z)
{
    Path p;

    int rev = 0;
    for (double i = minx; i < maxx; i += dx) {
        if (rev == 0) {
            p.append(Line(Point(i, miny, z), Point(i, maxy, z)));
            rev = 1;
        } else {
            p.append(Line(Point(i, maxy, z), Point(i, miny, z)));
            rev = 0;
        }
    }

    return p;
}

/******************************************************************************!
 * \fn isNearlyEqual
 ******************************************************************************/
bool
isNearlyEqual(double a, double b)
{
    int factor = 0.00001;

    double min_a = a -
        (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
    double max_a = a +
        (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;

    return min_a <= b && max_a >= b;
}

/******************************************************************************!
 * \fn main
 ******************************************************************************/
int
main(int argc, char* argv[])
{
    struct arguments arguments = {};
    ::argp_parse(&argp, argc, argv, 0, 0, &arguments);

    double zsafe = 5;
    double zstep = 3;

    cerr << "stl2ngc  Copyright (C) 2016 - 2023 Jakob Flierl" << endl;
    cerr << "This program comes with ABSOLUTELY NO WARRANTY;" << endl;
    cerr << "This is free software, and you are welcome to redistribute it"
        " under certain conditions." << endl << endl;

    cout.setf(ios::fixed, ios::floatfield);
    cout.setf(ios::showpoint);

    cerr.setf(ios::fixed, ios::floatfield);
    cerr.setf(ios::showpoint);

    double d_cutter = arguments.diameter;
    double l_cutter = arguments.length;
    MillingCutter* c;
    if (arguments.angle > 0) {
        double a_cutter = arguments.angle * PI / 180;
        c = new ConeCutter(d_cutter, a_cutter / 2, l_cutter);
        cerr << *static_cast<ConeCutter*>(c) << endl;
    } else {
        c = new CylCutter(d_cutter, l_cutter);
        cerr << *static_cast<CylCutter*>(c) << endl;
    }
    double d_overlap = d_cutter * (1 - 0.75);  // step percentage
    double corner = 0;  // d_cutter

    STLSurf s;
    STLReader r(wstr("/dev/stdin"), s);
    cerr << s << endl;
    cerr << s.bb << endl;

    double zdim = s.bb.maxpt.z - s.bb.minpt.z;
    cerr << "zdim " << zdim << endl;

    double zstart = s.bb.maxpt.z - zstep;
    cerr << "zstart " << zstart << endl;

    APDC apdc;
    apdc.setSTL(s);
    apdc.setCutter(c);
    apdc.setSampling(d_overlap);
    apdc.setMinSampling(d_overlap / 100);

    double minx, miny, maxx, maxy, z;
    minx = s.bb.minpt.x - corner;
    miny = s.bb.minpt.y - corner;
    maxx = s.bb.maxpt.x + corner;
    maxy = s.bb.maxpt.y + corner;
    z = s.bb.minpt.z - zsafe;

    double dx = d_overlap, dy = d_overlap;

    Path p;
    if (arguments.zigzag_x) {
        p = zigzag_x(minx, dx, maxx, miny, dy, maxy, z);
    } else {
        p = zigzag_y(minx, dx, maxx, miny, dy, maxy, z);
    }
    apdc.setPath(&p);
    apdc.setZ(z);

    cerr << "calculating... ";

    apdc.setThreads(4);
    apdc.run();

    cerr << "done." << endl;

    GCodeWriter w;

    cout << "G21 F200" << endl;
    cout << ";G64 P0.001" << endl;  // path control mode : constant velocity
    cout << "M03 S13500" << endl;  // start spindle

    cout << "G0" <<
        " X" << s.bb.minpt.x <<
        " Y" << s.bb.minpt.y <<
        " Z" << zsafe << endl;

    double zcurr = zstart;

    vector<CLPoint> pts = apdc.getPoints();

    bool fst = true;
    while (zcurr > s.bb.minpt.z - zstep) {

        cerr << "zcurr " << zcurr << endl;

        BOOST_FOREACH(Point cp, pts) {
            if (cp.z < s.bb.minpt.z) {
                z = 1;
            } else {
                z = -fmin(-cp.z, -zcurr) - s.bb.maxpt.z;
            }
            //if (!isNearlyEqual(z, 0)) {
            if (fst) {
                w.g0(cp.x, cp.y, zsafe);
                w.g0(cp.x, cp.y, 0);
                fst = false;
            }
            w.g1(cp.x, cp.y, z);
            //}
        }

        zcurr -= zstep;
        reverse(pts.begin(), pts.end());
    }

    cout << "G0Z" << zsafe << endl;
    cout << "M05" << endl;  // stop spindle
    cout << "G0 X0 Y0" << endl;
    cout << "M2" << endl;
    cout << "%" << endl;

    return 0;
}