/*
 * Copyright (C) 2017-2020  The Project X-Ray Authors.
 *
 * Use of this source code is governed by a ISC-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/ISC
 *
 * SPDX-License-Identifier: ISC
 */
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <iostream>
#include <set>
#include <string>
#include <vector>

#include <absl/strings/numbers.h>
#include <absl/strings/str_cat.h>
#include <absl/strings/str_split.h>
#include <gflags/gflags.h>
#include <prjxray/memory_mapped_file.h>
#include <prjxray/xilinx/architectures.h>
#include <prjxray/xilinx/bitstream_reader.h>
#include <prjxray/xilinx/configuration.h>

DEFINE_bool(c, false, "output '*' for repeating patterns");
DEFINE_bool(C, false, "do not ignore the checksum in each frame");
DEFINE_int32(f,
             -1,
             "only dump the specified frame (might be used more than once)");
DEFINE_string(F,
              "",
              "<first_frame_address>:<last_frame_address> only dump frame in "
              "the specified range");
DEFINE_string(o, "", "write machine-readable output file with config frames");
DEFINE_bool(p, false, "output a binary netpgm image");
DEFINE_bool(x,
            false,
            "use format 'bit_%%08x_%%03d_%%02d_t%%d_h%%d_r%%d_c%%d_m%%d'\n"
            "The fields have the following meaning:\n"
            "  - complete 32 bit hex frame id\n"
            "  - word index with that frame (decimal)\n"
            "  - bit index with that word (decimal)\n"
            "  - decoded frame type from frame id\n"
            "  - decoded top/botttom from frame id (top=0)\n"
            "  - decoded row address from frame id\n"
            "  - decoded column address from frame id\n"
            "  - decoded minor address from frame id\n");
DEFINE_bool(y, false, "use format 'bit_%%08x_%%03d_%%02d'");
DEFINE_bool(z, false, "skip zero frames (frames with all bits cleared) in o");
DEFINE_string(part_file, "", "YAML file describing a Xilinx part");
DEFINE_string(architecture,
              "Series7",
              "Architecture of the provided bitstream");
DEFINE_string(
    aux,
    "",
    "write machine-readable output file with auxiliary bitstream data");

namespace xilinx = prjxray::xilinx;

std::set<uint32_t> frames;
uint32_t frame_range_begin = 0, frame_range_end = 0;

std::vector<uint32_t> zero_frame(101);

struct BitReader {
	BitReader(const absl::Span<uint8_t>& bytes) : bytes_(bytes) {}

	const absl::Span<uint8_t>& bytes_;

	template <typename T>
	int operator()(T& arg) {
		using ArchType = std::decay_t<decltype(arg)>;
		auto reader =
		    xilinx::BitstreamReader<ArchType>::InitWithBytes(bytes_);
		if (!reader) {
			std::cerr << "Input doesn't look like a bitstream"
			          << std::endl;
			return 1;
		}

		std::cout << "Config size: " << reader->words().size()
		          << " words" << std::endl;

		auto part = ArchType::Part::FromFile(FLAGS_part_file);
		if (!part) {
			std::cerr << "Part file not found or invalid"
			          << std::endl;
			return 1;
		}
		auto config = xilinx::Configuration<ArchType>::InitWithPackets(
		    *part, *reader);
		if (!config) {
			std::cerr
			    << "Bitstream does not appear to be for this part"
			    << std::endl;
			return 1;
		}

		std::cout << "Number of configuration frames: "
		          << config->frames().size() << std::endl;

		FILE* f = stdout;

		if (!FLAGS_o.empty()) {
			f = fopen(FLAGS_o.c_str(), "w");

			if (f == nullptr) {
				printf(
				    "Can't open output file '%s' for "
				    "writing!\n",
				    FLAGS_o.c_str());
				return 1;
			}
		} else {
			fprintf(f, "\n");
		}

		if (!FLAGS_aux.empty()) {
			FILE* aux_file = fopen(FLAGS_aux.c_str(), "w");
			if (aux_file == nullptr) {
				printf(
				    "Can't open aux output file '%s' for "
				    "writing!\n",
				    FLAGS_aux.c_str());
				return 1;
			}
			// Extract and decode header information as in RBT file
			xilinx::BitstreamReader<ArchType>::PrintHeader(
			    bytes_, aux_file);
			// Extract FPGA configuration logic information
			reader->PrintFpgaConfigurationLogicData(aux_file);
			// Extract configuration frames' addresses
			config->PrintFrameAddresses(aux_file);
			fclose(aux_file);
		}

		std::vector<std::vector<bool>> pgmdata;
		std::vector<int> pgmsep;

		int word_length = sizeof(typename ArchType::WordType) * 8;
		for (auto& it : config->frames()) {
			if (FLAGS_z && it.second == zero_frame)
				continue;

			if (!frames.empty() && !frames.count(it.first))
				continue;

			if (frame_range_begin != frame_range_end &&
			    (it.first < frame_range_begin ||
			     frame_range_end <= it.first))
				continue;

			if (FLAGS_o.empty())
				printf(
				    "Frame 0x%08x (Type=%d Top=%d Row=%d "
				    "Column=%d "
				    "Minor=%d):\n",
				    static_cast<uint32_t>(it.first),
				    static_cast<unsigned int>(
				        it.first.block_type()),
				    it.first.is_bottom_half_rows() ? 1 : 0,
				    it.first.row(), it.first.column(),
				    it.first.minor());

			if (FLAGS_p) {
				if (it.first.minor() == 0 && !pgmdata.empty())
					pgmsep.push_back(pgmdata.size());

				pgmdata.push_back(std::vector<bool>());

				for (size_t i = 0; i < it.second.size(); i++)
					for (int k = 0; k < word_length; k++)
						pgmdata.back().push_back(
						    (it.second.at(i) &
						     (1 << k)) != 0);
			} else if (FLAGS_x || FLAGS_y) {
				for (int i = 0; i < (int)it.second.size();
				     i++) {
					for (int k = 0; k < word_length; k++) {
						if (((i != 50 || k > 12 ||
						      FLAGS_C) ||
						     std::is_same<
						         ArchType,
						         prjxray::xilinx::
						             Spartan6>::
						         value) &&
						    ((it.second.at(i) &
						      (1 << k)) != 0)) {
							if (FLAGS_x)
								fprintf(
								    f,
								    "bit_%08x_%"
								    "03d_%"
								    "02d_t%d_h%"
								    "d_r%d_c%"
								    "d_m%d\n",
								    static_cast<
								        uint32_t>(
								        it.first),
								    i, k,
								    static_cast<
								        unsigned int>(
								        it.first
								            .block_type()),
								    it.first.is_bottom_half_rows()
								        ? 1
								        : 0,
								    it.first
								        .row(),
								    it.first
								        .column(),
								    it.first
								        .minor());
							else
								fprintf(
								    f,
								    "bit_%08x_%"
								    "03d_"
								    "%02d\n",
								    static_cast<
								        uint32_t>(
								        it.first),
								    i, k);
						}
					}
				}
				if (FLAGS_o.empty())
					fprintf(f, "\n");
			} else {
				if (!FLAGS_o.empty())
					fprintf(
					    f, ".frame 0x%08x\n",
					    static_cast<uint32_t>(it.first));

				for (size_t i = 0; i < it.second.size(); i++)
					if (std::is_same<ArchType,
					                 prjxray::xilinx::
					                     Spartan6>::value) {
						fprintf(
						    f, "%08x%s",
						    it.second.at(i) &
						        0xffffffff,
						    (i % 6) == 5 ? "\n" : " ");
					} else {
						fprintf(
						    f, "%08x%s",
						    it.second.at(i) &
						        ((i != 50 || FLAGS_C)
						             ? 0xffffffff
						             : 0xffffe000),
						    (i % 6) == 5 ? "\n" : " ");
					}

				fprintf(f, "\n\n");
			}
		}

		if (FLAGS_p) {
			int width = pgmdata.size() + pgmsep.size();
			int height = ArchType::words_per_frame * word_length;
			fprintf(f, "P5 %d %d 15\n", width, height);

			for (int y = 0,
			         bit = ArchType::words_per_frame * word_length -
			               1;
			     y < height; y++, bit--) {
				for (int x = 0, frame = 0, sep = 0; x < width;
				     x++, frame++) {
					if (sep < int(pgmsep.size()) &&
					    frame == pgmsep.at(sep)) {
						fputc(8, f);
						x++, sep++;
					}

					if (bit >=
					    (int)pgmdata.at(frame).size()) {
						fputc(0, f);
						continue;
					}

					fputc(
					    pgmdata.at(frame).at(bit) ? 15 : 0,
					    f);
				}

				if (bit % word_length == 0 && y) {
					for (int x = 0; x < width; x++)
						fputc(8, f);
					y++;
				}
			}
		}

		if (!FLAGS_o.empty())
			fclose(f);

		printf("DONE\n");
		return 0;
	}
};

int main(int argc, char** argv) {
	gflags::SetUsageMessage(
	    absl::StrCat("Usage: ", argv[0], " [options] [bitfile]"));
	gflags::ParseCommandLineFlags(&argc, &argv, true);

	if (FLAGS_f >= 0) {
		frames.insert(FLAGS_f);
	}

	if (!FLAGS_F.empty()) {
		std::pair<std::string, std::string> p =
		    absl::StrSplit(FLAGS_F, ":");
		frame_range_begin = strtol(p.first.c_str(), nullptr, 0);
		frame_range_end = strtol(p.second.c_str(), nullptr, 0) + 1;
	}

	absl::Span<uint8_t> in_bytes;
	if (argc == 2) {
		auto in_file_name = argv[1];
		auto in_file =
		    prjxray::MemoryMappedFile::InitWithFile(in_file_name);
		if (!in_file) {
			std::cerr << "Can't open input file '" << in_file_name
			          << "' for reading!" << std::endl;
			return 1;
		}

		std::cout << "Bitstream size: " << in_file->size() << " bytes"
		          << std::endl;

		in_bytes = absl::Span<uint8_t>(
		    static_cast<uint8_t*>(in_file->data()), in_file->size());
	} else {
		std::vector<uint8_t> t;
		while (1) {
			int c = getchar();
			if (c == EOF)
				break;
			t.push_back(c);
		}
		in_bytes = absl::Span<uint8_t>(
			static_cast<uint8_t*>(t.data()), t.size());

		std::cout << "Bitstream size: " << in_bytes.size() << " bytes"
		          << std::endl;
	}

	xilinx::Architecture::Container arch_container =
	    xilinx::ArchitectureFactory::create_architecture(
	        FLAGS_architecture);
	return absl::visit(BitReader(in_bytes), arch_container);
}
