// ----------------------------------------------------------------------------
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ----------------------------------------------------------------------------

﻿using System;
using System.Collections;

namespace Microsoft.Singularity.Policy.Engine
{
    // The Wam class implements a restricted version of the Warren
    // Abstract Machine, as described in "Warren's Abstract
    // Machine: A Tutorial Reconstruction" by Hassan Ait-Kaci
    // (February 18, 1999, reprinted from the MIT Press version).

    // All variables are treated as permanent,
    // so there are no temporary Xn registers.

    // The code area is an Area of Instructions.

    // The heap is an Area of Cells, where a
    // Cell can be a Ref, a Str, or a Functor.

    // The stack is an Area of Cells, where a Cell can also be an Environment
    // or a ChoicePoint. These are stored as single cells with a strongly
    // typed internal structure. The cells for permanent variables follow an
    // Environment; the cells for copies of arguments follow a ChoicePoint.

    // Argument registers are stored in a separate Area of Cells.

    // The trail is an Area of Addresses of Cells
    // that must be unbound upon backtracking.

    // The PDL is local to the Unify procedure, and is a .NET Stack.

    // There are no anonymous variables: set_void and unify_void are
    // unimplemented. Anonymous variables can be added as an optimization.

    // There is no environment trimming: the WAM instruction
    // "call P, N" is replaced by the simpler "call P".
    // Environment trimming can be added as an optimization.

    // There is no TCO: execute, put_unsafe_value,
    // set_local_value, and unify_local_value are
    // unimplemented. TCO can be added as an optimization.

    // There are no list instructions: put_list and get_list are
    // unimplemented. List instructions can be added as an optimization.

    // There are no constant instructions: put_constant, set_constant,
    // get_constant, and unify-constant are unimplemented.
    // Constant instructions can be added as an optimization.

    // There are no indexing instructions: switch_on_term, switch_on_constant,
    // switch_on_structure, try, retry, and trust are unimplemented.
    // Indexing instructions can be added as an optimization.
    internal class Wam {
        Area _heap = new Area("HEAP", 1);
        Address _h, _s;
        Area _a;
        // _code really shouldn't be visible
        internal Area _code = new Area("CODE", 0);
        Address _p, _cp;
        // _label really shouldn't be visible
        internal Hashtable _label = new Hashtable();
        Area _stack = new Area("STACK", 2);
        // _e really shouldn't be visible
        internal Address _e;
        enum Mode { None, Read, Write };
        Mode _mode = Mode.None;
        internal Area A { get { return _a; } }
        // BACKTRACKING
        Address _b, _hb;
        // CUT
        Address _b0;
        // TRAIL
        Area _trail = new Area("TRAIL", 3);
        Address _tr;
        // failure
        bool _fail = false;

        // Put instructions. There is no TCO, so put_unsafe_value
        // is unimplemented. There are no list instructions,
        // so put_list is unimplemented. There are no constant
        // instructions, so put_constant is unimplemented.

        // PutVariable always allocates a heap cell.
        internal void PutVariable(Address yn, Address ai) {
            ai._cell = yn._cell = _h._cell = new Ref(_h);
            _h = _h + 1;
        }
        internal void PutValue(Address yn, Address ai) { ai._cell = yn._cell; }
        // PutStructure always allocates a STR cell on the heap.
        internal void PutStructure(Functor fn, Address yn) {
            _h[0] = new Str(_h);
            _h[1] = fn;
            yn._cell = _h._cell;
            _h = _h + 2;
        }

        // Get instructions. There are no list instructions,
        // so get_list is unimplemented. There are no constant
        // instructions, so get_constant is unimplemented.
        internal void GetVariable(Address yn, Address ai) {
            yn._cell = ai._cell;
        }
        internal void GetValue(Address yn, Address ai) {
            Unify(yn, ai);
            if (_fail) {
                Backtrack();
            }
        }
        internal void GetStructure(Functor fn, Address ai) {
            Address addr = Deref(ai);
            if (addr._cell is Ref) {
                _h[0] = new Str(_h + 1);
                _h[1] = fn;
                Bind(addr, _h);
                _h = _h + 2;
                // Is this right? It conflicts with the errata.
                _s = null;
                _mode = Mode.Write;
            }
            else if (addr._cell is Str) {
                Address a = ((Str)addr._cell).Value;
                if (a._cell.Equals(fn)) {
                    _s = a + 1;
                    _mode = Mode.Read;
                }
                else {
                    _fail = true;
                }
            }
            else {
                _fail = true;
            }
            if (_fail) {
                Backtrack();
            }
        }

        // Set instructions. There is no TCO, so set_local_value
        // is unimplemented. There are no constant instructions,
        // so set_constant is unimplemented. There are no
        // anonymous variables, so set_void is unimplemented.
        internal void SetVariable(Address yn) {
            yn._cell = _h._cell = new Ref(_h);
            _h = _h + 1;
        }
        internal void SetValue(Address yn) { _h[0] = yn._cell; _h = _h + 1; }

        // Unify instructions. There is no TCO, so unify_local_value
        // is unimplemented. There are no constant instructions,
        // so unify-constant is unimplemented. There are no
        // anonymous variables, so unify_void is unimplemented.
        internal void UnifyVariable(Address yn) {
            switch (_mode) {
            case Mode.Read:
                yn._cell = _s._cell;
                break;
            case Mode.Write:
                yn._cell = _h._cell = new Ref(_h);
                _h = _h + 1;
                break;
            default:
                throw new Exception();
            }
            _s = _s + 1;
        }
        internal void UnifyValue(Address yn) {
            switch (_mode) {
            case Mode.Read:
                Unify(yn, _s);
                break;
            case Mode.Write:
                _h[0] = yn._cell;
                _h = _h + 1;
                break;
            default:
                throw new Exception();
            }
            _s = _s + 1;
            if (_fail) {
                Backtrack();
            }
        }

        // Control instructions. There is no environment trimming,
        // so the WAM instruction "call P, N" is replaced with
        // "call P". There is no TCO, so execute is unimplemented.
        internal void Allocate(uint n) {
            Address e;
            if (Greater(_e, _b)) {
                e = _e + 1 + ((Environment)_e._cell)._n;
            }
            else {
                e = _b + 1 + ((ChoicePoint)_b._cell)._n;
            }
            e._cell = new Environment(n, _e, _cp);
            _e = e;
        }
        internal void Deallocate() {
            _cp = ((Environment)_e._cell)._cp;
            _e = ((Environment)_e._cell)._ce;
        }
        internal void Call(Functor P) {
            if (_label.ContainsKey(P)) {
                _cp = _p;
                _p = (Address)_label[P];
            }
            else {
                Backtrack();
            }
        }
        internal void Proceed() { _p = _cp; }

        // Choice instructions. There are no indexing instructions,
        // so try, retry, and trust are unimplemented.
        internal void TryMeElse(Address L, uint n) {
            Address b;
            if (Greater(_e, _b)) {
                b = _e + 1 + ((Environment)_e._cell)._n;
            }
            else {
                b = _b + 1 + ((ChoicePoint)_b._cell)._n;
            }
            ChoicePoint b_ = new ChoicePoint(n, _e, _cp, _b, L, _tr, _h, _hb);
            b._cell = b_;
            for (uint i = 1; i <= n; i++) {
                b[i] = _a[i];
            }
            _b = b;
            _hb = _h;
        }
        internal void RetryMeElse(Address L) {
            ChoicePoint b_ = (ChoicePoint)_b._cell;
            uint n = b_._n;
            for (uint i = 1; i <= n; i++) {
                _a[i] = _b[i];
            }
            _e = b_._ce;
            _cp = b_._cp;
            b_._bp = L;
            UnwindTrail(b_._tr._address, _tr._address);
            _tr = b_._tr;
            _h = b_._h;
            _hb = _h;
        }
        internal void TrustMe() {
            ChoicePoint b_ = (ChoicePoint)_b._cell;
            uint n = b_._n;
            for (uint i = 1; i <= n; i++) {
                _a[i] = _b[i];
            }
            _e = b_._ce;
            _cp = b_._cp;
            UnwindTrail(b_._tr._address, _tr._address);
            _tr = b_._tr;
            _h = b_._h;
            _b = b_._b;
            _hb = ((ChoicePoint)_b._cell)._hb;
        }

        // Cut instructions.
        void NeckCut() { if (Greater(_b, _b0)) { _b = _b0; TidyTrail(); } }
        void GetLevel(Address yn) { yn._address = _b0; }
        void Cut(Address yn) {
            if (Greater(_b, yn._address)) {
                _b0 = yn._address; TidyTrail();
            }
        }

        // Ancillary functions.
        internal void Backtrack() {
            _fail = false;
            if (_b == null) {
                throw new Exception("fail and exit program");
            }
            else {
                _p = ((ChoicePoint)_b._cell)._bp;
            }
        }
        Address Deref(Address a) {
            if (a._cell is Ref) {
                Address value = (a._cell as Ref).Value;
                if (value != a) {
                    return Deref(value);
                }
            }
            return a;
        }
        void Bind(Address a1, Address a2) {
            // should add an occurs-check
            if (a1._cell is Ref && (!(a2._cell is Ref) || Less(a2, a1))) {
                a1._cell = a2._cell;
                Trail(a1);
            }
            else {
                a2._cell = a1._cell;
                Trail(a2);
            }
        }
        void Trail(Address a) {
            if (Less(a, _hb) || Less(_h, a) && Less(a, _b)) {
                _tr._address = a;
                _tr = _tr + 1;
            }
        }
        void UnwindTrail(Address a1, Address a2) {
            Address a = a1;
            while (Less(a, a2)) {
                a._cell = new Ref(a); a = a + 1;
            }
        }
        void TidyTrail() {
            Address i = ((ChoicePoint)_b._cell)._tr;
            while (Less(i, _tr)) {
                if (
                    Less(i._address, _hb)
                 || Less(_h, i._address) && Less(i._address, _b)
                ) {
                    i = i + 1;
                }
                else {
                    _tr = _tr - 1;
                    i._cell = _tr._cell;
                }
            }
        }
        void Unify(Address a1, Address a2) {
            Stack pdl = new Stack();
            pdl.Push(a1);
            pdl.Push(a2);
            _fail = false;
            while (!(pdl.Count == 0 || _fail)) {
                Address d1 = Deref((Address)pdl.Pop());
                Address d2 = Deref((Address)pdl.Pop());
                if (d1 != d2) {
                    Cell cell1 = d1._cell;
                    Cell cell2 = d2._cell;
                    if (cell1 is Ref) {
                        Bind(d1, d2);
                    }
                    else {
                        if (cell2 is Ref) {
                            Bind(d1, d2);
                        }
                        else if (cell2 is Str) {
                            if (!(cell1 is Str)) {
                                _fail = true;
                            }
                            else {
                                Address v1 = ((Str)cell1).Value;
                                Address v2 = ((Str)cell2).Value;
                                Functor f1 = (Functor)v1._cell;
                                Functor f2 = (Functor)v2._cell;
                                if (f1 != f2) {
                                    _fail = true;
                                }
                                else {
                                    for (int i = 1; i <= f1.Arity; i++) {
                                        pdl.Push(v1 + i);
                                        pdl.Push(v2 + i);
                                    }
                                }
                            }
                        }
                        else {
                            _fail = true;
                        }
                    }
                }
            }
        }

        // Other functions
        bool Less(Address a, Address b) {
            return a.Order < b.Order || a.Order == b.Order && a.At < b.At;
        }
        bool Greater(Address a, Address b) { return Less(b, a); }

        // Run starts execution of the WAM at _p (the current instruction).
        internal void Run() {
            while (_p != null) {
                Instruction instr = _p._instruction;
                _p = _p + 1;
                instr._execute();
            }
        }

        // The constructor.
        internal Wam() {
            _h = _hb = new Address(_heap, 0);
            _a = new Area("A", 9);
            _p = new Address(_code, 0);
            _b = new Address(_stack, 0);
            _b[0]
          = new ChoicePoint(0, null, null, null, null, null, null, null);
            _e = _b + 1;
            _e[0] = new Environment(0, null, null);
            _tr = new Address(_trail, 0);
        }
    }
}
