(*  File: dshave.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: dshave.ml,v 1.5 2006/01/17 00:23:37 mottl Exp $ *)

open Utils
open Algdt_types
open Algdt_utils

(* Shave domain variables with redundant constructors *)

type sh_chain = (var_ix * tp * cnstr) one_list
type sh_el = FinVar of var_ix | ShVar of sh_chain * var_ix
type dom_ixs = sh_el array

let dummy_sh_el = FinVar 0
let dom_ixs_iota n = Array.init n (fun n -> FinVar n)

let rec fill_radix dom_ixs radix dom_ix lix ix =
  let new_ix = ix - 1 in
  dom_ixs.(dom_ix + new_ix) <- ShVar (radix, new_ix);
  if new_ix > lix then fill_radix dom_ixs radix dom_ix lix new_ix

let dshave dom_ixs (ShInfo (pos_infos, dvars)) n_sh_dvars =
  let new_dom_ixs = Array.make n_sh_dvars dummy_sh_el in
  let sh_dvars = Array.make n_sh_dvars dummy_var in
  let rec sub_loop rev_radix dvars lix sh_dvar_ix sub_infos =
    let radix = one_list_rev rev_radix in
    let rec inner_loop lix sh_dvar_ix = function
      | [] ->
          let n_dvars = Array.length dvars in
          let n_last = n_dvars - lix in
          if n_last > 0 then (
            Array.blit dvars lix sh_dvars sh_dvar_ix n_last;
            fill_radix new_dom_ixs radix (sh_dvar_ix - lix) lix n_dvars;
            sh_dvar_ix + n_last)
          else sh_dvar_ix
      | (sh_ix, cnstr, sh_info) :: rest ->
          let n_between = sh_ix - lix in
          if n_between > 0 then (
            Array.blit dvars lix sh_dvars sh_dvar_ix n_between;
            fill_radix new_dom_ixs radix (sh_dvar_ix - lix) lix sh_ix);
          let new_lix = sh_ix + 1 in
          let new_sh_dvar_ix =
            let new_sh_dvar_ix = sh_dvar_ix + n_between in
            match sh_info with
            | None | Some (0, _) -> new_sh_dvar_ix
            | Some (_, ShInfo (sub_infos, sub_vars)) ->
                let new_rev_radix =
                  OneCons ((sh_ix, dvars.(sh_ix).tp, cnstr), rev_radix) in
                sub_loop new_rev_radix sub_vars 0 new_sh_dvar_ix sub_infos in
          inner_loop new_lix new_sh_dvar_ix rest in
    inner_loop lix sh_dvar_ix sub_infos in
  let rec loop lix sh_dvar_ix = function
    | [] ->
        let n_last = Array.length dvars - lix in
        if n_last > 0 then (
          Array.blit dvars lix sh_dvars sh_dvar_ix n_last;
          Array.blit dom_ixs lix new_dom_ixs sh_dvar_ix n_last);
        new_dom_ixs, sh_dvars
    | (sh_ix, cnstr, sh_info) :: rest ->
        let n_between = sh_ix - lix in
        if n_between > 0 then (
          Array.blit dvars lix sh_dvars sh_dvar_ix n_between;
          Array.blit dom_ixs lix new_dom_ixs sh_dvar_ix n_between);
        let new_lix = sh_ix + 1 in
        let new_sh_dvar_ix =
          let new_sh_dvar_ix = sh_dvar_ix + n_between in
          match sh_info with
          | None | Some (0, _) -> new_sh_dvar_ix
          | Some (_, ShInfo (sub_infos, sub_vars)) ->
              let rev_radix =
                match dom_ixs.(sh_ix) with
                | FinVar ix -> OneEl (ix, dvars.(sh_ix).tp, cnstr)
                | ShVar (one_lst, last_ix) ->
                    let one_el = last_ix, dvars.(sh_ix).tp, cnstr in
                    OneCons (one_el, one_list_rev one_lst) in
              sub_loop rev_radix sub_vars 0 new_sh_dvar_ix sub_infos in
        loop new_lix new_sh_dvar_ix rest in
  loop 0 0 pos_infos


(* Shave domain indices and find shaved index bounds *)

let rec shave_lbound ix_cnt sh_ix dom_ixs ix =
  if ix >= 0 then
    match dom_ixs.(ix) with
    | FinVar _ -> ix + 1
    | ShVar (OneEl (var_ix, _, _), _)
    | ShVar (OneCons ((var_ix, _, _), _), _) when var_ix <> sh_ix -> ix + 1
    | ShVar (OneEl _, last_ix) ->
        dom_ixs.(ix) <- FinVar (ix_cnt + last_ix);
        shave_lbound ix_cnt sh_ix dom_ixs (ix - 1)
    | ShVar (OneCons (_, radix), last_ix) ->
        let new_radix =
          match radix with
          | OneEl (var_ix, tp, cnstr) -> OneEl (ix_cnt + var_ix, tp, cnstr)
          | OneCons ((var_ix, tp, cnstr), rest) ->
              OneCons ((ix_cnt + var_ix, tp, cnstr), rest) in
        dom_ixs.(ix) <- ShVar (new_radix, last_ix);
        shave_lbound ix_cnt sh_ix dom_ixs (ix - 1)
  else 0

let rec shave_rbound ix_cnt sh_ix dom_ixs ix =
  if ix < Array.length dom_ixs then
    match dom_ixs.(ix) with
    | FinVar _ -> ix
    | ShVar (OneEl (var_ix, _, _), _)
    | ShVar (OneCons ((var_ix, _, _), _), _) when var_ix <> sh_ix -> ix
    | ShVar (OneEl _, last_ix) ->
        dom_ixs.(ix) <- FinVar (ix_cnt + last_ix);
        shave_rbound ix_cnt sh_ix dom_ixs (ix + 1)
    | ShVar (OneCons (_, radix), last_ix) ->
        let new_radix =
          match radix with
          | OneEl (var_ix, tp, cnstr) -> OneEl (ix_cnt + var_ix, tp, cnstr)
          | OneCons ((var_ix, tp, cnstr), rest) ->
              OneCons ((ix_cnt + var_ix, tp, cnstr), rest) in
        dom_ixs.(ix) <- ShVar (new_radix, last_ix);
        shave_rbound ix_cnt sh_ix dom_ixs (ix + 1)
  else ix
