#include "type.hpp"
#include <ostream>
#include <sstream>
#include <algorithm>
#include "error.hpp"

void type_scheme::print(const type_mgr& mgr, std::ostream& to) const {
    if(forall.size() != 0) {
        to << "forall ";
        for(auto& var : forall) {
            to << var << " ";
        }
        to << ". ";
    }
    monotype->print(mgr, to);
}

type_ptr substitute(const type_mgr& mgr, const std::map<std::string, type_ptr>& subst, const type_ptr& t) {
    type_var* var;
    type_ptr resolved = mgr.resolve(t, var);
    if(var) {
        auto subst_it = subst.find(var->name);
        if(subst_it == subst.end()) return resolved;
        return subst_it->second;
    } else if(type_arr* arr = dynamic_cast<type_arr*>(t.get())) {
        auto left_result = substitute(mgr, subst, arr->left);
        auto right_result = substitute(mgr, subst, arr->right);
        if(left_result == arr->left && right_result == arr->right) return t;
        return type_ptr(new type_arr(left_result, right_result));
    }
    return t;
}

type_ptr type_scheme::instantiate(type_mgr& mgr) const {
    if(forall.size() == 0) return monotype;
    std::map<std::string, type_ptr> subst;
    for(auto& var : forall) {
        subst[var] = mgr.new_type();
    }
    return substitute(mgr, subst, monotype);
}

void type_var::print(const type_mgr& mgr, std::ostream& to) const {
    auto it = mgr.types.find(name);
    if(it != mgr.types.end()) {
        it->second->print(mgr, to);
    } else {
        to << name;
    }
}

void type_base::print(const type_mgr& mgr, std::ostream& to) const {
    to << name;
}

void type_arr::print(const type_mgr& mgr, std::ostream& to) const {
    left->print(mgr, to);
    to << " -> (";
    right->print(mgr, to);
    to << ")";
}

std::string type_mgr::new_type_name() {
    int temp = last_id++;
    std::string str = "";

    while(temp != -1) {
        str += (char) ('a' + (temp % 26));
        temp = temp / 26 - 1;
    }

    std::reverse(str.begin(), str.end());
    return str;
}

type_ptr type_mgr::new_type() {
    return type_ptr(new type_var(new_type_name()));
}

type_ptr type_mgr::new_arrow_type() {
    return type_ptr(new type_arr(new_type(), new_type()));
}

type_ptr type_mgr::resolve(type_ptr t, type_var*& var) const {
    type_var* cast;

    var = nullptr;
    while((cast = dynamic_cast<type_var*>(t.get()))) {
        auto it = types.find(cast->name);

        if(it == types.end()) {
            var = cast;
            break;
        }
        t = it->second;
    }

    return t;
}

void type_mgr::unify(type_ptr l, type_ptr r) {
    type_var* lvar;
    type_var* rvar;
    type_arr* larr;
    type_arr* rarr;
    type_base* lid;
    type_base* rid;

    l = resolve(l, lvar);
    r = resolve(r, rvar);

    if(lvar) {
        bind(lvar->name, r);
        return;
    } else if(rvar) {
        bind(rvar->name, l);
        return;
    } else if((larr = dynamic_cast<type_arr*>(l.get())) &&
            (rarr = dynamic_cast<type_arr*>(r.get()))) {
        unify(larr->left, rarr->left);
        unify(larr->right, rarr->right);
        return;
    } else if((lid = dynamic_cast<type_base*>(l.get())) &&
            (rid = dynamic_cast<type_base*>(r.get()))) {
        if(lid->name == rid->name) return;
    }

    throw unification_error(l, r);
}

void type_mgr::bind(const std::string& s, type_ptr t) {
    type_var* other = dynamic_cast<type_var*>(t.get());
    
    if(other && other->name == s) return;
    types[s] = t;
}

void type_mgr::find_free(const type_ptr& t, std::set<std::string>& into) const {
    type_var* var;
    type_ptr resolved = resolve(t, var);

    if(var) {
        into.insert(var->name);
    } else if(type_arr* arr = dynamic_cast<type_arr*>(resolved.get())) {
        find_free(arr->left, into);
        find_free(arr->right, into);
    }
}
