#include "printf.h"

#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>


std::string mini_sprintf_format(bool left_align, bool sign, bool space_on_left, bool padding_by_zero, int width, int precision, int length, char specifier, const MlValue &value) {
	std::string s;
	std::ostringstream stream_str;		// PERF simpler solution..without string stream
	bool is_positive = false;

	if (specifier == 's') {
		return value.as_string();
	}
	if (specifier == 'c') {
		stream_str << (char) value.as_int();
		return stream_str.str();
	}
	if (specifier == 'i' || specifier == 'd') {
		if (value.is_number()) {
			int ival = value.as_int();
			is_positive = ival >= 0;

			s = std::to_string(ival);
		} else if (value.is_string()) {		// print ascii code of character
			if (value.as_string().size() > 0) {
				stream_str << (int)value.as_string()[0];
				return stream_str.str();
			} else {
				// TODO handle empty string - error?
			}
		}
	} else if (specifier == 'f' || specifier == 'e') {
		double dval = value.as_float();
		is_positive = dval >= 0;

		if (specifier == 'f')
			stream_str << std::fixed;
		else
			stream_str << std::scientific;

		if (precision >= 0)
			stream_str << std::setprecision(precision);

		stream_str << dval;
		s = stream_str.str(); // TODO ??
	}

	if (width > -1 && s.size() < width) {
		int new_width = width - (sign && is_positive ? 1 : 0); // spce for + sign
		if (s.size() < new_width) {
			char padd = padding_by_zero ? '0' : ' ';
			if (left_align) {
				s.insert(0, new_width - s.size(), padd);
			} else {
				s.append(new_width - s.size(), ' '); // cannot append 0 because its different number then
			}
		}
	}
	if (sign && is_positive) {
		s.insert(0, 1, '+');
	}
	if (space_on_left) {
		s.insert(0, 1, ' ');
	}

	return s;
}

std::string mini_sprintf(const std::string &format_str, const std::vector<MlValue> &parameters) {
	int arg_position = 0;
	char c, c1;
	std::string buf;
	std::string output_str;

	std::string::const_iterator si;
	for (si = format_str.begin(); si != format_str.end(); ++si) {
		c = *si;

		// formatting directives
		if (c == '%') {
			if (++si >= format_str.end()) {
				return output_str; // end of string, invalid % on last pos
			}

			c1 = *si;
			switch (c1) {
			case '%':
				output_str += c1;
				break;
			case '-':
			case '+':
			case ' ':
			case '#':
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			case '.':
			case 'i':
			case 'd':
			case 'f':
			case 'c':
			case 's':
				bool left_align = false;
				bool sign = false;
				bool space_on_left = false;
				bool padding_by_zero = false;
				int width = -1;
				int precision = -1;
				int length = -1;
				// http://www.cplusplus.com/reference/cstdio/printf/
				// https://www.menie.org/georges/embedded/small_printf_source_code.html
				//
				// %[flags][width][.precision][length]specifier

				// flags: - + \_ # 0
				while ((*si == '-' || *si == '+' || *si == ' ' || *si == '#' || *si == '0') && si < format_str.end()) {
					switch (*si) {
					case '-':
						left_align = true;
						break;
					case '+':
						sign = true;
						break;
					case ' ':
						space_on_left = true;
						break;
					case '#':
						break;
					case '0':
						padding_by_zero = true;
						left_align = true;
						break;
					};
					si++;
				}
				// width
				if (si >= format_str.end())
					return output_str; // invalid end of string
				while (*si >= '0' && *si <= '9' && si < format_str.end()) {
					if (width == -1) {
						width = 0;
					}
					width = width * 10 + (*si - '0');
					si++;
				}
				// precision
				if (si >= format_str.end())
					return output_str; // invalid end of string
				if (*si == '.') {
					precision = 0;
					if (++si >= format_str.end())
						return output_str; // invalid end of string
					while (*si >= '0' && *si <= '9' && si < format_str.end()) {
						precision = precision * 10 + (*si - '0');
						si++;
					}
				}
				// length
				// specifier
				if (si >= format_str.end())
					return output_str;				    // invalid end of string
				if (*si == 'i' || *si == 'd' || *si == 'f' || *si == 's' || *si == 'c') { // TODO more specifiers
					std::string s = mini_sprintf_format(left_align, sign, space_on_left, padding_by_zero, width, precision, length, *si, parameters[arg_position]);
					arg_position++;
					output_str += s;
				} else {
					output_str += "UNKNOWN FORMAT SPECIFIER";
				}
			};

			// escaping sequences
		} else if (c == '\\') {
			if (++si >= format_str.end()) {
				return output_str; // end of string, invalid % on last pos
			}

			c1 = *si;
			switch (c1) {
			case 't':
				output_str += '\t';
				break;
			case 'r':
				output_str += '\r';
				break;
			case 'n':
				output_str += '\n';
				break;
			case 'x':	// hex ie \x1b
				if (si + 2 >= format_str.end()) return output_str; // end of string, invalid hex constant
				buf.clear();
				buf.push_back(*(si + 1));
				buf.push_back(*(si + 2));
				output_str.push_back((char)std::strtol( &buf[0], 0, 16));
				si += 2;
				break;
			case '0':	// octal ie "\033"
				if (si + 2 >= format_str.end()) return output_str; // end of string, invalid octal constant
				buf.clear();
				buf.push_back(*(si + 1));
				buf.push_back(*(si + 2));
				output_str.push_back((char)std::strtol( &buf[0], 0, 8));
				// TODO maybe octal constant has 3 bytes
				// if (si + 2 >= format_str.end()) return output_str; // end of string, invalid octal or hex constant
				// buf.push_back(*(si + 3));
				// si += 3;
				si += 2;
				break;
			default:
				output_str += c1;
			};

			// normal characters
		} else {
			output_str += c;
		}
	}

	return output_str;
}