/*
   Copyright (C) 2004 - 2016 by Guillaume Melquiond <guillaume.melquiond@inria.fr>
   Part of the Gappa tool https://gappa.gitlabpages.inria.fr/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the CeCILL Free Software License Agreement or
   under the terms of the GNU General Public License version.

   See the COPYING and COPYING.GPL files for more details.
*/

#include <algorithm>
#include <iostream>
#include <list>

#include "numbers/interval_utility.hpp"
#include "numbers/real.hpp"
#include "parser/ast.hpp"
#include "proofs/proof_graph.hpp"
#include "proofs/schemes.hpp"
#include "proofs/updater.hpp"

struct backend;
extern backend *proof_generator;
extern bool parameter_constrained;
extern double parameter_slow_convergence;

graph_t *top_graph = NULL;

/** Timestamp for the currently running graph algorithm. Increased each time an algorithm starts. */
static unsigned visit_counter = 0;

/**
 * Tells if the node has yet to be visited by the current graph algorithm.
 * In that case, the function updates the #visited timestamp so that the next call returns false.
 */
bool node::can_visit() const
{
  if (visited == visit_counter) return false;
  visited = visit_counter;
  return true;
}

typedef std::list< node * > node_list;

/**
 * Creates a node of type @a t. Inserts it in the graph @a g, if any.
 */
node::node(node_id t, graph_t *g, unsigned d)
  : type(t), graph(g), nb_good(0), depth(d), visited(0)
{
  if (g)
    g->insert(this);
}

/**
 * Destroys the node and removes it from its graph #graph.
 * @pre The graph should have no successors nor be referenced by any graph_t::known_reals.
 */
node::~node()
{
  assert(succ.empty() && nb_good == 0);
  if (graph)
    graph->remove(this);
}

/**
 * Called when this node is removed from a graph_t::known_reals.
 * Automatically destroys this node if possible.
 */
void node::remove_known()
{
  assert(nb_good > 0);
  if (--nb_good == 0 && succ.empty()) delete this;
}

/**
 * Removes node @a n from the successors of this node.
 * Automatically destroys this node if possible
 */
void node::remove_succ(node const *n)
{
  succ.erase(const_cast< node * >(n));
  if (succ.empty() && nb_good == 0) delete this;
}

/**
 * Tells if graph @a g is a super-set of this graph.
 * @note It means that @a g has stronger hypotheses than this graph.
 */
bool graph_t::dominates(graph_t const *g) const
{
  while (g)
  {
    if (g == this) return true;
    g = g->father;
  }
  return false;
}

/**
 * Returns the widest property this node can prove without invalidating one of the successors.
 */
property node::maximal() const
{
  property res;
  property const &current = get_result();
  for (node *n: succ)
  {
    property p = n->maximal_for(this);
    if (res.null()) res = p;
    else res.intersect(p);
    assert(current.implies(res));
    if (!current.strict_implies(res)) break;
  }
  return res.null() ? current : res;
}

/**
 * Tells if node @a n1 is owned by a graph dominating the graph of node @a n2.
 * @note It means that @a n2 can rely on the result proven by @a n1
 *       (assuming the proof of @a n1 does not depend on @a n2).
 * @see graph_t::dominates
 */
static bool dominates(node const *n1, node const *n2)
{
  assert(n1 && n1->graph && n2);
  return n1->graph->dominates(n2->graph);
}

theorem_node::theorem_node(int nb, property const h[], property const &p, std::string const &n, proof_scheme const *s)
  : res(p), name(n), sch(s)
{
  for(int i = 0; i < nb; ++i) hyp.push_back(h[i]);
}

/**
 * Adds node @a n as an immediate ancestor to this node.
 * @pre If this node is not a ::UNION node, then node @a n shall dominate it.
 */
void dependent_node::insert_pred(node *n) {
  assert(dominates(n, this) || type == UNION);
  n->succ.insert(this);
  pred.push_back(n);
  if (n->depth >= depth) depth = n->depth + 1;
}

/**
 * Removes all the dependencies of this node.
 * @note As the node is no longer a successor of the nodes it directly relies on, this may cause these nodes to be collected.
 */
void dependent_node::clean_dependencies()
{
  std::sort(pred.begin(), pred.end()); // do not remove a node more than once
  for(node_vect::const_iterator i = pred.begin(), end = std::unique(pred.begin(), pred.end()); i != end; ++i)
    (*i)->remove_succ(this);
  pred.clear();
}

void dependent_node::subst_subproof(node *m, node *n)
{
  bool found = false;
  for (node *&i: pred) {
    if (i == m) { i = n; found = true; }
  }
  assert(found);
  n->succ.insert(this);
  m->remove_succ(this);
}

/**
 * Creates a ::MODUS node. Finds predecessors needed by @a n with ::find_proof.
 */
modus_node::modus_node(theorem_node *n)
  : dependent_node(MODUS), target(n)
{
  assert(n);
  if (!proof_generator && parameter_constrained) return;
  node_set nodes;
  for (property const &p: n->hyp)
  {
    node *m = find_proof(p.real);
    if (!m || !m->get_result().implies(p))
    {
      assert(!parameter_constrained);
      m = create_theorem(0, NULL, p, "$FALSE", NULL);
    }
    assert(m && dominates(m, this));
    if (nodes.insert(m).second)
      insert_pred(m);
  }
}

modus_node::~modus_node()
{
  // axioms are not owned by modus node
  if (!target->name.empty())
    delete target;
}

/**
 * Returns the intersection of all the hypotheses dealing with the same real than the result proved by @a n.
 */
property modus_node::maximal_for(node const *n) const
{
  predicated_real r = n->get_result().real;
  property res;
  for (property const &p: target->hyp) {
    if (r != p.real) continue;
    if (res.null()) res = p;
    else res.intersect(p);
  }
  assert(!res.null());
  return res;
}

void modus_node::enlarge(property const &p)
{
  expand(target, p);
}

/** Helper function for creating both a ::MODUS node and its associated ::theorem_node. */
node *create_theorem(int nb, property const h[], property const &p, std::string const &n, proof_scheme const *s)
{
  return new modus_node(new theorem_node(nb, h, p, n, s));
}

logic_node::logic_node(property_tree const &t, logic_node *n, node *m)
  : node(LOGIC, top_graph, std::max(n->depth, m->depth) + 1), before(n), modifier(m), tree(t)
{
  n->succ.insert(this);
  m->succ.insert(this);
}

logic_node::logic_node(property_tree const &t, logic_node *n, int i)
  : node(LOGIC, top_graph, n->depth + 1), before(n), modifier(NULL), index(i), tree(t)
{
  n->succ.insert(this);
}

logic_node::~logic_node()
{
  if (before) before->remove_succ(this);
  if (modifier) modifier->remove_succ(this);
}

property const &logic_node::get_result() const
{
  assert(tree.empty());
  static property p;
  return p;
}

node_vect const &logic_node::get_subproofs() const
{
  static node_vect res;
  res.clear();
  if (before) res.push_back(before);
  if (modifier) res.push_back(modifier);
  return res;
}

logicp_node::logicp_node(property const &p, logic_node *n, int i)
  : node(LOGICP, top_graph, n->depth + 1), before(n), res(p), index(i)
{
  n->succ.insert(this);
}

logicp_node::~logicp_node()
{
  before->remove_succ(this);
}

node_vect const &logicp_node::get_subproofs() const
{
  static node_vect res;
  res.clear();
  res.push_back(before);
  return res;
}

class intersection_node: public dependent_node
{
  property res;
 public:
  intersection_node(node *n1, node *n2);
  virtual property const &get_result() const { return res; }
  virtual property maximal() const { return res.null() ? res : node::maximal(); }
  virtual property maximal_for(node const *) const;
  virtual void enlarge(property const &p) { res = boundify(p, res); }
};

static void get_inner_intersection_node(node *&n, int i)
{
  if (n->type != INTERSECTION) return;
  node *m = n->get_subproofs()[i];
  if (n->get_result().real != m->get_result().real) return;
  n = m;
}

/**
 * Creates a node proving a property that is an intersection between the results of two nodes @a n1 and @a n2.
 *
 * Calls graph_t::set_contradiction if the new node proves an empty result.
 *
 * @note Predecessors are ordered so that the first one is "less than" the second one.
 */
intersection_node::intersection_node(node *n1, node *n2)
  : dependent_node(INTERSECTION)
{
  assert(dominates(n1, this) && dominates(n2, this));
  property const &res1 = n1->get_result(), &res2 = n2->get_result();
  assert(res1.real == res2.real && res1.real.pred_bnd());
  res = res1;
  res.intersect(res2);
  if (lower(res1.bnd()) > lower(res2.bnd())) std::swap(n1, n2);
  // to simplify the graph, no intersection should be nested
  get_inner_intersection_node(n1, 0);
  get_inner_intersection_node(n2, 1);
  insert_pred(n1);
  insert_pred(n2);
  if (is_empty(res.bnd()))
  {
    if (res1.real.pred() == PRED_REL || res1.real.pred() == PRED_LIN)
    {
      // "always 0" is not a contradiction, so state that the real is "0" instead
      res = property(res1.real.real2(), interval(0, 0));
      return;
    }
    res = property();
    top_graph->set_contradiction(this);
  }
}

/**
 * Extends the interval of the left predecessor towards minus infinity and the
 * interval of the right predecessor towards plus infinity.
 */
property intersection_node::maximal_for(node const *n) const
{
  node_vect const &v = get_subproofs();
  number l = number::neg_inf, u = number::pos_inf;
  predicated_real const &r = n->get_result().real;
  if (res.null() || res.real != r)
  {
    // TODO: improve bounds
    if (n == v[0]) u = upper(v[0]->get_result().bnd());
    else l = lower(v[1]->get_result().bnd());
  }
  else
  {
    if (n == v[0])
      u = upper(res.bnd());
    else
      l = lower(res.bnd());
  }
  return property(r, interval(l, u));
}

/**
 * Creates a new graph inheriting from the parent graph @a f.
 * \li Marks all the parent nodes in #known_reals and #trees as known in this new graph too.
 * \li Adds a ::LOGIC nodes for @a h.
 */
graph_t::graph_t(graph_t *f, property_tree const &h)
  : father(f), hyps(h), contradiction(NULL)
{
  graph_loader loader(this);
  if (f)
  {
    assert(!f->contradiction);
    known_reals = f->known_reals;
    for (auto const &i: known_reals) {
      ++i.second->nb_good;
    }
    for (logic_node *n: f->trees) {
      ++n->nb_good;
      trees.push_back(n);
    }
  }
  logic_node *n = new logic_node(h);
  ++n->nb_good;
  trees.push_back(n);
}

/**
 * Empties #known_reals and #trees.
 */
void graph_t::purge()
{
  for (auto const &i: known_reals) {
    i.second->remove_known();
  }
  known_reals.clear();
  for (logic_node *n: trees) {
    n->remove_known();
  }
  trees.clear();
}

/**
 * Sets @a n in #contradiction and increases its node::nb_good reference count. Purges the graph.
 */
void graph_t::set_contradiction(node *n)
{
  assert(n && !contradiction);
  contradiction = n;
  ++n->nb_good;
  purge();
}

int stat_successful_app = 0, stat_discarded_pred = 0, stat_intersected_pred = 0;

/**
 * Checks whether property @a p is worth it:
 *
 * \li the real of its result is not yet known, or
 * \li its result is not a superset of an already known result.
 *
 * @return 2 if the property is worth it, 1 if it is hardly an improvement, 0 if it is useless.
 */
int graph_t::try_property(property const &p, unsigned depth, bool check_slow) const
{
  assert(top_graph == this && !contradiction);
  ++stat_successful_app;
  node_map::const_iterator i = known_reals.find(p.real);
  if (i == known_reals.end()) return 2;
  node const *n = i->second;
  property const &old = n->get_result();
  if (old.implies(p)) {
    if (depth < i->second->depth && top_graph == n->graph && !old.strict_implies(p)) return 1;
    ++stat_discarded_pred;
    return 0;
  }
  if (!p.real.pred_bnd()) return 2;
  if (depth <= i->second->depth && top_graph == n->graph && p.strict_implies(old)) return 1;
  property r = old;
  r.intersect(p);
  interval const &bold = old.bnd(), &bnew = r.bnd();
  if (is_empty(bnew)) {
    if (p.real.pred() != PRED_REL && p.real.pred() != PRED_LIN) return 2;
    return try_property(property(p.real.real2(), interval(0, 0)), depth + 1, false);
  }
  if (!check_slow || parameter_slow_convergence >= 1) return 2;
  double f = parameter_slow_convergence,
    dlnew = lower(bnew).to_double(), dunew = upper(bnew).to_double(),
    dlold = lower(bold).to_double(), duold = upper(bold).to_double();
  if (dunew - dlnew < (duold - dlold) * f) return 2;
  if (dlold < 0 && dlnew > dlold * f) return 2;
  if (dlnew > 0 && dlnew * f > dlold) return 2;
  if (dunew < 0 && dunew * f < duold) return 2;
  if (duold > 0 && dunew < duold * f) return 2;
  return 1;
}

/**
 * Stores @a n and updates it if an additional node was created.
 *
 * An ::intersection_node is created if there was already a node in #known_reals.
 *
 * @pre the node should be worth it.
 * @see #try_property.
 */
void graph_t::insert_node(node *&n)
{
  assert(top_graph == this && !contradiction);
  assert(n && n->graph && n->graph->dominates(this));
  property const &res2 = n->get_result();
  std::pair< node_map::iterator, bool > ib = known_reals.insert(std::make_pair(res2.real, n));
  node *&dst = ib.first->second;
  if (!ib.second)
  {
    // there was already a known range
    node *old = dst;
    property const &res1 = old->get_result();
    if (res1.implies(res2)) {
      // the old range was just as good, but the new node is shallower
      assert(old->depth > n->depth && !res1.strict_implies(res2));
    } else if (!res2.strict_implies(res1)) {
      ++stat_intersected_pred;
      n = new intersection_node(old, n);
      if (n == contradiction) return;
      if (n->get_result().real != res2.real) { insert_node(n); return; }
    }
    dst = n;
    old->remove_known();
  }
  ++n->nb_good;
}

/**
 * Inserts node @a n if it is worth it.
 *
 * @see #try_property and #insert_node.
 */
void graph_t::try_node(node *n)
{
  if (try_property(n->get_result(), n->depth)) insert_node(n);
  else delete n;
}

/**
 * Returns the best node proving a result on real @a real.
 */
node *graph_t::find_already_known(predicated_real const &real) const
{
  node_map::const_iterator i = known_reals.find(real);
  return i != known_reals.end() ? i->second : NULL;
}

/**
 * Deletes the #contradiction node if any, otherwise purges the graph.
 * No nodes should remain in the graph after these deletions.
 */
graph_t::~graph_t()
{
  if (contradiction)
  {
    --contradiction->nb_good;
    delete contradiction;
  }
  else
    purge();
  assert(nodes.empty());
}

/**
 * Replaces the known reals by the nodes from @a v which are usually weaker and a subset of #known_reals.
 * @note This function is meant to be called once the graph is complete, in order to retain only the nodes useful to prove the user proposition.
 */
void graph_t::replace_known(node_vect const &v)
{
  node_map old;
  old.swap(known_reals);
  for (node *n: v) {
    ++n->nb_good;
    known_reals.insert(std::make_pair(n->get_result().real, n));
  }
  for (auto const &i: old)
    i.second->remove_known();
}

/**
 * Displays the nodes with assumed results in unconstrained mode.
 * Assumed results are associated to a special theorem with the name "$FALSE".
 */
void graph_t::show_dangling() const
{
  bool first = true;
  for (node *n: nodes)
  {
    if (n->type == MODUS && static_cast< modus_node * >(n)->target->name == "$FALSE")
    {
      if (first)
      {
        first = false;
        std::cerr << "Warning: some properties were assumed:\n";
      }
      std::cerr << "  " << dump_property(n->get_result()) << '\n';
    }
  }
}

typedef std::set<predicated_real> preal_set;

static void find_negative(preal_set &ps, property_tree const &t)
{
  if (t.left) {
    find_negative(ps, *t.left);
    find_negative(ps, *t.right);
    return;
  }
  if (t.conjunction) return;
  ps.insert(t.atom->real);
}

/**
 * Displays the negative tree properties and the currently known properties.
 */
void graph_t::show_negative() const
{
  preal_set ps;
  for (logic_node *n: trees) {
    find_negative(ps, n->tree);
  }
  if (ps.empty()) {
    std::cerr << "Error: no contradiction was found.\n";
    return;
  }
  std::cerr << "Error: some properties were not satisfied:\n";
  for (predicated_real const &p: ps)
  {
    std::cerr << "  " << dump_real(p);
    if (node *n = find_already_known(p)) {
      std::cerr << ", best: ";
      if (p.pred_bnd()) std::cerr << n->get_result().bnd();
      else std::cerr << n->get_result().cst();
    }
    std::cerr << '\n';
  }
}

bool graph_t::get_undefined(undefined_map &umap) const
{
  size_t nb = umap.size();
  hyps.get_undefined(umap);
  return nb != umap.size();
}

/**
 * Scans the nodes of the graph from goals to hypotheses and tries to weaken results.
 */
void enlarger(node *top)
{
  // perform a topological sort of the nodes
  ++visit_counter;
  typedef std::pair<bool, node *> bnode;
  std::list<bnode> bns;
  node_list pending;
  bns.push_back(std::make_pair(false, top));
  while (!bns.empty())
  {
    bnode &bn = bns.back();
    if (bn.first)
    {
      pending.push_front(bn.second);
      bns.pop_back();
      continue;
    }
    if (!bn.second->can_visit())
    {
      bns.pop_back();
      continue;
    }
    bn.first = true;
    for (node *n: bn.second->get_subproofs()) {
      bns.push_back(std::make_pair(false, n));
    }
  }
  // compute which nodes can be used as a replacement
  std::set<graph_t *> gs;
  std::multimap<graph_t *, graph_t *> ggs;
  for (node *n: pending)
  {
    graph_t *g = n->graph;
    if (!gs.insert(g).second) continue;
    // the nodes of k can be used by the nodes of g
    for (graph_t *k = g; k; k = k->father)
      ggs.insert(std::make_pair(k, g));
  }
  std::map<graph_t *, node_list> gnls;
  for (node *n: pending)
  {
    typedef std::multimap<graph_t *, graph_t *>::const_iterator iter;
    std::pair<iter, iter> jj = ggs.equal_range(n->graph);
    // the nodes of *j can use node n
    for (iter j = jj.first; j != jj.second; ++j)
      gnls[j->second].push_front(n);
  }
  // enlarge node results and see if some of them can be replaced
  node_list replaced;
  while (!pending.empty())
  {
    node *n = pending.front();
    pending.pop_front();
    if (n->type == LOGIC || n->type == LOGICP) continue;
    n->enlarge(n->maximal());
    for (node *m: n->get_subproofs())
    {
      property p = n->maximal_for(m);
      if (!m->get_result().strict_implies(p)) continue;
      node_list const &ns = gnls[m->graph];
      for (node_list::const_iterator j = ns.begin(),
           j_end = ns.end(); *j != m; ++j)
      {
        assert (j != j_end);
        node *k = *j;
        // TODO: replace n entirely by k in that case, as it implies that k dominates n
        if (n->type == UNION && k->graph->father != n->graph) continue;
        if (k->type == LOGIC) continue;
        if (!k->get_result().implies(p)) continue;
        ++m->nb_good;
        n->subst_subproof(m, k);
        replaced.push_back(m);
        break;
      }
    }
  }
  for (node *n: replaced) {
    n->remove_known();
  }
}
