/*
 * PostChecker.java
 *
 * Created on 18 de Junho de 2005, 09:14
 *
 * To change this template, choose Tools | Options and locate the template under
 * the Source Creation and Management node. Right-click the template and choose
 * Open. You can then make changes to the template in the Source Editor.
 */

package net.sourceforge.czt.typecheck.circus;

import java.util.ArrayList;
import java.util.List;

import static net.sourceforge.czt.typecheck.circus.util.GlobalDefs.*;

import net.sourceforge.czt.circustools.ast.*;
import net.sourceforge.czt.base.ast.*;
import net.sourceforge.czt.circus.ast.*;
import net.sourceforge.czt.circus.visitor.*;
import net.sourceforge.czt.z.ast.*;
import net.sourceforge.czt.z.visitor.*;
import net.sourceforge.czt.typecheck.z.util.*;
import net.sourceforge.czt.typecheck.z.impl.*;
import net.sourceforge.czt.typecheck.circus.impl.*;

/**
 * At the end of the typechecker, this checker visits any previously
 * unresolved SetExprs and RefExprs (expressions that may introduce a
 * variable type into their type) to ensure that all implicit
 * parameters have been determined.
 */
public class PostChecker
  extends Checker
  implements CallProcessVisitor,
             CallActionVisitor
{
  protected net.sourceforge.czt.typecheck.z.PostChecker zPostChecker;

  public PostChecker(TypeChecker typeChecker)
  {
    super(typeChecker);
    zPostChecker =
      new net.sourceforge.czt.typecheck.z.PostChecker(typeChecker);
  }

  public Object visitTerm(Term term)
  {
    return term.accept(zPostChecker);
  }
  
  public Object visitCallProcess(CallProcess term) 
  {

    RefName procRef = term.getRefName();
    DeclName procDecl = factory().createDeclName(procRef);
    ErrorAnn errorAnn = null;
    
    DeclName currentProc = (DeclName)processes4PostCheck().remove(0);
    List<NameTypePair> localVars = localVars4ProcPostCheck().remove(0);
    
    typeEnv().enterScope();    
    typeEnv().add(localVars);

    if(!isProcess(procDecl)) {
      Object [] params = {currentProc.getWord(), procDecl.getWord()};
      errorAnn = errorAnn(term, ErrorMessage.IS_NOT_PROCESS_NAME, params);
    } else {
      ProcessType procType = (ProcessType)sectTypeEnv().getType(procRef);
      ProcessSignature procSignature = procType.getProcessSignature();
      
      setCurrentProcess(currentProc);

      List<NameTypePair> paramsOrIndexes = null;
      if(procSignature.getParamsOrIndexes() != null) {
        paramsOrIndexes = procSignature.getParamsOrIndexes().getNameTypePair();
      }
      // chama um método auxiliar que irá verificar se a chamada está correta
      errorAnn = checkCallProcess(term, paramsOrIndexes, errorAnn);
    }
    
    typeEnv().exitScope();

    return errorAnn;
    
  }

  public Object visitCallAction(CallAction term)
  {
    ActionSignature actionSignature = factory().createActionSignature();
    RefName actionRef = term.getRefName();
    DeclName actionDecl = factory().createDeclName(actionRef);
    ErrorAnn errorAnn = null;

    DeclName currentAct = (DeclName)actions4PostCheck().remove(0);
    List<NameTypePair> localVars = localVars4ActPostCheck().remove(0);
    
    typeEnv().enterScope();    
    typeEnv().add(localVars);

    if(!localCircTypeEnv().isAction(actionDecl)) {
      Object [] params = {currentAct.getWord(), currentProcess().getWord(), actionDecl.getWord()};
      errorAnn = errorAnn(term, ErrorMessage.IS_NOT_ACTION_NAME, params);
    } 
    else {
      ActionType actionType = localCircTypeEnv().getActionType(actionDecl);
      actionSignature = actionType.getActionSignature();

      setCurrentAction(currentAct);

      List<NameTypePair> params = null;
      if(actionSignature.getParams() != null) {
        params = actionSignature.getParams().getNameTypePair();
      }          
      // chama um método auxiliar que irá verificar se a chamada está correta
      errorAnn = checkCallAction(term, params, errorAnn);
    }
    
    typeEnv().exitScope();

    setCurrentAction(null);
    
    return errorAnn;
  }

  private ErrorAnn checkCallProc(DeclName procName, List decs, List types){
    ErrorAnn errorAnn = null;
    int i = 0;
    if(decs.size() == types.size()) {
      for(NameTypePair pair : (List<NameTypePair>)decs) {
        Type2 expectedU = unwrapType(pair.getType());
        Type2 foundU = unwrapType((Type)types.get(i));
        if(foundU instanceof UnknownType) {
          Object [] params = {currentProcess().getWord(), procName.getWord(), i+1};
          errorAnn = errorAnn(procName, ErrorMessage.PARAM_PROC_CALL_UNDECLARED_VAR, params);
          break;
        }
        if (unify(foundU, expectedU) != SUCC) {
          Object [] params = {currentProcess().getWord(), expectedU, foundU, i, procName.getWord()};
          errorAnn = errorAnn(procName, ErrorMessage.PARAM_PROC_CALL_NOT_UNIFY, params);
          break;
        }   
        i++;
      }
    } else {
      Object [] params = {currentProcess().getWord(), decs.size(), types.size(), procName.getWord()};
      errorAnn = errorAnn(procName, ErrorMessage.PROC_CALL_DIFF_NUMBER_EXPRS, params);
    }
    
    return errorAnn;
  }

  private ErrorAnn checkCallProcess(CallProcess term, List<NameTypePair> paramsOrIndexes, ErrorAnn errorAnn) {
    
    DeclName procDecl = factory().createDeclName(term.getRefName());
    String kindOfProcess = getKindOfProcess(procDecl);
    List<Type> typeExprs = new ArrayList<Type>();
    List<Expr> exprs = null;
    
    switch(term.getCallType()) {
      case Param :
        if(!kindOfProcess.equals("PARAM")){
          Object [] params = {currentProcess().getWord(), procDecl.getWord()};
          errorAnn = errorAnn(term, ErrorMessage.IS_NOT_PARAM_PROCESS_IN_PROC_CALL, params);
        }
        else {
          exprs = (List<Expr>)term.getExpr();
          for(Expr expr : exprs) {
            Type type = (Type)expr.accept(exprChecker());
            typeExprs.add(type);
          }
          errorAnn = checkCallProc(procDecl, paramsOrIndexes, typeExprs);
        }
        break;
      case Index :
        if(!kindOfProcess.equals("INDEX")){
          Object [] params = {currentProcess().getWord(), procDecl.getWord()};
          errorAnn = errorAnn(term, ErrorMessage.IS_NOT_INDEX_PROCESS_IN_PROC_CALL, params);
        }
        else {
          exprs = (List<Expr>)term.getExpr();
          for(Expr expr : exprs) {
            Type type = (Type)expr.accept(exprChecker());
            typeExprs.add(type);
          }
          errorAnn = checkCallProc(procDecl, paramsOrIndexes, typeExprs);
        }
        break;
      case Gen :
        if(!isGenericProcess(procDecl)){
          Object [] params = {currentProcess().getWord(), procDecl.getWord()};
          errorAnn = errorAnn(term, ErrorMessage.IS_NOT_GEN_PROCESS_IN_PROC_CALL, params);
        }
        else {
          exprs = (List<Expr>)term.getGenExpr();
          for(Expr expr : exprs) {
            Type type = (Type)expr.accept(exprChecker());
            if(!(type instanceof PowerType)) {
              // ERRO. A EXPRESSÃO DEVE SER UM CONJUNTO
              Object [] params = {currentProcess().getWord(), procDecl.getWord()};
              errorAnn = errorAnn(term, ErrorMessage.IS_NOT_POWER_TYPE_IN_GEN_PROCESS, params);
              break;
            }
            else {
              typeExprs.add(type);
            }
          }
          List<DeclName> genParams = getGenParamsProcess(procDecl);
          if(genParams.size() != typeExprs.size()) {
            Object [] params = {currentProcess().getWord(), procDecl.getWord(),
                                genParams.size(), typeExprs.size()};
            errorAnn = errorAnn(term, ErrorMessage.GEN_PROCESS_INSTANTIATION_ERROR, params);
          }
        }
        break;
      case GenParam :
        if(!isGenericProcess(procDecl)){
          Object [] params = {currentProcess().getWord(), procDecl.getWord()};
          errorAnn = errorAnn(term, ErrorMessage.IS_NOT_GEN_PROCESS_IN_PROC_CALL, params);
        }
        else {
          List<Expr> genExprs = (List<Expr>)term.getGenExpr();
          List<Type> typeGenExprs = new ArrayList<Type>();
          for(Expr genExpr : genExprs) {
            Type type = (Type)genExpr.accept(exprChecker());
            typeGenExprs.add(type);
          }
          List<DeclName> genParams = getGenParamsProcess(procDecl);
          if(genParams.size() != typeGenExprs.size()) {
            Object [] params = {currentProcess().getWord(), procDecl.getWord(),
                                genParams.size(), typeGenExprs.size()};
            errorAnn = errorAnn(term, ErrorMessage.GEN_PROCESS_INSTANTIATION_ERROR, params);
          }
          if(!kindOfProcess.equals("PARAM")){
            Object [] params = {currentProcess().getWord(), procDecl.getWord()};
            errorAnn = errorAnn(term, ErrorMessage.IS_NOT_PARAM_PROCESS_IN_PROC_CALL, params);
          }
          else {
            exprs = (List<Expr>)term.getExpr();
            for(Expr expr : exprs) {
              Type type = (Type)expr.accept(exprChecker());
              typeExprs.add(type);
            }
            errorAnn = checkCallProc(procDecl, paramsOrIndexes, typeExprs);
          }
        }
        break;
      case GenIndex :
        if(!isGenericProcess(procDecl)){
          Object [] params = {currentProcess().getWord(), procDecl.getWord()};
          errorAnn = errorAnn(term, ErrorMessage.IS_NOT_GEN_PROCESS_IN_PROC_CALL, params);
        }
        else {
          List<Expr> genExprs = (List<Expr>)term.getGenExpr();
          List<Type> typeGenExprs = new ArrayList<Type>();
          for(Expr genExpr : genExprs) {
            Type type = (Type)genExpr.accept(exprChecker());
            typeGenExprs.add(type);
          }
          List<DeclName> genParams = getGenParamsProcess(procDecl);
          if(genParams.size() != typeGenExprs.size()) {
            Object [] params = {currentProcess().getWord(), procDecl.getWord(),
                                genParams.size(), typeGenExprs.size()};
            errorAnn = errorAnn(term, ErrorMessage.GEN_PROCESS_INSTANTIATION_ERROR, params);
          }
          if(!kindOfProcess.equals("INDEX")){
            Object [] params = {currentProcess().getWord(), procDecl.getWord()};
            errorAnn = errorAnn(term, ErrorMessage.IS_NOT_INDEX_PROCESS_IN_PROC_CALL, params);
          }
          else {
            exprs = (List<Expr>)term.getExpr();
            for(Expr expr : exprs) {
              Type type = (Type)expr.accept(exprChecker());
              typeExprs.add(type);
            }
            errorAnn = checkCallProc(procDecl, paramsOrIndexes, typeExprs);
          }
        }
        break;
      case Normal :
        if(!kindOfProcess.equals("NORMAL")){
          Object [] params = {currentProcess().getWord(), procDecl.getWord()};
          errorAnn = errorAnn(term, ErrorMessage.PROC_CALL_NEEDS_PARAMS, params);
        }
        break;
    }
    
    return errorAnn;
    
  }

  private ErrorAnn checkCallAction(CallAction term, List<NameTypePair> params, ErrorAnn errorAnn) {
    
    DeclName actionDecl = factory().createDeclName(term.getRefName());
    
    switch(term.getCallType()) {
      case Param :
        if(!localCircTypeEnv().isParamAction(actionDecl)){
          Object [] paramsError = {currentAction().getWord(), currentProcess().getWord(), actionDecl.getWord()};
          errorAnn = errorAnn(term, ErrorMessage.IS_NOT_PARAM_ACTION_IN_ACTION_CALL, paramsError);
        }
        else {
          List<Type> typeExprs = new ArrayList<Type>();
          List<Expr> exprs = null;
          exprs = (List<Expr>)term.getExpr();
          for(Expr expr : exprs) {
            Type type = (Type)expr.accept(exprChecker());
            typeExprs.add(type);
          }
//
          int i = 0;
          if(params.size() == typeExprs.size()) {
            for(NameTypePair pair : (List<NameTypePair>)params) {
              Type2 expectedU = unwrapType(pair.getType());
              Type2 foundU = unwrapType((Type)typeExprs.get(i));
              if(foundU instanceof UnknownType) {
                Object [] paramsError = {currentAction().getWord(), currentProcess().getWord(), actionDecl.getWord(), i+1};
                errorAnn = errorAnn(actionDecl, ErrorMessage.PARAM_ACTION_CALL_UNDECLARED_VAR, paramsError);
                break;
              }
              if(unify(foundU, expectedU) != SUCC) {
                Object [] paramsError = {currentAction().getWord(), currentProcess().getWord(), actionDecl.getWord(), expectedU, foundU, i+1};
                errorAnn = errorAnn(actionDecl, ErrorMessage.PARAM_ACTION_CALL_NOT_UNIFY, paramsError);
                break;
              }   
              i++;
            }
          } else {
            Object [] paramsError = {currentAction().getWord(), currentProcess().getWord(), params.size(), typeExprs.size(), actionDecl.getWord()};
            errorAnn = errorAnn(actionDecl, ErrorMessage.ACTION_CALL_DIFF_NUMBER_EXPRS, paramsError);
          }
//
        }
        break;
      case Normal :
        if(localCircTypeEnv().isParamAction(actionDecl)) {
          Object [] paramsError = {currentAction().getWord(), currentProcess().getWord(), actionDecl.getWord()};
          errorAnn = errorAnn(term, ErrorMessage.PARAM_ACTION_CALL_WITHOUT_EXPRS, paramsError);
        }
        break;
    }
    
    return errorAnn;
  }

}
