package pl.edu.pw.mini.jk.sg.opt.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.HashMap;

import java.util.zip.GZIPInputStream;

import com.google.common.collect.ImmutableList;
import java.util.stream.IntStream;

public final class ScipUtil {

    public static Reader getReaderByExtension(final File file) throws IOException {
	if(file.getPath().endsWith(".gz")) {
	    return new InputStreamReader(new GZIPInputStream(new FileInputStream(file)));
	} else {
	    return new FileReader(file);
	}
    }
    
    public static Map<String, Double> readSolutionColumnValues(final File solutionFile) throws IOException {
        try (final Reader reader = getReaderByExtension(solutionFile);
                final BufferedReader bufferedReader = new BufferedReader(reader);) {
            final String firstLine = bufferedReader.readLine();
            if (!"solution status: optimal solution found".equals(firstLine) && !firstLine.startsWith("# Solution for model") ) {
                throw new IOException("A file with optimal solution expected");
            }
            bufferedReader.readLine(); // skip objective function value
            return bufferedReader.lines().map(line -> {
                Scanner scanner = new Scanner(line);
                final String name = scanner.next();
                final double value = scanner.nextDouble();
                scanner.close();
                return new ColumnValue(name, value);
            }).filter(c -> c.getValue()!=0).collect(Collectors.toMap(c -> c.getName(), c -> c.getValue()));
        }
    }

    public static Map<Integer, Double> defenderCodeProbabilities(Map<String, Double> optVars) {
        final Pattern zvar = Pattern.compile("z[_#][0-9]+[_#][0-9]+");
        return optVars
                .entrySet()
                .stream()
                .filter(e -> zvar.matcher(e.getKey()).matches())
                .collect(
                        Collectors.toMap(k -> Integer.valueOf(k.getKey().split("[_#]")[1]), k -> k.getValue(), (i, j) -> i
                                + j));
    }
    

    public static Map<ImmutableList<Integer>, Double> defenderDecodeMoves(Map<Integer, Double> codedMoves,
            Map<Integer, ImmutableList<Integer>> codeMap) {
        return codedMoves.keySet().stream().collect(Collectors.toMap(codeMap::get, codedMoves::get));
    }

    public static void printColumnLimits(final PrintStream writer, final String columnName,
            final Map<String, Double> coefficients) {
        int counter = 0;
    
        for (final Entry<String, Double> cell : coefficients.entrySet()) {
            if (counter % 2 == 0) {
                writer.format(" %-13s%-10s%14f ", columnName, cell.getKey(), cell.getValue());
            } else {
                writer.format("%-10s%15f\n", cell.getKey(), cell.getValue());
            }
            counter++;
        }
        if (counter % 2 == 1)
            writer.println();
    }


    public static void printOptimizationProblem(PrintStream writer, String problemName,
						Map<Integer, Double> defenderMoveProbability, double[][] attackerPayoffs) {
	final int defenderMoves = attackerPayoffs[0].length;
	final int attackerMoves = attackerPayoffs.length;
	final double eps = 0.001;

	writer.format("%-15s%s\n", "NAME", problemName);
	writer.println("ROWS");
	writer.println(" N Obj");
	writer.println(" E Probsum");
	writer.println("COLUMNS");
	for (int i = 0; i < attackerMoves; i++) {
	    final int ii = i;
	    final String name = "a_" + i;
	    final Map<String, Double> coefficients = new HashMap<>();
	    double c = IntStream.range(0, defenderMoves)
		.mapToDouble(j -> -attackerPayoffs[ii][j] * defenderMoveProbability.getOrDefault(j, 0.)).sum();
	    coefficients.put("Obj", c);
	    coefficients.put("Probsum", 1.);
	    ScipUtil.printColumnLimits(writer, name, coefficients);
	}

	writer.println("RHS");
	writer.format("    RHS       %-15s%10f\n", "Probsum", 1.);
	writer.println("BOUNDS");
	for (int i = 0; i < attackerMoves; i++) {
	    final String columnName = "a_" + i;
	    writer.format(" UP Bound     %-15s%10f\n", columnName, 1.);
	    writer.format(" LO Bound     %-15s%10f\n", columnName, 0.);
	}
	writer.format("QUADOBJ\n");
	for (int i = 0; i < attackerMoves; i++) {
	    writer.format("   %-10s%-15s%10f\n", "a_" + i, "a_" + i, eps);
	}
	writer.format("ENDATA\n");

    }

    
    public static void writeOptimizationProblem(File file, Map<Integer, Double> defenderMoveProbability,
						double[][] attackerPayoffs) throws FileNotFoundException, IOException {
	try (FileOutputStream fs = new FileOutputStream(file); PrintStream ps = new PrintStream(fs);) {
	    printOptimizationProblem(ps, file.getName(), defenderMoveProbability, attackerPayoffs);
	}
    }

    
}
