#pragma once

#include <vector>
#include <string>

namespace iosender {

enum class Dialect {
    Grbl,
    GrblHAL,
    LinuxCNC
};

enum class AxisFlags : int {
    None = 0,
    X = 1 << 0,
    Y = 1 << 1,
    Z = 1 << 2,
    A = 1 << 3,
    B = 1 << 4,
    C = 1 << 5,
    U = 1 << 6,
    V = 1 << 7,
    W = 1 << 8,
    XY = 0x03,
    XZ = 0x05,
    XYZ = 0x07,
    All = 0x3F
};

enum class IJKFlags : int {
    None = 0,
    I = 1 << 0,
    J = 1 << 1,
    K = 1 << 2,
    All = 0x07
};

enum class ThreadingFlags : int {
    None = 0,
    R = 1 << 0,
    Q = 1 << 1,
    H = 1 << 2,
    E = 1 << 3,
    L = 1 << 4,
    All = 0x1F
};

enum class Plane {
    XY,
    XZ,
    YZ
};

enum class DistanceMode {
    Absolute,
    Incremental
};

enum class FeedRateMode {
    InverseTime,    //G93
    UnitsPerMin,    //G94 - default
    UnitsPerRev     //G95
};

enum class MotionMode {
    G0 = 0,
    G1 = 10,
    G2 = 20,
    G3 = 30,
    G5 = 50,
    G5_1 = 51,
    G5_2 = 52,
    G33 = 330,
    G38_2 = 382,
    G38_3 = 383,
    G38_4 = 384,
    G38_5 = 385,
    G73 = 730,
    G76 = 760,
    G80 = 800,
    None = G80,
    G81 = 810,
    G82 = 820,
    G83 = 830,
    G84 = 840,
    G85 = 850,
    G86 = 860,
    G87 = 870,
    G88 = 880,
    G89 = 890
};

enum class IJKMode {
    Absolute,
    Incremental
};

enum class Units {
    Imperial = 0,
    Metric = 1
};

enum class SpindleState : int {
    Off = 1 << 0,
    CW = 1 << 1,
    CCW = 1 << 2
};

enum class CoolantState : int {
    Off = 0,
    Flood = 1 << 0,
    Mist = 1 << 1,
    Shower = 1 << 2
};

enum class ToolLengthOffset {
    Cancel = 0,         // G49 (Default: Must be zero)
    Enable = 1,         // G43
    EnableDynamic = 2,  // G43.1
    ApplyAdditional = 3 // G43.2
};

enum class ThreadTaper : int {
    None = 0,
    Entry = 1 << 0,
    Exit = 1 << 1,
    Both = Entry | Exit
};

enum class LatheMode : int {
    Disabled = 0,
    Diameter = 1, // Do not change
    Radius = 2    // Do not change
};

enum class Direction {
    Positive = 0,
    Negative
};

enum class InputWaitMode {
    Immediate = 0,
    Rise,
    Fall,
    High,
    Low
};

enum class Commands {
    G0,
    G1,
    G2,
    G3,
    G4,
    G5,
    G5_1,
    G7,
    G8,
    G10,
    G17,
    G18,
    G19,
    G20,
    G21,
    G28,
    G28_1,
    G30,
    G30_1,
    G33,
    G38_2,
    G38_3,
    G38_4,
    G38_5,
    G40,
    G43,
    G43_1,
    G43_2,
    G49,
    G50,
    G51,
    G53,
    G54,
    G55,
    G56,
    G57,
    G58,
    G59,
    G59_1,
    G59_2,
    G59_3,
    G61,
    G61_1,
    G64,
    G73,
    G76,
    G80,
    G81,
    G82,
    G83,
    G85,
    G86,
    G89,
    G90,
    G90_1,
    G91,
    G91_1,
    G92,
    G92_1,
    G92_2,
    G92_3,
    G93,
    G94,
    G95,
    G96,
    G97,
    G98,
    G99,
    M0,
    M1,
    M2,
    M3,
    M4,
    M5,
    M6,
    M7,
    M8,
    M9,
    M30,
    M48,
    M49,
    M50,
    M51,
    M52,
    M53,
    M56,
    M61,
    M62,
    M63,
    M64,
    M65,
    M66,
    M67,
    M68,
    Feedrate,
    SpindleRPM,
    ToolSelect,
    Comment,
    UserMCommand,
    Undefined
};

std::vector<int> ToIndices(AxisFlags flags);
std::vector<int> ToIndices(IJKFlags flags);
std::vector<int> ToIndices(ThreadingFlags flags);


class GCodeUtils {
public:
    static std::string StripSpaces(std::string line);
};

struct Point3D {
    union {
        struct {
            double X, Y, Z;
        };
        double value[3];
    };

    double& operator[](int index) {
        return value[index];
    }

    double *Array() { return value; }
};


struct Point6D {
    union {
        struct {
            double X, Y, Z, A, B, C, U, V, W;
        };
        double value[9];
    };

    double& operator[](int index) {
        return value[index];
    }

    double *Array() { return value; }

    explicit operator Point3D() { return Point3D{X, Y, Z}; }

    void Set(double *values, AxisFlags axisFlags, bool relative = false) {
        if (relative) {
            Add(values, axisFlags);
        } else {
            for (auto i: ToIndices(axisFlags)) {
                switch (i) {
                    case 0:
                        X = values[0];
                        break;
                    case 1:
                        Y = values[1];
                        break;
                    case 2:
                        Z = values[2];
                        break;
                    case 3:
                        A = values[3];
                        break;
                    case 4:
                        B = values[4];
                        break;
                    case 5:
                        C = values[5];
                        break;
                    case 6:
                        U = values[6];
                        break;
                    case 7:
                        V = values[7];
                        break;
                    case 8:
                        W = values[8];
                        break;
                }
            }
        }
    }

    void Add(const double values[], AxisFlags axisFlags) {
        for (auto i: ToIndices(axisFlags)) {
            switch (i) {
                case 0:
                    X += values[0];
                    break;
                case 1:
                    Y += values[1];
                    break;
                case 2:
                    Z += values[2];
                    break;
                case 3:
                    A += values[3];
                    break;
                case 4:
                    B += values[4];
                    break;
                case 5:
                    C += values[5];
                    break;
                case 6:
                    U += values[6];
                    break;
                case 7:
                    V += values[7];
                    break;
                case 8:
                    W += values[8];
                    break;
            }
        }
    }
};


}