/*
 * Copyright (c) 2016, 2017, 2018, 2019 Frank Fischer <frank-fischer@shadow-soft.de>
 *
 * 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/>
 */

use env_logger;
use env_logger::fmt::Color;
use log::{info, Level};
use rustop::opts;
use std::io::Write;

use bundle::master::{Builder, FullMasterBuilder, MasterProblem, MinimalMasterBuilder};
use bundle::mcf::MMCFProblem;
use bundle::solver::sync::Solver;
use bundle::{terminator::StandardTerminator, weighter::HKWeighter};

use std::error::Error;
use std::result::Result;

fn solve_parallel<M>(master: M, mmcf: MMCFProblem) -> Result<(), Box<dyn Error>>
where
    M: Builder,
    M::MasterProblem: MasterProblem<MinorantIndex = usize>,
{
    let mut slv = Solver::<_, StandardTerminator, HKWeighter, M>::with_master(mmcf, master);
    slv.weighter.set_weight_bounds(1e-1, 100.0);
    slv.terminator.termination_precision = 1e-6;
    slv.solve()?;

    let costs: f64 = (0..slv.problem().num_subproblems())
        .map(|i| {
            let aggr_primals = slv.aggregated_primal(i).unwrap();
            slv.problem().get_primal_costs(i, &[aggr_primals])
        })
        .sum();
    info!("Primal costs: {}", costs);
    Ok(())
}

fn main() -> Result<(), Box<dyn Error>> {
    better_panic::install();
    env_logger::builder()
        .format(|buf, record| {
            let mut style = buf.style();
            let color = match record.level() {
                Level::Error | Level::Warn => Color::Red,
                Level::Trace => Color::Blue,
                Level::Debug => Color::Yellow,
                _ => Color::White,
            };
            style.set_color(color);
            writeln!(
                buf,
                "{}{:5}{} {}",
                style.value("["),
                style.value(record.level()),
                style.value("]"),
                style.value(record.args())
            )
        })
        .init();

    let (args, _) = opts! {
        synopsis "Solve MMCF problems using a bundle method.";
        opt separate:bool, desc:"Separate capacity constraints";
        opt minimal:bool, desc:"Use the minimal master model";
        opt aggregated:bool, desc:"Use aggregated model";
        opt bundle_size:usize = 0, name:"NUM", desc:"The bundle size";
        param file:String, desc:"Input file in MNetGen format";
    }
    .parse_or_exit();

    let filename = args.file;
    info!("Reading instance: {}", filename);

    if !args.minimal {
        let mut mmcf = MMCFProblem::read_mnetgen(&filename)?;
        mmcf.set_separate_constraints(args.separate);
        mmcf.multimodel = true;

        let mut master = FullMasterBuilder::default();
        if args.aggregated {
            master.max_bundle_size(if args.bundle_size <= 1 { 50 } else { args.bundle_size });
            master.use_full_aggregation();
        } else {
            master.max_bundle_size(if args.bundle_size <= 1 { 5 } else { args.bundle_size });
        }
        solve_parallel(master, mmcf)?;
    } else {
        let mut mmcf = MMCFProblem::read_mnetgen(&filename)?;
        mmcf.set_separate_constraints(args.separate);
        mmcf.multimodel = true;

        let master = MinimalMasterBuilder::default();
        solve_parallel(master, mmcf)?;
    }

    Ok(())
}
