%-----------------------------------------------------------------------------%
% Copyright (C) 1995-1999, 2006, 2011 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%

% file: ztype.m
% main author: philip

:- module ztype.

:- interface.

:- import_module assoc_list.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module word.
:- import_module zabstract.

:- type ztype
    --->    given(ident)
    ;       power(ztype)
    ;       cross(list(ztype))
    ;       schema(slist)
    ;       var(ztvar)
    ;       parameter(ident)
    ;       unity
    ;       abbreviation(ident, list(ztype), depth, ztarity, ztype).

:- type slist == assoc_list(ident, ztype).

:- type ztvar
    --->    ztvar(ref, int).
            % ref is identifer reference that introduces the variable,
            % and the int is the ordinal of the generic parameter
            % represented by the variable.

:- type varsource
    --->    parameter(ref, int)
    ;       function_arg
    ;       function_result
    ;       power
    ;       extension.

:- type apply_status
    --->    normal
    ;   from_apply.

:- type varinfo
    --->    varinfo(expr, varsource, maybe(ztype), apply_status).

:- type subst == pair(map(ztvar, varinfo), ref).

:- func initSubst = subst.

:- func ztapply(subst, ztype) = ztype.

:- pred subst_lookup(subst::in, ztvar::in, varinfo::out) is det.
:- pred subst_lookup(ztvar::in, varinfo::out, subst::in, subst::out) is det.
:- pred subst_insert(ztvar::in, varinfo::in, subst::in, subst::out) is det.
:- pred subst_update(ztvar::in, varinfo::in, subst::in, subst::out) is det.

:- type depth == int.       % >= 1

:- type ztarity == int.     % >= 0

:- type entry
    --->    e(ztarity, ztype).

:- func powerEntry = entry.
:- func givenType(ident) = ztype.
:- func givenEntry(ident) = entry.
:- func branchEntry(ident, ztype) = entry.

%%%

% :- type ptype == pair(int, ztype).
:- type ptypes.

:- func initPtypes = ptypes.

% :- pred addPtypes(assoc_list(ref, ptype)::in, ptypes::in, ptypes::out) is det.

:- pred add_sexpr_type(ref::in, slist::in, ptypes::in, ptypes::out) is det.

:- pred find_sexpr_type(ref::in, slist::out, ptypes::in) is det.

% :- pred findPtypes(subst::in, ref::in, list(expr)::out) is semidet.

:- type gentypes.

:- func initGenTypes = gentypes.

:- pred substToGenTypes(subst::in, gentypes::out) is det.

:- pred getGenType(gentypes::in, ref::in, maybe(list(expr))::out) is det.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- implementation.

:- import_module require.
:- import_module string.

powerEntry = e(1, power(power(var(ztvar(0, 1))))).
givenType(Id) = power(given(Id)).
givenEntry(Id) = e(0, power(given(Id))).
branchEntry(Id, T) = e(0, power(cross([T, given(Id)]))).

% This type contains information about the inferred types
% for each parameter for every reference to a generic identifier.
:- type ptypes == map(ref, slist).

initSubst = S-0 :-
    map.init(S).

initPtypes = PM :-
    map.init(PM).

add_sexpr_type(R, SL, P0, P) :-
    map.det_insert(R, SL, P0, P).

% *** Make this semidet, and handle failure case in zclp.m ***
find_sexpr_type(R, SL, P) :-
    map.lookup(P, R, SL).
%   Pred = (pred(SL0::out) is nondet :-
%       multi_map.nondet_search(P, R, 0 - schema(SL0))),
%   solutions(Pred, Solutions),
%   ( if Solutions = [] then
%       string.format("find_sexpr_type/3: ref %p not found",
%           [i(R)], Mesg),
%       error(Mesg)
%   else if Solutions = [SL1] then
%       SL = SL1
%   else
%       string.format("find_sexpr_type/3: ref %p multiple entries",
%           [i(R)], Mesg),
%       error(Mesg)
%   ).

% findPtypes(Subst, Ref, to_exprL(L)) :-
%   Subst = Subst1-_,
%   map.to_assoc_list(Subst1, L0),
%   P = (pred(_ - Info::in, Ord - T::out) is semidet :-
%       Info = varinfo(_, parameter(Ref, Ord), yes(T0), _),
%       Ord > 0,
%       T = ztapply(Subst, T0)),
%       % 0 is used for the enclosing type, not just one parameter
%   list.filter_map(P, L0, L1),
%   % P = (pred(Ord - ztapply(Subst, T)::out) is nondet :-
%   %   map.member(Subst1, ztvar(Ord, _),
%   %       varinfo(ref(Ref, _, _)-_, _, yes(T), _)),
%   %   Ord > 0),
%   %   % 0 is used for the enclosing type, not just one parameter
%   % solutions(P, L1),
%   list.sort(L1, L2),
%   assoc_list.values(L2, L).

:- type gentypes == map(ref, list(expr)).

initGenTypes = M :-
    map.init(M).

:- type triple
    --->    triple(ref, int, expr).

% Reverse ordering used because list is built up in reverse
:- pred ordSort(triple::in, triple::in, comparison_result::out) is det.

ordSort(triple(_, Ord1, _), triple(_, Ord2, _), C) :-
    compare(C, Ord2, Ord1).

:- pred refSort(triple::in, triple::in, comparison_result::out) is det.

refSort(triple(Ref1, _, _), triple(Ref2, _, _), C) :-
    compare(C, Ref1, Ref2).

substToGenTypes(Subst, GenTypes) :-
    Subst = Subst1-_,
    map.to_assoc_list(Subst1, L0),
    P =
        ( pred(_ - Info::in, triple(Ref, Ord, T)::out) is semidet :-
            Info = varinfo(_, parameter(Ref, Ord), yes(T0), _),
            T = to_expr(ztapply(Subst, T0)) - 0
        ),
    list.filter_map(P, L0, L1),
    list.sort(ordSort, L1, L2),
    list.sort(refSort, L2, L3),
    listToGenTypes(L3, GenTypes).   % Group then insert in map

:- pred listToGenTypes(list(triple)::in, gentypes::out) is det.

listToGenTypes([], GenTypes) :-
    map.init(GenTypes).
listToGenTypes(List, GenTypes) :-
    List = [triple(Ref, _, Expr) | List1],
    map.init(M0),
    listToGenTypes(Ref, [Expr], List1, M0, GenTypes).

:- pred listToGenTypes(ref::in, list(expr)::in, list(triple)::in,
    gentypes::in, gentypes::out) is det.

listToGenTypes(Ref, ExprList, [], M0, M) :-
    map.det_insert(Ref, ExprList, M0, M).
listToGenTypes(Ref, ExprList, [triple(Ref1, _, Expr) | List], M0, M) :-
    ( if Ref = Ref1 then
        listToGenTypes(Ref, [Expr | ExprList], List, M0, M)
    else
        map.det_insert(Ref, ExprList, M0, M1),
        listToGenTypes(Ref1, [Expr], List, M1, M)
    ).

getGenType(GenTypes, Ref, M) :-
    ( if map.search(GenTypes, Ref, L) then
        M = yes(L)
    else
        M = no
    ).

:- func to_expr(ztype) = expr1.

to_expr(given(I)) = ref(0, I, no).
to_expr(power(T)) = powerset(to_expr(T)-0).
to_expr(cross(L)) = product(to_exprL(L)).
to_expr(schema(SL)) = sexp(sexpr(0, text(to_declL(SL), []), 0)).
to_expr(var(_)) = _ :-
    error("to_expr/2: converting non-ground type to expr").
to_expr(parameter(I)) = ref(0, I, no).
to_expr(unity) = _ :-
    error("to_expr/2: converting unity to expr").
to_expr(abbreviation(I, L, _, _, _)) = ref(0, I, yes(to_exprL(L))).

% *** Could sort this by type to produce simpler logic representation ***
:- func to_declL(slist) = list(decl).

to_declL([]) = [].
to_declL([I - T | L]) = [decl([I], to_expr(T)-0) | to_declL(L)].

:- func to_exprL(list(ztype)) = list(expr).

to_exprL([]) = [].
to_exprL([H | T]) = [to_expr(H)-0 | to_exprL(T)].

ztapply(_, G) = G :-
    G = given(_).
ztapply(S, power(T)) = power(ztapply(S, T)).
ztapply(S, cross(L0)) = cross(L) :-
    list.map(
        ( pred(T::in, U::out) is det :-
            U = ztapply(S, T)
        ), L0, L).
ztapply(S, schema(DL0)) = schema(DL) :-
    list.map(do_decl(ztapply(S)), DL0, DL).
ztapply(S, V) = T :-
    V = var(I),
    subst_lookup(S, I, varinfo(_, _, MT, _)),
    (MT = yes(T1), T = ztapply(S, T1) ; MT = no, T = V ).
ztapply(_, P) = P :-
    P = parameter(_).
ztapply(_, unity) = unity.
ztapply(S, abbreviation(I, L0, D, N, T)) = abbreviation(I, L , D, N, T) :-
    list.map(
        ( pred(IN::in, OUT::out) is det :-
            OUT = ztapply(S, IN)
        ), L0, L).

:- pred do_decl(func(ztype) = ztype, pair(ident, ztype), pair(ident, ztype)).
:- mode do_decl(func(in) = out is det, in, out) is det.

do_decl(F, I - H, I - F(H)).

subst_lookup(S-_, V, VI) :-
    ( if map.search(S, V, VI0) then
        VI = VI0
    else
        V = ztvar(Ref, Int),
        string.format("subst_lookup/4: var %i:%i not found",
            [i(Ref), i(Int)], Str),
        error(Str)
    ).

subst_lookup(V, VI, S, S) :-
    subst_lookup(S, V, VI).

subst_insert(V, VI, S0 - G, S - G) :-
    ( if map.insert(V, VI, S0, S1) then
        S = S1
    else
        V = ztvar(Ref, Int),
        string.format("subst_insert/4: var %i:%i already in subst",
                            [i(Ref), i(Int)], Str),
        error(Str)
    ).

subst_update(V, VI, S0 - G, S - G) :-
    ( if map.update(V, VI, S0, S1) then
        S = S1
    else
        V = ztvar(Ref, Int),
        string.format("subst_update/4: var %i:%i not found",
            [i(Ref), i(Int)], Str),
        error(Str)
    ).
