(*  File: deco_impl.ml

    AIFAD - Automated Induction of Functions over Algebraic Datatypes

    Author: Markus Mottl
    email:  markus.mottl@gmail.com
    WWW:    http://www.ocaml.info

    Copyright (C) 2002  Austrian Research Institute for Artificial Intelligence
    Copyright (C) 2003- Markus Mottl

    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 2 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, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*)

(* $Id: deco_impl.ml,v 1.23 2006/01/17 00:23:37 mottl Exp $ *)

open Utils
open Algdt_types
open Algdt_utils
open Model_types
open Deco_intf

module VarSet = IntSet
module VarMap = IntMap

let coll_sub_map ix_cnt ix m sub_tp = VarMap.add (ix_cnt + ix) sub_tp m

let calc_sub_vars sums cnstr ix_cnt =
  let rec loop sub_vars var_ix =
    if var_ix < 0 then sub_vars
    else loop (VarSet.add (var_ix + ix_cnt) sub_vars) (var_ix - 1) in
  loop VarSet.empty (Array.length sums.(cnstr) - 1)


module Make (Spec : SPEC) = struct

open Spec

let failwith str = failwith ("Deco_impl." ^ str)

let deco_dsum_of_fdsum, decorate_sums = make_deco_dsum_cnv cfspec cispec


(* Decorate variable models *)

let rec deco_var_mods var_tp_lst prod_el var_mods =
  if Array.length var_mods = 1 then
    match prod_el with
    | TpVal tp ->
        let dvar_mod, new_var_tp_lst =
          deco_var_mod var_tp_lst tp var_mods.(0) in
        TpVal dvar_mod, new_var_tp_lst
    | ProdEls _ -> failwith "deco_var_mods: expected TpVal"
  else deco_var_mods_prod var_tp_lst var_mods prod_el

and deco_var_mod var_tp_lst tp = function
  | VarFree fdsum -> DVarFree (deco_dsum_of_fdsum tp fdsum), var_tp_lst
  | Var -> DVar tp, tp :: var_tp_lst
  | VarStrct (cnstr, var_mods) ->
      let dvar_mod, var_tp_lst =
        match cispec.(tp) with
        | Prod _ -> failwith "deco_var_mod: expected Sums"
        | Sums sums ->
            match sums.(cnstr) with
            | IAtom -> failwith "deco_var_mod: expected IStrct"
            | IStrct prod_el -> deco_var_mods var_tp_lst prod_el var_mods in
      DVarStrct (tp, cnstr, dvar_mod), var_tp_lst

and deco_var_mods_prod var_tp_lst var_mods = function
  | ProdEls prod_els ->
      let dprod_els, new_var_tp_lst, _ =
        deco_vprod_els var_tp_lst prod_els 0 var_mods in
      ProdEls dprod_els, new_var_tp_lst
  | TpVal tp ->
      match cispec.(tp) with
      | Sums _ -> failwith "deco_var_mods_prod: expected Prod"
      | Prod prod_el -> deco_var_mods_prod var_tp_lst var_mods prod_el

and deco_vprod_els var_tp_lst prod_els ix var_mods =
  let dprod_el, new_var_tp_lst, new_ix =
    deco_vprod_el var_tp_lst ix var_mods prod_els.(0) in
  let dprod_els = Array.make (Array.length prod_els) dprod_el in
  let colli i (var_tp_lst, ix) prod_el =
    let dprod_el, new_var_tp_lst, new_ix =
      deco_vprod_el var_tp_lst ix var_mods prod_el in
    dprod_els.(i) <- dprod_el;
    new_var_tp_lst, new_ix in
  let new_var_tp_lst, new_ix =
    array_fold_left1i colli (new_var_tp_lst, new_ix) prod_els in
  dprod_els, new_var_tp_lst, new_ix

and deco_vprod_el var_tp_lst ix var_mods = function
  | ProdEls prod_els ->
      let dprod_els, new_var_tp_lst, new_ix =
        deco_vprod_els var_tp_lst prod_els ix var_mods in
      ProdEls dprod_els, new_var_tp_lst, new_ix
  | TpVal tp ->
      match cispec.(tp) with
      | Prod sub_prod_el -> deco_vprod_el var_tp_lst ix var_mods sub_prod_el
      | Sums sums ->
          let dvar_mod, new_var_tp_lst =
            deco_var_mod var_tp_lst tp var_mods.(ix) in
          TpVal dvar_mod, new_var_tp_lst, ix + 1


(* Decorate model *)

let prod_el_of_tp tp = TpVal tp

let rec deco_model ix_cnt var_tps cdoms = function
  | Val sums -> DVal (decorate_sums cdoms sums), VarSet.empty
  | Let (match_mod, var_mods) ->
      let dvar_mods, var_tp_rev_lst, _ = deco_vprod_els [] cdoms 0 var_mods in
      let var_tp_ar = array_of_rev_list var_tp_rev_lst in
      let new_cdoms = array_map_of_rev_list prod_el_of_tp var_tp_rev_lst in
      let dmatch_mod, used_vars =
        deco_match_mod ix_cnt var_tps new_cdoms match_mod in
      DLet (dmatch_mod, var_tp_ar, dvar_mods), used_vars
  | MatchMod match_mod ->
      let dmatch_mod, used_vars =
        deco_match_mod ix_cnt var_tps cdoms match_mod in
      DMatchMod dmatch_mod, used_vars

and deco_match_mod ix_cnt var_tps cdoms = function
  | Shave (var_ix, cnstr, sh_model, model) ->
      let tp = VarMap.find var_ix var_tps in
      let sums = dfspec.(tp) in
      let sub_tps = sums.(cnstr) in
      let dsh_model, match_used_vars =
        let new_ix_cnt = ix_cnt + Array.length sub_tps in
        let colli = coll_sub_map ix_cnt in
        let new_var_tps = array_fold_lefti colli var_tps sub_tps in
        deco_model new_ix_cnt new_var_tps cdoms sh_model in
      let sub_vars = calc_sub_vars sums cnstr ix_cnt in
      let used_subs_set = VarSet.inter sub_vars match_used_vars in
      let used_subs =
        List.map (fun el -> el - ix_cnt) (VarSet.elements used_subs_set) in
      let dmodel, sh_used_vars = deco_model ix_cnt var_tps cdoms model in
      let match_used_no_subs = VarSet.diff match_used_vars used_subs_set in
      let used_vars = VarSet.union match_used_no_subs sh_used_vars in
      let used_vars = VarSet.add var_ix used_vars in
      DShave (var_ix, tp, cnstr, used_subs, dsh_model, dmodel), used_vars
  | Split (var_ix, models) ->
      let tp = VarMap.find var_ix var_tps in
      let sums = dfspec.(tp) in
      let used_vars_ref = ref (VarSet.singleton var_ix) in
      let dmodels =
        let colli = coll_sub_map ix_cnt in
        let sub_ix_cnt el = el - ix_cnt in
        Array.mapi (fun cnstr model ->
            let sub_tps = sums.(cnstr) in
            let new_ix_cnt = ix_cnt + Array.length sub_tps in
            let new_var_tps = array_fold_lefti colli var_tps sub_tps in
            let dmodel, match_used_vars =
              deco_model new_ix_cnt new_var_tps cdoms model in
            let sub_vars = calc_sub_vars sums cnstr ix_cnt in
            let used_subs_set = VarSet.inter sub_vars match_used_vars in
            let used_subs =
              List.map sub_ix_cnt (VarSet.elements used_subs_set) in
            let match_used_no_subs =
              VarSet.diff match_used_vars used_subs_set in
            used_vars_ref := VarSet.union match_used_no_subs !used_vars_ref;
            used_subs, dmodel) models in
      DSplit (var_ix, tp, dmodels), !used_vars_ref
  | PSplit (var_ix, maybe_models, model) ->
      let tp = VarMap.find var_ix var_tps in
      let sums = dfspec.(tp) in
      let ddef_mod, def_used_vars = deco_model ix_cnt var_tps cdoms model in
      let used_vars_ref = ref (VarSet.add var_ix def_used_vars) in
      let maybe_dmodels =
        let colli = coll_sub_map ix_cnt in
        let sub_ix_cnt el = el - ix_cnt in
        Array.mapi (fun cnstr -> function
            | Some model ->
                let sub_tps = sums.(cnstr) in
                let new_ix_cnt = ix_cnt + Array.length sub_tps in
                let new_var_tps = array_fold_lefti colli var_tps sub_tps in
                let dmodel, match_used_vars =
                  deco_model new_ix_cnt new_var_tps cdoms model in
                let sub_vars = calc_sub_vars sums cnstr ix_cnt in
                let used_subs_set = VarSet.inter sub_vars match_used_vars in
                let used_subs =
                  List.map sub_ix_cnt (VarSet.elements used_subs_set) in
                let match_used_no_subs =
                  VarSet.diff match_used_vars used_subs_set in
                used_vars_ref := VarSet.union match_used_no_subs !used_vars_ref;
                Some (used_subs, dmodel)
            | None -> None) maybe_models in
      DPSplit (var_ix, tp, maybe_dmodels, ddef_mod), !used_vars_ref


(* Rewrite variable indices according to actually used variables *)

let rewrite_dmodel (dmodel, used_vars) =
  let from_ix_ref = ref 0 in
  let to_ix_ref = ref 0 in
  let var_map_ref = ref VarMap.empty in

  (* Decorate a product element according to used variables *)
  let calc_prod_el_pat var_lst prod_el =
    let ix_ref = ref 0 in
    let var_lst_ref = ref var_lst in
    let rec loop = function
      | ProdEls prod_els ->
          let vars_lst = !var_lst_ref in
          let new_prod_els = Array.map loop prod_els in
          if !var_lst_ref == vars_lst then None
          else Some (`ProdEls new_prod_els)
      | TpVal tp ->
          match dispec.(tp) with
          | Prod prod_el -> loop prod_el
          | Sums _ ->
              let from_ix = !from_ix_ref in
              from_ix_ref := from_ix + 1;
              match !var_lst_ref with
              | [] -> None
              | used_var :: rest ->
                  let ix = !ix_ref in
                  ix_ref := ix + 1;
                  if ix < used_var then None
                  else (
                    let to_ix = !to_ix_ref in
                    var_map_ref := VarMap.add from_ix to_ix !var_map_ref;
                    to_ix_ref := to_ix + 1;
                    var_lst_ref := rest;
                    Some (`TpVal tp)) in
    loop prod_el in

  (* Compute pattern from used subvariables *)
  let calc_pat tp cnstr used_subs =
    match dispec.(tp) with
    | Prod _ -> failwith "calc_pat: expected Sums"
    | Sums sums ->
        match sums.(cnstr) with
        | IAtom -> None
        | IStrct prod_el -> calc_prod_el_pat used_subs prod_el in

  (* Perform rewriting *)
  let rec rwr_dmodel = function
    | DVal _ as dmodel -> dmodel
    | DLet (dmatch_mod, var_tp_ar, dvar_mods) ->
        DLet (rwr_dmatch_mod dmatch_mod, var_tp_ar, dvar_mods)
    | DMatchMod dmatch_mod -> DMatchMod (rwr_dmatch_mod dmatch_mod)
  and rwr_dmatch_mod dmatch_mod =
    let from_ix = !from_ix_ref in
    let to_ix = !to_ix_ref in
    let var_map = !var_map_ref in
    match dmatch_mod with
    | DShave (var_ix, tp, cnstr, used_subs, dsh_mod, dmodel) ->
        let rvar_ix = VarMap.find var_ix var_map in
        let rdmodel = rwr_dmodel dmodel in
        from_ix_ref := from_ix;
        to_ix_ref := to_ix;
        var_map_ref := var_map;
        let pat = calc_pat tp cnstr used_subs in
        let rdsh_mod = rwr_dmodel dsh_mod in
        DShave (rvar_ix, tp, cnstr, pat, rdsh_mod, rdmodel)
    | DSplit (var_ix, tp, dmodels) ->
        let rvar_ix = VarMap.find var_ix var_map in
        let cnvi cnstr (used_subs, dmodel) =
          from_ix_ref := from_ix;
          to_ix_ref := to_ix;
          var_map_ref := var_map;
          let pat = calc_pat tp cnstr used_subs in
          pat, rwr_dmodel dmodel in
        let rdmodels = Array.mapi cnvi dmodels in
        DSplit (rvar_ix, tp, rdmodels)
    | DPSplit (var_ix, tp, maybe_dmodels, dmodel) ->
        let rvar_ix = VarMap.find var_ix var_map in
        let rdmodel = rwr_dmodel dmodel in
        let cnvi cnstr = function
          | Some (used_subs, dmodel) ->
              from_ix_ref := from_ix;
              to_ix_ref := to_ix;
              var_map_ref := var_map;
              let pat = calc_pat tp cnstr used_subs in
              Some (pat, rwr_dmodel dmodel)
          | None -> None in
        let maybe_rdmodels = Array.mapi cnvi maybe_dmodels in
        DPSplit (rvar_ix, tp, maybe_rdmodels, rdmodel) in
  let ddpat = calc_prod_el_pat (VarSet.elements used_vars) init_prod_el in
  rwr_dmodel dmodel, ddpat


(* Decorate model for pretty-printing *)

let decorate_model model =
  let var_tps = array_fold_righti VarMap.add init_tps VarMap.empty in
  let n_init_tps = Array.length init_tps in
  rewrite_dmodel (deco_model n_init_tps var_tps init_prod_els model)

end
