#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <stdexcept>
#include <iomanip>
#include <cstdlib> 

/**
 * @brief Class representing a Linear Programming problem instance
 * 
 *  This reader can be used as a starting point for the exercise class on linear and integer programming
 *  held by Stephan Held at the University of Bonn. It is intended for educational purposes only. An efficient 
 *  reader for large LPs would store the matrix in a sparse format (storing only non-zero entries).
 *  The reader assumes that the LP is in the format specified in the exercise sheet. The LP form is max { ct : Ax <= b}.
 *  - The first line contains the number m of rows and n of columns of A.
 *  - The second line contains n floating point numbers specifying c.
 *  - The third line contains m floating point numbers specifying b.
 *  - The next m lines contain the rows of A. Each line contains the n floating point numbers in the respective row.
 */
class LinearProgram {
public:
    size_t m;  // Number of rows (constraints)
    size_t n;  // Number of columns (variables)
    std::vector<std::vector<double>> A;  // Constraint matrix
    std::vector<double> b;  // Right-hand side vector
    std::vector<double> c;  // Objective function coefficients

    /**
     * @brief Default constructor
     */
    LinearProgram() : m(0), n(0) {}

    /**
     * @brief Constructor that reads LP from file
     * @param filename Path to the LP file
     * @throws std::runtime_error if file cannot be opened or parsed
     */
    explicit LinearProgram(const std::string& filename) {
        read_from_file(filename);
    }

    /**
     * @brief Read LP instance from file
     * @param filename Path to the LP file
     * @throws std::runtime_error if file cannot be opened or parsed
     * 
     * File format: First line contains m and n, followed by:
     * - n values for objective function c
     * - m values for right-hand side b
     * - m×n values for constraint matrix A (row by row)
     */
    void read_from_file(const std::string& filename) {
        std::ifstream file(filename);
        
        if (!file.is_open()) {
            throw std::runtime_error("Could not open \"" + filename + "\".");
        }

        // Read dimensions
        if (!(file >> m >> n)) {
            throw std::runtime_error("Failed to read dimensions from file.");
        }

        if (m <= 0 || n <= 0) {
            throw std::runtime_error("Invalid dimensions: m and n must be positive.");
        }

        try {
            // Allocate memory using vectors (automatic memory management)
            c.resize(n);
            b.resize(m);
            A.resize(m, std::vector<double>(n));

            // Read objective function coefficients
            for (size_t j = 0; j < n; ++j) {
                if (!(file >> c[j])) {
                    throw std::runtime_error("Failed to read objective function coefficient.");
                }
            }

            // Read right-hand side vector
            for (size_t i = 0; i < m; ++i) {
                if (!(file >> b[i])) {
                    throw std::runtime_error("Failed to read right-hand side value.");
                }
            }

            // Read constraint matrix
            for (size_t i = 0; i < m; ++i) {
                for (size_t j = 0; j < n; ++j) {
                    if (!(file >> A[i][j])) {
                        throw std::runtime_error("Failed to read matrix element.");
                    }
                }
            }
        } catch (const std::bad_alloc& e) {
            throw std::runtime_error("Memory allocation failure: " + std::string(e.what()));
        }

    }

    /**
     * @brief Print the LP instance to output stream
     * @param os Output stream (default: std::cout)
     */
    void print(std::ostream& os = std::cout) const {
        os << "A has " << m << " row" << (m == 1 ? "" : "s")
           << " and " << n << " column" << (n == 1 ? "" : "s") << ".\n";
          
        os << std::fixed << std::setprecision(3);   

        // Print objective function
        os << "c = [";
        for (size_t j = 0; j < n; ++j) {
            os << c[j];
            if (j < n - 1) os << ", ";
        }
        os << "]\n";

        // Print right-hand side
        os << "transpose(b) = [";
        for (size_t i = 0; i < m; ++i) {
            os << b[i];
            if (i < m - 1) os << ", ";
        }
        os << "]\n";

        // Print constraint matrix
        for (size_t i = 0; i < m; ++i) {
            os << "A[" << i << "] = [";
            for (size_t j = 0; j < n; ++j) {
                os << std::setw(8) <<  A[i][j];
                if (j < n - 1) os << ", ";
            }
            os << "]\n";
        }
    }
};

/**
 * @brief Test function to read and display an LP file
 * @param lp_file Path to the LP file
 */
static void test_it(const std::string& lp_file) {
    try {
        LinearProgram lp(lp_file);
        lp.print();
    } catch (const std::exception& e) {
        std::cerr << "Error parsing \"" << lp_file << "\": " << e.what() << std::endl;
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "Usage:  " << argv[0] << "  <lp file>" << std::endl;
        return EXIT_FAILURE;
    }

    test_it(argv[1]);
    return EXIT_SUCCESS;
}

