// Copyright (c) 2016, 2017 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 {DVector, Real};

use cplex_sys as cpx;

use std::ptr;
use std::ffi::CString;
use std::result::Result;

use std::os::raw::{c_char, c_double, c_int};

use failure::{err_msg, Error};

pub struct Solver {
    net: *mut cpx::Net,
    logfile: *mut cpx::File,
}


impl Drop for Solver {
    fn drop(&mut self) {
        unsafe {
            cpx::NETfreeprob(cpx::env(), &mut self.net);
            cpx::fclose(self.logfile);
        }
    }
}

impl Solver {
    pub fn new(nnodes: usize) -> Result<Solver, Error> {
        let mut status: c_int;
        let mut net = ptr::null_mut();
        let logfile;

        unsafe {
            #[cfg_attr(feature = "cargo-clippy", allow(never_loop))]
            loop {
                logfile = cpx::fopen(
                    const_cstr!("mcf.cpxlog").as_ptr(),
                    const_cstr!("w").as_ptr(),
                );
                if logfile.is_null() {
                    return Err(err_msg("Can't open log-file"));
                }
                status = cpx::setlogfile(cpx::env(), logfile);
                if status != 0 {
                    break;
                }

                net = cpx::NETcreateprob(cpx::env(), &mut status, const_cstr!("mcf").as_ptr());
                if status != 0 {
                    break;
                }
                status = cpx::NETaddnodes(cpx::env(), net, nnodes as c_int, ptr::null(), ptr::null());
                if status != 0 {
                    break;
                }
                status = cpx::NETchgobjsen(cpx::env(), net, cpx::ObjectiveSense::Minimize.to_c());
                if status != 0 {
                    break;
                }
                break;
            }

            if status != 0 {
                let msg = CString::new(vec![0; cpx::MESSAGE_BUF_SIZE])
                    .unwrap()
                    .into_raw();
                cpx::geterrorstring(cpx::env(), status, msg);
                cpx::NETfreeprob(cpx::env(), &mut net);
                cpx::fclose(logfile);
                return Err(
                    cpx::CplexError {
                        code: status,
                        msg: CString::from_raw(msg).to_string_lossy().into_owned(),
                    }.into(),
                );
            }
        }

        Ok(Solver {
            net: net,
            logfile: logfile,
        })
    }

    pub fn num_nodes(&self) -> usize {
        unsafe { cpx::NETgetnumnodes(cpx::env(), self.net) as usize }
    }

    pub fn num_arcs(&self) -> usize {
        unsafe { cpx::NETgetnumarcs(cpx::env(), self.net) as usize }
    }

    pub fn set_balance(&mut self, node: usize, supply: Real) -> Result<(), Error> {
        let n = node as c_int;
        let s = supply as c_double;
        Ok(trycpx!(cpx::NETchgsupply(
            cpx::env(),
            self.net,
            1,
            &n,
            &s as *const c_double
        )))
    }

    pub fn set_objective(&mut self, obj: &DVector) -> Result<(), Error> {
        let inds = (0..obj.len() as c_int).collect::<Vec<_>>();
        Ok(trycpx!(cpx::NETchgobj(
            cpx::env(),
            self.net,
            obj.len() as c_int,
            inds.as_ptr(),
            obj.as_ptr()
        )))
    }

    pub fn add_arc(&mut self, src: usize, snk: usize, cost: Real, cap: Real) -> Result<(), Error> {
        let f = src as c_int;
        let t = snk as c_int;
        let c = cost as c_double;
        let u = cap as c_double;
        let name = CString::new(format!("x{}#{}_{}", self.num_arcs() + 1, f + 1, t + 1)).unwrap();
        let cname = name.as_ptr();
        Ok(trycpx!(cpx::NETaddarcs(
            cpx::env(),
            self.net,
            1,
            &f,
            &t,
            ptr::null(),
            &u,
            &c,
            &cname as *const *const c_char
        )))
    }

    pub fn solve(&mut self) -> Result<(), Error> {
        Ok(trycpx!(cpx::NETprimopt(cpx::env(), self.net)))
    }

    pub fn objective(&self) -> Result<Real, Error> {
        let mut objval: c_double = 0.0;
        trycpx!(cpx::NETgetobjval(
            cpx::env(),
            self.net,
            &mut objval as *mut c_double
        ));
        Ok(objval)
    }

    pub fn get_solution(&self) -> Result<DVector, Error> {
        let mut sol = dvec![0.0; self.num_arcs()];
        let mut stat: c_int = 0;
        let mut objval: c_double = 0.0;
        trycpx!(cpx::NETsolution(
            cpx::env(),
            self.net,
            &mut stat as *mut c_int,
            &mut objval as *mut c_double,
            sol.as_mut_ptr(),
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut()
        ));
        Ok(sol)
    }

    pub fn writelp(&self, filename: &str) -> Result<(), Error> {
        let fname = CString::new(filename).unwrap();
        Ok(trycpx!(cpx::NETwriteprob(
            cpx::env(),
            self.net,
            fname.as_ptr(),
            ptr::null_mut()
        )))
    }
}
