/*
 *  yosys -- Yosys Open SYnthesis Suite
 *
 *  Copyright (C) 2020  whitequark <whitequark@whitequark.org>
 *
 *  Permission to use, copy, modify, and/or distribute this software for any
 *  purpose with or without fee is hereby granted, provided that the above
 *  copyright notice and this permission notice appear in all copies.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include "libs/bigint/BigUnsigned.hh"
#include "kernel/fmt.h"

USING_YOSYS_NAMESPACE

void Fmt::append_string(const std::string &str) {
	FmtPart part = {};
	part.type = FmtPart::STRING;
	part.str = str;
	parts.push_back(part);
}

void Fmt::parse_rtlil(const RTLIL::Cell *cell) {
	std::string fmt = cell->getParam(ID(FORMAT)).decode_string();
	RTLIL::SigSpec args = cell->getPort(ID(ARGS));
	parts.clear();

	FmtPart part;
	for (size_t i = 0; i < fmt.size(); i++) {
		if (fmt.substr(i, 2) == "}}") {
			part.str += '}';
			++i;
		} else if (fmt.substr(i, 2) == "{{") {
			part.str += '{';
			++i;
		} else if (fmt[i] == '}')
			log_assert(false && "Unexpected '}' in format string");
		else if (fmt[i] == '{') {
			if (!part.str.empty()) {
				part.type = FmtPart::STRING;
				parts.push_back(part);
				part = {};
			}

			if (++i == fmt.size())
				log_assert(false && "Unexpected end in format substitution");

			size_t arg_size = 0;
			for (; i < fmt.size(); i++) {
				if (fmt[i] >= '0' && fmt[i] <= '9') {
					arg_size *= 10;
					arg_size += fmt[i] - '0';
				} else if (fmt[i] == ':') {
					++i;
					break;
				} else {
					log_assert(false && "Unexpected character in format substitution");
				}
			}
			if (i == fmt.size())
				log_assert(false && "Unexpected end in format substitution");

			if ((size_t)args.size() < arg_size)
				log_assert(false && "Format part overruns arguments");
			part.sig = args.extract(0, arg_size);
			args.remove(0, arg_size);

			if (fmt[i] == '>')
				part.justify = FmtPart::RIGHT;
			else if (fmt[i] == '<')
				part.justify = FmtPart::LEFT;
			else
				log_assert(false && "Unexpected justification in format substitution");
			if (++i == fmt.size())
				log_assert(false && "Unexpected end in format substitution");

			if (fmt[i] == '0' || fmt[i] == ' ')
				part.padding = fmt[i];
			else
				log_assert(false && "Unexpected padding in format substitution");
			if (++i == fmt.size())
				log_assert(false && "Unexpected end in format substitution");

			for (; i < fmt.size(); i++) {
				if (fmt[i] >= '0' && fmt[i] <= '9') {
					part.width *= 10;
					part.width += fmt[i] - '0';
					continue;
				} else if (fmt[i] == 'b') {
					part.type = FmtPart::INTEGER;
					part.base = 2;
				} else if (fmt[i] == 'o') {
					part.type = FmtPart::INTEGER;
					part.base = 8;
				} else if (fmt[i] == 'd') {
					part.type = FmtPart::INTEGER;
					part.base = 10;
				} else if (fmt[i] == 'h') {
					part.type = FmtPart::INTEGER;
					part.base = 16;
				} else if (fmt[i] == 'c') {
					part.type = FmtPart::CHARACTER;
				} else if (fmt[i] == 't') {
					part.type = FmtPart::TIME;
				} else if (fmt[i] == 'r') {
					part.type = FmtPart::TIME;
					part.realtime = true;
				} else {
					log_assert(false && "Unexpected character in format substitution");
				}
				++i;
				break;
			}
			if (i == fmt.size())
				log_assert(false && "Unexpected end in format substitution");

			if (part.type == FmtPart::INTEGER) {
				if (fmt[i] == '+') {
					part.plus = true;
					if (++i == fmt.size())
						log_assert(false && "Unexpected end in format substitution");
				}

				if (fmt[i] == 'u')
					part.signed_ = false;
				else if (fmt[i] == 's')
					part.signed_ = true;
				else
					log_assert(false && "Unexpected character in format substitution");
				if (++i == fmt.size())
					log_assert(false && "Unexpected end in format substitution");
			}

			if (fmt[i] != '}')
				log_assert(false && "Expected '}' after format substitution");

			parts.push_back(part);
			part = {};
		} else {
			part.str += fmt[i];
		}
	}
	if (!part.str.empty()) {
		part.type = FmtPart::STRING;
		parts.push_back(part);
	}
}

void Fmt::emit_rtlil(RTLIL::Cell *cell) const {
	std::string fmt;
	RTLIL::SigSpec args;

	for (auto &part : parts) {
		switch (part.type) {
			case FmtPart::STRING:
				for (char c : part.str) {
					if (c == '{')
						fmt += "{{";
					else if (c == '}')
						fmt += "}}";
					else
						fmt += c;
				}
				break;

			case FmtPart::TIME:
				log_assert(part.sig.size() == 0);
				YS_FALLTHROUGH
			case FmtPart::CHARACTER:
				log_assert(part.sig.size() % 8 == 0);
				YS_FALLTHROUGH
			case FmtPart::INTEGER:
				args.append(part.sig);
				fmt += '{';
				fmt += std::to_string(part.sig.size());
				fmt += ':';
				if (part.justify == FmtPart::RIGHT)
					fmt += '>';
				else if (part.justify == FmtPart::LEFT)
					fmt += '<';
				else log_abort();
				log_assert(part.width == 0 || part.padding != '\0');
				fmt += part.padding != '\0' ? part.padding : ' ';
				if (part.width > 0)
					fmt += std::to_string(part.width);
				if (part.type == FmtPart::INTEGER) {
					switch (part.base) {
						case  2: fmt += 'b'; break;
						case  8: fmt += 'o'; break;
						case 10: fmt += 'd'; break;
						case 16: fmt += 'h'; break;
						default: log_abort();
					}
					if (part.plus)
						fmt += '+';
					fmt += part.signed_ ? 's' : 'u';
				} else if (part.type == FmtPart::CHARACTER) {
					fmt += 'c';
				} else if (part.type == FmtPart::TIME) {
					if (part.realtime)
						fmt += 'r';
					else
						fmt += 't';
				} else log_abort();
				fmt += '}';
				break;

			default: log_abort();
		}
	}

	cell->setParam(ID(FORMAT), fmt);
	cell->setParam(ID(ARGS_WIDTH), args.size());
	cell->setPort(ID(ARGS), args);
}

static size_t compute_required_decimal_places(size_t size, bool signed_)
{
	BigUnsigned max;
	if (!signed_)
		max.setBit(size, true);
	else
		max.setBit(size - 1, true);
	size_t places = 0;
	while (!max.isZero()) {
		places++;
		max /= 10;
	}
	if (signed_)
		places++;
	return places;
}

static size_t compute_required_nondecimal_places(size_t size, unsigned base)
{
	log_assert(base != 10);
	BigUnsigned max;
	max.setBit(size - 1, true);
	size_t places = 0;
	while (!max.isZero()) {
		places++;
		max /= base;
	}
	return places;
}

// Only called for integers, either when:
//
// (a) passed without a format string (e.g. "$display(a);"), or
//
// (b) the corresponding format specifier has no leading zero, e.g. "%b",
// "%20h", "%-10d".
//
// In these cases, for binary/octal/hex, we always zero-pad to the size of the
// signal; i.e. whether "%h" or "%10h" or "%-20h" is used, if the corresponding
// signal is 32'h1234, "00001234" will always be a substring of the output.
//
// For case (a), we have no specified width, so there is nothing more to do.
//
// For case (b), because we are only called with no leading zero on the
// specifier, any specified width beyond the signal size is therefore space
// padding, whatever the justification.
//
// For decimal, we do no zero-padding, instead space-padding to the size
// required for the signal's largest value.  This is per other Verilog
// implementations, and intuitively makes sense as decimal representations lack
// a discrete mapping of digits to bit groups.  Decimals may also show sign and
// must accommodate this, whereas other representations do not.
void Fmt::apply_verilog_automatic_sizing_and_add(FmtPart &part)
{
	if (part.base == 10) {
		size_t places = compute_required_decimal_places(part.sig.size(), part.signed_);
		part.padding = ' ';
		part.width = std::max(part.width, places);
		parts.push_back(part);
		return;
	}

	part.padding = '0';

	size_t places = compute_required_nondecimal_places(part.sig.size(), part.base);
	if (part.width < places) {
		part.justify = FmtPart::RIGHT;
		part.width = places;
		parts.push_back(part);
	} else if (part.width == places) {
		parts.push_back(part);
	} else if (part.width > places) {
		auto gap = std::string(part.width - places, ' ');
		part.width = places;

		if (part.justify == FmtPart::RIGHT) {
			append_string(gap);
			parts.push_back(part);
		} else {
			part.justify = FmtPart::RIGHT;
			parts.push_back(part);
			append_string(gap);
		}
	}
}

void Fmt::parse_verilog(const std::vector<VerilogFmtArg> &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name)
{
	parts.clear();

	auto arg = args.begin();
	for (; arg != args.end(); ++arg) {
		switch (arg->type) {
			case VerilogFmtArg::INTEGER: {
				FmtPart part = {};
				part.type = FmtPart::INTEGER;
				part.sig = arg->sig;
				part.base = default_base;
				part.signed_ = arg->signed_;
				apply_verilog_automatic_sizing_and_add(part);
				break;
			}

			case VerilogFmtArg::STRING: {
				if (arg == args.begin() || !sformat_like) {
					const auto fmtarg = arg;
					const std::string &fmt = fmtarg->str;
					FmtPart part = {};
					for (size_t i = 0; i < fmt.size(); i++) {
						if (fmt[i] != '%') {
							part.str += fmt[i];
						} else if (fmt.substr(i, 2) == "%%") {
							i++;
							part.str += '%';
						} else if (fmt.substr(i, 2) == "%l" || fmt.substr(i, 2) == "%L") {
							i++;
							part.str += module_name.str();
						} else if (fmt.substr(i, 2) == "%m" || fmt.substr(i, 2) == "%M") {
							i++;
							part.str += module_name.str();
						} else {
							if (!part.str.empty()) {
								part.type = FmtPart::STRING;
								parts.push_back(part);
								part = {};
							}
							if (++i == fmt.size()) {
								log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with incomplete format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1);
							}

							if (++arg == args.end()) {
								log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with fewer arguments than the format specifiers in argument %zu require.\n", task_name.c_str(), fmtarg - args.begin() + 1);
							}
							part.sig = arg->sig;
							part.signed_ = arg->signed_;

							for (; i < fmt.size(); i++) {
								if (fmt[i] == '-') {
									// left justify; not in IEEE 1800-2017 or verilator but iverilog has it
									part.justify = FmtPart::LEFT;
								} else if (fmt[i] == '+') {
									// always show sign; not in IEEE 1800-2017 or verilator but iverilog has it
									part.plus = true;
								} else break;
							}
							if (i == fmt.size()) {
								log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with incomplete format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1);
							}

							bool has_leading_zero = false, has_width = false;
							for (; i < fmt.size(); i++) {
								if (fmt[i] >= '0' && fmt[i] <= '9') {
									if (fmt[i] == '0' && !has_width) {
										has_leading_zero = true;
									} else {
										has_width = true;
										part.width *= 10;
										part.width += fmt[i] - '0';
									}
									continue;
								} else if (fmt[i] == 'b' || fmt[i] == 'B') {
									part.type = FmtPart::INTEGER;
									part.base =  2;
								} else if (fmt[i] == 'o' || fmt[i] == 'O') {
									part.type = FmtPart::INTEGER;
									part.base =  8;
								} else if (fmt[i] == 'd' || fmt[i] == 'D') {
									part.type = FmtPart::INTEGER;
									part.base = 10;
								} else if (fmt[i] == 'h' || fmt[i] == 'H' ||
								           fmt[i] == 'x' || fmt[i] == 'X') {
									// hex digits always printed in lowercase for %h%x as well as %H%X
									part.type = FmtPart::INTEGER;
									part.base = 16;
								} else if (fmt[i] == 'c' || fmt[i] == 'C') {
									part.type = FmtPart::CHARACTER;
									part.sig.extend_u0(8);
									// %10c and %010c not fully defined in IEEE 1800-2017 and do different things in iverilog
								} else if (fmt[i] == 's' || fmt[i] == 'S') {
									part.type = FmtPart::CHARACTER;
									if ((part.sig.size() % 8) != 0)
										part.sig.extend_u0((part.sig.size() + 7) / 8 * 8);
									// %10s and %010s not fully defined in IEEE 1800-2017 and do the same thing in iverilog
									part.padding = ' ';
								} else if (fmt[i] == 't' || fmt[i] == 'T') {
									if (arg->type == VerilogFmtArg::TIME) {
										part.type = FmtPart::TIME;
										part.realtime = arg->realtime;
										if (!has_width && !has_leading_zero)
											part.width = 20;
									} else {
										log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with format character `%c' in argument %zu, but the argument is not $time or $realtime.\n", task_name.c_str(), fmt[i], fmtarg - args.begin() + 1);
									}
								} else {
									log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with unrecognized format character `%c' in argument %zu.\n", task_name.c_str(), fmt[i], fmtarg - args.begin() + 1);
								}
								break;
							}
							if (i == fmt.size()) {
								log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with incomplete format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1);
							}

							if (part.padding == '\0')
								part.padding = (has_leading_zero && part.justify == FmtPart::RIGHT) ? '0' : ' ';

							if (part.type == FmtPart::INTEGER && part.base != 10 && part.plus)
								log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with invalid format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1);

							if (part.type == FmtPart::INTEGER && !has_leading_zero)
								apply_verilog_automatic_sizing_and_add(part);
							else
								parts.push_back(part);
							part = {};
						}
					}
					if (!part.str.empty()) {
						part.type = FmtPart::STRING;
						parts.push_back(part);
					}
				} else {
					FmtPart part = {};
					part.type = FmtPart::STRING;
					part.str = arg->str;
					parts.push_back(part);
				}
				break;
			}

			default: log_abort();
		}
	}
}

std::vector<VerilogFmtArg> Fmt::emit_verilog() const
{
	std::vector<VerilogFmtArg> args;
	VerilogFmtArg fmt = {};
	fmt.type = VerilogFmtArg::STRING;

	for (auto &part : parts) {
		switch (part.type) {
			case FmtPart::STRING:
				for (char c : part.str) {
					if (c == '%')
						fmt.str += "%%";
					else
						fmt.str += c;
				}
				break;

			case FmtPart::INTEGER: {
				VerilogFmtArg arg = {};
				arg.type = VerilogFmtArg::INTEGER;
				arg.sig = part.sig;
				arg.signed_ = part.signed_;
				args.push_back(arg);

				fmt.str += '%';
				if (part.plus)
					fmt.str += '+';
				if (part.justify == FmtPart::LEFT)
					fmt.str += '-';
				if (part.width == 0) {
					fmt.str += '0';
				} else if (part.width > 0) {
					log_assert(part.padding == ' ' || part.padding == '0');
					if (part.base != 10 || part.padding == '0')
						fmt.str += '0';
					fmt.str += std::to_string(part.width);
				}
				switch (part.base) {
					case  2: fmt.str += 'b'; break;
					case  8: fmt.str += 'o'; break;
					case 10: fmt.str += 'd'; break;
					case 16: fmt.str += 'h'; break;
					default: log_abort();
				}
				break;
			}

			case FmtPart::CHARACTER: {
				VerilogFmtArg arg;
				arg.type = VerilogFmtArg::INTEGER;
				arg.sig = part.sig;
				args.push_back(arg);

				fmt.str += '%';
				if (part.justify == FmtPart::LEFT)
					fmt.str += '-';
				if (part.sig.size() == 8) {
					if (part.width > 0) {
						log_assert(part.padding == '0' || part.padding == ' ');
						if (part.padding == '0')
							fmt.str += part.padding;
						fmt.str += std::to_string(part.width);
					}
					fmt.str += 'c';
				} else {
					log_assert(part.sig.size() % 8 == 0);
					if (part.width > 0) {
						log_assert(part.padding == ' '); // no zero padding
						fmt.str += std::to_string(part.width);
					}
					fmt.str += 's';
				}
				break;
			}

			case FmtPart::TIME: {
				VerilogFmtArg arg;
				arg.type = VerilogFmtArg::TIME;
				if (part.realtime)
					arg.realtime = true;
				args.push_back(arg);

				fmt.str += '%';
				if (part.plus)
					fmt.str += '+';
				if (part.justify == FmtPart::LEFT)
					fmt.str += '-';
				log_assert(part.padding == ' ' || part.padding == '0');
				if (part.padding == '0' && part.width > 0)
					fmt.str += '0';
				fmt.str += std::to_string(part.width);
				fmt.str += 't';
				break;
			}

			default: log_abort();
		}
	}

	args.insert(args.begin(), fmt);
	return args;
}

void Fmt::emit_cxxrtl(std::ostream &f, std::function<void(const RTLIL::SigSpec &)> emit_sig) const
{
	for (auto &part : parts) {
		switch (part.type) {
			case FmtPart::STRING:
				f << " << \"";
				for (char c : part.str) {
					switch (c) {
						case '\\':
							YS_FALLTHROUGH
						case '"':
							f << '\\' << c;
							break;
						case '\a':
							f << "\\a";
							break;
						case '\b':
							f << "\\b";
							break;
						case '\f':
							f << "\\f";
							break;
						case '\n':
							f << "\\n";
							break;
						case '\r':
							f << "\\r";
							break;
						case '\t':
							f << "\\t";
							break;
						case '\v':
							f << "\\v";
							break;
						default:
							f << c;
							break;
					}
				}
				f << '"';
				break;

			case FmtPart::INTEGER:
			case FmtPart::CHARACTER: {
				f << " << value_formatted<" << part.sig.size() << ">(";
				emit_sig(part.sig);
				f << ", " << (part.type == FmtPart::CHARACTER);
				f << ", " << (part.justify == FmtPart::LEFT);
				f << ", (char)" << (int)part.padding;
				f << ", " << part.width;
				f << ", " << part.base;
				f << ", " << part.signed_;
				f << ", " << part.plus;
				f << ')';
				break;
			}

			case FmtPart::TIME: {
				// CXXRTL only records steps taken, so there's no difference between
				// the values taken by $time and $realtime.
				f << " << value_formatted<64>(";
				f << "value<64>{steps}";
				f << ", " << (part.type == FmtPart::CHARACTER);
				f << ", " << (part.justify == FmtPart::LEFT);
				f << ", (char)" << (int)part.padding;
				f << ", " << part.width;
				f << ", " << part.base;
				f << ", " << part.signed_;
				f << ", " << part.plus;
				f << ')';
				break;
			}

			default: log_abort();
		}
	}
}

std::string Fmt::render() const
{
	std::string str;

	for (auto &part : parts) {
		switch (part.type) {
			case FmtPart::STRING:
				str += part.str;
				break;

			case FmtPart::INTEGER:
			case FmtPart::TIME:
			case FmtPart::CHARACTER: {
				std::string buf;
				if (part.type == FmtPart::INTEGER) {
					RTLIL::Const value = part.sig.as_const();

					if (part.base != 10) {
						size_t minimum_size = 0;
						for (size_t index = 0; index < (size_t)value.size(); index++)
							if (value[index] != State::S0)
								minimum_size = index + 1;
						value = value.extract(0, minimum_size);
					}

					if (part.base == 2) {
						buf = value.as_string();
					} else if (part.base == 8 || part.base == 16) {
						size_t step = (part.base == 16) ? 4 : 3;
						for (size_t index = 0; index < (size_t)value.size(); index += step) {
							RTLIL::Const subvalue = value.extract(index, min(step, value.size() - index));
							bool has_x = false, all_x = true, has_z = false, all_z = true;
							for (State bit : subvalue) {
								if (bit == State::Sx)
									has_x = true;
								else
									all_x = false;
								if (bit == State::Sz)
									has_z = true;
								else
									all_z = false;
							}
							if (all_x)
								buf += 'x';
							else if (all_z)
								buf += 'z';
							else if (has_x)
								buf += 'X';
							else if (has_z)
								buf += 'Z';
							else
								buf += "0123456789abcdef"[subvalue.as_int()];
						}
						std::reverse(buf.begin(), buf.end());
					} else if (part.base == 10) {
						bool has_x = false, all_x = true, has_z = false, all_z = true;
						for (State bit : value) {
							if (bit == State::Sx)
								has_x = true;
							else
								all_x = false;
							if (bit == State::Sz)
								has_z = true;
							else
								all_z = false;
						}
						if (all_x)
							buf += 'x';
						else if (all_z)
							buf += 'z';
						else if (has_x)
							buf += 'X';
						else if (has_z)
							buf += 'Z';
						else {
							bool negative = part.signed_ && value[value.size() - 1];
							RTLIL::Const absvalue;
							if (negative)
								absvalue = RTLIL::const_neg(value, {}, part.signed_, {}, value.size() + 1);
							else
								absvalue = value;
							log_assert(absvalue.is_fully_def());
							if (absvalue.is_fully_zero())
								buf += '0';
							while (!absvalue.is_fully_zero())	{
								buf += '0' + RTLIL::const_mod(absvalue, 10, false, false, 4).as_int();
								absvalue = RTLIL::const_div(absvalue, 10, false, false, absvalue.size());
							}
							if (negative || part.plus)
								buf += negative ? '-' : '+';
							std::reverse(buf.begin(), buf.end());
						}
					} else log_abort();
				} else if (part.type == FmtPart::CHARACTER) {
					buf = part.sig.as_const().decode_string();
				} else if (part.type == FmtPart::TIME) {
					// We only render() during initial, so time is always zero.
					buf = "0";
				}

				log_assert(part.width == 0 || part.padding != '\0');
				if (part.justify == FmtPart::RIGHT && buf.size() < part.width) {
					size_t pad_width = part.width - buf.size();
					if (part.padding == '0' && (!buf.empty() && (buf.front() == '+' || buf.front() == '-'))) {
						str += buf.front();
						buf.erase(0, 1);
					}
					str += std::string(pad_width, part.padding);
				}
				str += buf;
				if (part.justify == FmtPart::LEFT && buf.size() < part.width)
					str += std::string(part.width - buf.size(), part.padding);
				break;
			}
		}
	}

	return str;
}
