#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstring>
#include <cerrno>

// no
//
// ... by Eli Fulkerson.  See http://www.elifulkerson.com for updates
//

/*
    This program 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.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using namespace std;

enum Mode { TOGGLE, ADD, DELETE };

static bool is_space(char c) {
	return c == ' ' || c == '\t';
}

// Transform a single line according to the selected mode.  Leading
// whitespace (the Cisco-style indentation of a config block) is preserved:
// the "no " prefix is added or removed immediately after the indentation,
// not at column zero.  Blank or whitespace-only lines are passed through
// untouched.
static string toggle_line(const string& line, Mode mode, bool ignore_case) {

	// measure the leading indentation
	size_t i = 0;
	while (i < line.size() && is_space(line[i])) {
		i++;
	}

	// nothing but whitespace (or empty): pass through unchanged
	if (i >= line.size()) {
		return line;
	}

	const string indent = line.substr(0, i);

	// does the content start with "no" followed by whitespace?
	bool has_no = false;
	if (line.size() - i >= 3) {
		char c0 = line[i];
		char c1 = line[i + 1];
		bool no_match = ignore_case
			? ((c0 == 'n' || c0 == 'N') && (c1 == 'o' || c1 == 'O'))
			: (c0 == 'n' && c1 == 'o');
		if (no_match && is_space(line[i + 2])) {
			has_no = true;
		}
	}

	if (has_no) {
		// strip in toggle or delete mode; otherwise leave as-is
		if (mode == TOGGLE || mode == DELETE) {
			size_t j = i + 2;
			while (j < line.size() && is_space(line[j])) {
				j++;
			}
			return indent + line.substr(j);
		}
		return line;
	}

	// no leading "no ": add it in toggle or add mode; otherwise leave as-is
	if (mode == TOGGLE || mode == ADD) {
		return indent + "no " + line.substr(i);
	}
	return line;
}

// Stream a single input through the transform.  The presence (or absence)
// of a trailing newline on the final line is preserved.  Output is left to
// the stream's normal buffering -- we do not flush per line.
static void process(istream& in, Mode mode, bool ignore_case) {
	string line;
	while (getline(in, line)) {
		cout << toggle_line(line, mode, ignore_case);
		// getline only sets eof() when it consumed the last line without a
		// terminating '\n'; in that case we must not invent one.
		if (!in.eof()) {
			cout << '\n';
		}
	}
}

static void print_help() {
	cout << "no takes stdin (or files), and toggles the presence of a leading 'no ' on each line." << endl;
	cout << endl;
	cout << "Usage: no [OPTION]... [FILE]..." << endl;
	cout << endl;
	cout << "With no FILE, or when FILE is -, read standard input." << endl;
	cout << "Leading indentation is preserved; blank lines pass through unchanged." << endl;
	cout << endl;
	cout << "  cat config.txt | no" << endl;
	cout << endl;
	cout << "Options:" << endl
		 << "  -t, --toggle       Default behavior: toggle 'no ' on where it is not and vice versa" << endl
		 << "  -a, --add          Always add a 'no ' if it isn't present" << endl
		 << "  -d, --delete       Always strip a 'no ' if it is present" << endl
		 << "  -i, --ignore-case  Match a leading 'no' case-insensitively" << endl
		 << "  -v, --version      Print version information and exit" << endl
		 << "  -h, --help         Print this help and exit" << endl << flush;
}

int main(int argc, char* argv[]) {

	Mode mode = TOGGLE;
	bool ignore_case = false;
	vector<string> files;
	bool opts_done = false;

	// parse arguments: options, then files (with '-' meaning stdin).
	// "--" terminates option processing; unknown options are an error.
	for (int i = 1; i < argc; i++) {

		string arg = argv[i];

		if (!opts_done && arg == "--") {
			opts_done = true;
			continue;
		}

		if (!opts_done && arg.size() > 1 && arg[0] == '-') {

			if (arg == "-a" || arg == "--add") {
				mode = ADD;
			} else if (arg == "-d" || arg == "--delete") {
				mode = DELETE;
			} else if (arg == "-t" || arg == "--toggle") {
				mode = TOGGLE;
			} else if (arg == "-i" || arg == "--ignore-case") {
				ignore_case = true;
			} else if (arg == "-v" || arg == "--version") {
				cout << "no v0.2  1 June 2026" << endl
					 << "by Eli Fulkerson.  See http://www.elifulkerson.com for updates" << endl << flush;
				return 0;
			} else if (arg == "-h" || arg == "--help" || arg == "--usage" || arg == "-?") {
				print_help();
				return 0;
			} else {
				cerr << "no: unknown option '" << arg << "'" << endl
					 << "Try 'no --help' for more information." << endl;
				return 2;
			}
			continue;
		}

		// not an option: it's a file (or "-")
		files.push_back(arg);
	}

	if (files.empty()) {
		process(cin, mode, ignore_case);
		return 0;
	}

	int rc = 0;
	for (size_t i = 0; i < files.size(); i++) {
		if (files[i] == "-") {
			process(cin, mode, ignore_case);
			continue;
		}
		ifstream fin(files[i].c_str());
		if (!fin) {
			cerr << "no: cannot open '" << files[i] << "': " << strerror(errno) << endl;
			rc = 1;
			continue;
		}
		process(fin, mode, ignore_case);
	}
	return rc;
}
