#include "GCodeReader.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <iostream>

namespace Slic3r {

void
GCodeReader::apply_config(const PrintConfigBase &config)
{
    this->_config.apply(config, true);
    this->_extrusion_axis = this->_config.get_extrusion_axis()[0];
}

void
GCodeReader::parse(const std::string &gcode, callback_t callback)
{
    std::istringstream ss(gcode);
    std::string line;
    while (std::getline(ss, line)) {
        GCodeLine gline(this);
        gline.raw = line;
        if (this->verbose)
            std::cout << line << std::endl;
        
        // strip comment
        {
            size_t pos = line.find(';');
            if (pos != std::string::npos) {
                gline.comment = line.substr(pos+1);
                line.erase(pos);
            }
        }
        
        // command and args
        {
            std::vector<std::string> args;
            boost::split(args, line, boost::is_any_of(" "));
            
            // first one is cmd
            gline.cmd = args.front();
            args.erase(args.begin());
            
            for (std::string &arg : args) {
                if (arg.size() < 2) continue;
                gline.args.insert(std::make_pair(arg[0], arg.substr(1)));
            }
        }
        
        // convert extrusion axis
        if (this->_extrusion_axis != 'E') {
            const auto it = gline.args.find(this->_extrusion_axis);
            if (it != gline.args.end()) {
                std::swap(gline.args['E'], it->second);
                gline.args.erase(it);
            }
        }
        
        if (gline.has('E') && this->_config.use_relative_e_distances)
            this->E = 0;
        
        if (callback) callback(*this, gline);
        
        // update coordinates
        if (gline.cmd == "G0" || gline.cmd == "G1" || gline.cmd == "G92") {
            this->X = gline.new_X();
            this->Y = gline.new_Y();
            this->Z = gline.new_Z();
            this->E = gline.new_E();
            this->F = gline.new_F();
        }
    }
}

void
GCodeReader::GCodeLine::set(char arg, std::string value)
{
    const std::string space(" ");
    if (this->has(arg)) {
        size_t pos = this->raw.find(space + arg)+2;
        size_t end = this->raw.find(' ', pos+1);
        this->raw = this->raw.replace(pos, end-pos, value);
    } else {
        size_t pos = this->raw.find(' ');
        if (pos == std::string::npos) {
            this->raw += space + arg + value;
        } else {
            this->raw = this->raw.replace(pos, 0, space + arg + value);
        }
    }
    this->args[arg] = value;
}

}
