(**************************************************************************)
(*                                                                        *)
(*  This file is part of WP plug-in of Frama-C.                           *)
(*                                                                        *)
(*  Copyright (C) 2007-2020                                               *)
(*    CEA (Commissariat a l'energie atomique et aux energies              *)
(*         alternatives)                                                  *)
(*                                                                        *)
(*  you can redistribute it and/or modify it under the terms of the GNU   *)
(*  Lesser General Public License as published by the Free Software       *)
(*  Foundation, version 2.1.                                              *)
(*                                                                        *)
(*  It 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 Lesser General Public License for more details.                   *)
(*                                                                        *)
(*  See the GNU Lesser General Public License version 2.1                 *)
(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
(*                                                                        *)
(**************************************************************************)

(* -------------------------------------------------------------------------- *)
(* --- Built-in Tactics                                                   --- *)
(* -------------------------------------------------------------------------- *)

open Lang
open Lang.F

let (^) a b =
  if a="" then b else
  if b="" then a else
    Printf.sprintf "%s ; %s" a b

let t_id s = ["",s]
let t_absurd s = [ "Absurd" , (fst s,p_false) ]
let t_descr d0 p s = List.map (fun (d,s) -> (d0 ^ d) , s) (p s)

let t_finally d0 s = [ d0 , s ]

let t_chain (p : Tactical.process) (q : Tactical.process) s =
  let pool = ref [] in
  List.iter
    (fun (d,s) ->
       List.iter
         (fun (d',s') ->
            pool := (d ^ d' , s') :: !pool
         ) (q s)
    ) (p s) ;
  List.rev !pool

let t_split ?(pos="") ?(neg="") p (hs,g) =
  [ pos , (hs,p_imply p g) ; neg , (hs,p_imply (p_not p) g) ]

let t_cut ?(by="") (p : F.pred) (pi : Tactical.process) (hs,g) =
  ( by , (hs,p) ) :: (pi (hs,p_imply p g))

let t_case (p : F.pred) (a : Tactical.process) (b : Tactical.process) =
  fun (hs,g) ->
  List.append
    (a (hs,F.p_imply p g))
    (b (hs,F.p_imply (F.p_not p) g))

let t_cases ?(complete = "complete") (dps : (pred * Tactical.process) list) =
  fun (hs,g) ->
  let pool = ref [] in
  List.iter
    (fun (p,pi) ->
       List.iter
         (fun u -> pool := u :: !pool)
         (pi (hs , p_imply p g))
    ) dps ;
  ( complete , (hs , p_any fst dps) ) :: List.rev !pool

let t_range e a b ~upper ~lower ~range s =
  if (not (a <= b)) then raise (Invalid_argument "Wp.Auto.t_range") ;
  let cases = ref [] in
  for i = a to b do
    cases := (Printf.sprintf "Value %d" i , p_equal e (e_int i)) :: !cases ;
  done ;
  List.concat [
    upper (fst s , p_lt e (e_int a)) ;
    lower (fst s , p_lt (e_int b) e) ;
    t_chain (Tactical.insert !cases) range s ;
  ]

let t_replace ?(equal="equal") ~src ~tgt (pi : Tactical.process) s =
  let s' =
    Conditions.subst
      (fun e -> if e == src then tgt else raise Not_found)
      s
  in
  (equal , (fst s, p_equal src tgt)) :: (pi s')

(* -------------------------------------------------------------------------- *)
(* --- Built-in Strategies                                                --- *)
(* -------------------------------------------------------------------------- *)

let array = TacArray.strategy
let choice = TacChoice.Choice.strategy
let absurd = TacChoice.Absurd.strategy
let contrapose = TacChoice.Contrapose.strategy
let compound = TacCompound.strategy
let cut = TacCut.strategy
let filter = TacFilter.strategy
let havoc = TacHavoc.Havoc.strategy
let separated = TacHavoc.Separated.strategy
let intuition = TacNormalForm.strategy
let range = TacRange.strategy
let split = TacSplit.strategy
let definition = TacUnfold.strategy
let instance = TacInstance.strategy
let lemma = TacLemma.strategy

(* -------------------------------------------------------------------------- *)
(* --- Auto-Range                                                         --- *)
(* -------------------------------------------------------------------------- *)

module Range =
struct

  open Repr

  let update merge x v ofs map =
    match Repr.term v with
    | Int v ->
        let v0 = Integer.add v ofs in
        let v1 = try merge v0 (Tmap.find x map) with Not_found -> v0 in
        Tmap.add x v1 map
    | _ -> map

  type rg = {
    mutable vmin : Integer.t F.Tmap.t ;
    mutable vmax : Integer.t F.Tmap.t ;
  }

  let set_vmin rg x v ofs =
    rg.vmin <- update Integer.max x v ofs rg.vmin

  let set_vmax rg x v ofs =
    rg.vmax <- update Integer.min x v ofs rg.vmax

  let rec add_bound rg p =
    match Repr.term p with
    | And ps -> List.iter (add_bound rg) ps
    | Lt(a,b) when Lang.F.is_int a && Lang.F.is_int b ->
        set_vmax rg a b Integer.minus_one ;
        set_vmin rg b a Integer.one ;
    | Leq(a,b) when Lang.F.is_int a && Lang.F.is_int b ->
        set_vmax rg a b Integer.zero ;
        set_vmin rg b a Integer.zero ;
    | _ -> ()

  let compute hs =
    let rg = { vmin = F.Tmap.empty ; vmax = F.Tmap.empty } in
    Conditions.iter
      (fun s ->
         let open Conditions in
         match s.condition with
         | Have p | When p | Core p -> add_bound rg (F.e_prop p)
         | _ -> ())
      hs ;
    rg

  let ranges rg =
    Tmap.interf
      (fun _ a b ->
         try Some(Integer.to_int a,Integer.to_int b)
         with Z.Overflow -> None
      ) rg.vmin rg.vmax

  let small = function
    | None -> None
    | Some z -> try Some(Integer.to_int z) with Z.Overflow -> None

  let bounds rg =
    Tmap.merge
      (fun _ a b -> Some(small a,small b))
      rg.vmin rg.vmax

end

(* -------------------------------------------------------------------------- *)
(* --- Heuristics: Auto-Range                                             --- *)
(* -------------------------------------------------------------------------- *)

class autorange =
  object
    method id = "wp:range"
    method title = "Auto Range"
    method descr = "Iterate over term constrained by a finite interval"

    method search push (hyps,goal) =
      let ranged = Range.ranges (Range.compute hyps) in
      Tmap.iter
        (fun e (a,b) ->
           if Strategy.occurs_p e goal && b-a <= 1024 then
             let selection = Tactical.(Inside(Goal goal,e)) in
             push (range selection ~vmin:a ~vmax:b)
        ) ranged
  end

let auto_range = Strategy.export (new autorange)

(* -------------------------------------------------------------------------- *)
(* --- Heuristics: Auto-Split                                             --- *)
(* -------------------------------------------------------------------------- *)

class autosplit =
  object(self)
    method id = "wp:split"
    method title = "Auto Split"
    method descr = "Split on goal or any branch (priority to goal variables)"

    method private search_goal push seq =
      let goal = snd seq in
      let is_split =
        let open Qed.Logic in
        match F.e_expr goal with
        | And _ | If _ -> true
        | Bind (Exists,_,phi) -> let rec is_split = function
            | Bind (Exists,_,phi) -> is_split (F.repr (F.QED.lc_repr phi))
            | And _ | Or _ | If _ | Imply _ -> true
            | _ -> false
            in is_split (F.repr (F.QED.lc_repr phi))
        | Neq(x,y) | Eq(x,y) -> (F.is_prop x) && (F.is_prop y)
        | _ -> false
      in
      if is_split then
        let selection = Tactical.(Clause (Goal goal)) in
        push (split ~priority:2.0 selection)

    method private search_branch push seq =
      let target = Lang.F.varsp (snd seq) in
      Conditions.iter
        (fun s ->
           let open Lang in
           let open Conditions in
           match s.condition with
           | Branch(_,sa,sb) ->
               let priority =
                 if F.Vars.intersect target (Conditions.vars_hyp sa) ||
                    F.Vars.intersect target (Conditions.vars_hyp sb)
                 then 1.0 else 0.5 in
               let selection = Tactical.(Clause(Step s)) in
               push (split ~priority selection)
           | _ -> ()
        ) (fst seq)

    method search push seq =
      self#search_goal push seq ;
      self#search_branch push seq

  end

let auto_split = Strategy.export (new autosplit)

(**************************************************************************)
