/*
 * ParaChecker.java
 *
 * Created on 15 de Junho de 2005, 20:55
 *
 * 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 java.util.Iterator;

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.base.util.*;
import net.sourceforge.czt.typecheck.circus.util.KindOfProcess;
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.z.*;
import net.sourceforge.czt.circus.ast.*;
import net.sourceforge.czt.circus.visitor.*;
import net.sourceforge.czt.circus.util.*;

/**
 * Visitor que checa os tipos dos parágrafos. Retorna sempre um objeto do 
 * tipo Signature, associando o nome do parágrafo a seus tipos.
 *
 * @author Manuela 
 */
public class ParaChecker 
  extends Checker 
  implements 
    ChannelParaVisitor,
    ChannelSetParaVisitor,
    ProcessParaVisitor,
    ActionParaVisitor,
    NameSetParaVisitor
{
    
  protected net.sourceforge.czt.typecheck.z.ParaChecker zParaChecker_;

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

  // CircusParagraph ::= Paragraph
  public Object visitTerm(Term term)
  {
    return term.accept(zParaChecker_);
  }
  
  // CircusParagraph ::= channel CDeclaration
  // ok - verificado em 25/09/2005 às 10:25
  public Object visitChannelPara(ChannelPara term) 
  {
    //the list of NameTypePairs for this paras signature
    List<NameTypePair> allPairs = new ArrayList<NameTypePair>();

    //the list of CDeclaration
    List<ChannelDecl> chanDecls = term.getChannelDecl();
        
    for (ChannelDecl chanDecl : chanDecls) {
      List<NameTypePair> pairs = (List<NameTypePair>)chanDecl.accept(declChecker());
      allPairs.addAll(pairs);
    }
    
    //create the signature for this paragraph and add it as
    //an annotation
    Signature signature = factory().createSignature(allPairs);

    addSignatureAnn(term, signature);

    return signature;
  }

  // CircusParagraph ::= chanset N == CSExpression
  // ok - verificado em 25/09/2005 às 10:25
  public Object visitChannelSetPara(ChannelSetPara term)
  {
    
    List<NameTypePair> pairs = new ArrayList<NameTypePair>();
    DeclName name = term.getDeclName();
    
    Type2 type = (Type2)term.getChannelSet().accept(exprChecker());
    
    if (!addChannelSet(name)) {  
      Object [] params = {name.getWord()};
      error(term, ErrorMessage.REDECLARED_CHANSET_NAME, params);
    }
    
    NameTypePair pair = factory().createNameTypePair(name, type);
    pairs.add(pair);
    
    Signature signature = factory().createSignature(pairs);
    addSignatureAnn(term, signature);

    return signature;
  }

  // CircusParagraph ::= ProcessDeclaration
  // ProcessDeclaration ::= process N \defs ProcessDefinition
  // ProcessDeclaration ::= process N[N+] \defs ProcessDefinition
  // ok - verificado em 25/09/2005 às 10:26
  public Object visitProcessPara(ProcessPara term)
  {
   
    List<NameTypePair> allPairs = new ArrayList<NameTypePair>();

    DeclName nameProc = term.getDeclName();
    List<DeclName> genParams = term.getGenFormals();
    CircusProcess circProc = term.getCircusProcess();
   
    // verifica se o nome já foi declarado no escopo global
    Type type = sectTypeEnv().getType(factory().createRefName(nameProc));
    
    if(type != null && !(type instanceof UnknownType)) {
      Object [] params = {nameProc.getWord()};
      error(term, ErrorMessage.REDECLARED_PROCESS_NAME, params);
    }
    
    setCurrentProcess(nameProc);
    setCurrentGenParams(genParams);

    if(!genParams.isEmpty()){
      //cria um processo genérico
      if (!addGenProcess(nameProc, genParams)) {  
        Object [] params = {nameProc.getWord()};
        error(term, ErrorMessage.REDECLARED_PROCESS_NAME, params);
      }
    } else {
      //cria um processo normal
      if (!addProcess(nameProc)) {  
        Object [] params = {nameProc.getWord()};
        error(term, ErrorMessage.REDECLARED_PROCESS_NAME, params);
      }
    }    
    
    localCircTypeEnv().enterScope();
    typeEnv().enterScope();

    //adiciona os parâmetros genéricos no escopo local do processo
    //esse método já verifica se não existem repetições entre os nomes
    addGenParamTypes(genParams);

    ProcessSignature sigProc = (ProcessSignature)circProc.accept(processChecker());
    ProcessSignature signature = cloneProcessSignature(sigProc);
    signature.setProcessName(nameProc);
    
    // pega os canais usados pelo processo e adiciona os possíveis canais implicitos
    List<NameTypePair> usedChans = localCircTypeEnv().getUsedChans();
    this.addImplicitChans(usedChans);
    getProcessInfo(nameProc).setUsedChans(usedChans);
    //

    // útil para chamadas cíclicas de ações: A \defs B e B \defs A ...
    // as regras de tipos de Circus não trata isto.
//    postActionCallCheck();
    
    typeEnv().exitScope();
    localCircTypeEnv().exitScope();
    
    ProcessType procType = factory().createProcessType();
    procType.setProcessSignature(signature);
    NameTypePair pair = factory().createNameTypePair(nameProc, procType);
    allPairs.add(pair);

    Signature sig = factory().createSignature(allPairs);
    addSignatureAnn(term, sig);

    return sig;
  }

  // PParagraph ::= N \defs ActionDefinition
  // ok - verificado em 25/09/2005 às 10:29
  public Object visitActionPara(ActionPara term)
  {
    List<NameTypePair> allPairs = new ArrayList<NameTypePair>();
    
    DeclName actionName = term.getDeclName();
    CircusAction action = term.getCircusAction();

    if(!isNewDef(actionName)) {
      Object [] params = {currentProcess().getWord(), actionName.getWord()};
      error(term, ErrorMessage.REDECLARED_DEF, params);
    }
    
    setCurrentAction(actionName);

    if(!localCircTypeEnv().addAction(actionName)) {
      Object [] params = {actionName.getWord(), currentProcess().getWord()};
      error(term, ErrorMessage.REDECLARED_ACTION_NAME, params);
    }

    typeEnv().enterScope();

    ActionSignature actionSig = (ActionSignature)action.accept(actionChecker());
    ActionSignature signature = cloneActionSignature(actionSig);
    signature.setActionName(actionName);
    
    typeEnv().exitScope();

    ActionType actionType = factory().createActionType();
    actionType.setActionSignature(signature);
    NameTypePair pair = factory().createNameTypePair(actionName, actionType);
    allPairs.add(pair);
    
    // adiciona a ação no escopo do processo
    typeEnv().add(allPairs);
    
    //seta o tipo da ação no ambiente local. Impostante essa informação
    //para postCheck
    localCircTypeEnv().setActionType(actionName, actionType);

    Signature sig = factory().createSignature(allPairs);
    addSignatureAnn(term, sig);

    setCurrentAction(null);
    
    return sig;
  }

  // PParagraph ::= nameset N == NSExpression
  //ok - verificado em 25/09/2005 às 10:30
  public Object visitNameSetPara(NameSetPara term)
  {
    List<NameTypePair> pairs = new ArrayList<NameTypePair>();
    DeclName name = term.getDeclName();
    Type2 type = (Type2)term.getNameSet().accept(exprChecker());
    
    if (!localCircTypeEnv().addNameSet(name)) {  
      Object [] params = {name.getWord()};
      error(term, ErrorMessage.REDECLARED_NAMESET_NAME, params); 
    }                       
    
    NameTypePair pair = factory().createNameTypePair(name, type);
    pairs.add(pair);

    if(!isNewDef(name)) {
      Object [] params = {currentProcess().getWord(), name.getWord()};
      error(term, ErrorMessage.REDECLARED_DEF, params);
    }

    // adiciona o conjunto de nomes no escopo do processo
    typeEnv().add(pair);
    
    Signature signature = factory().createSignature(pairs);
    addSignatureAnn(term, signature);

    return signature;
  }

  /*
   * Método auxiliar que adiciona os canais implicitos passados como parâmetro
   * no ambiente global, caso tais canais já não tenham sido declarados com
   * mesmo nome e tipos diferentes.
   * @param chans  a lista de canais implícitos a adicionar no ambiente
   */
  private void addImplicitChans(List<NameTypePair> chans) {
    for(NameTypePair chan : chans) {
      DeclName chanName = chan.getName();
      Type chanType = chan.getType();

      Type type = sectTypeEnv().getType(factory().createRefName(chanName));
      if (!sectTypeEnv().add(chanName, chanType)) {
        if (unify(unwrapType(type), unwrapType(chanType)) != SUCC) {
          // muitas vezes, porcausa da instanciação, o tipo de um canal genérico 
          // passa a ser diferente.
          if(!isGenericChannel(chanName)) {
            Object [] params = {currentProcess().getWord(), chanName.getWord()};
            error(chanName, ErrorMessage.REDECLARED_GLOBAL_NAME_WITH_DIFF_TYPE, params);
            break;
          }
        } 
        else {
          if(!isChannel(chanName)) {
            Object [] params = {chanName.getWord()};
            error(chanName, ErrorMessage.REDECLARED_GLOBAL_NAME, params);
            break;
          }
        }  
      }
      else {
        List<DeclName> genericImplicitChans = localCircTypeEnv().getGenericImplicitChans();
        if(genericImplicitChans.contains(chanName)) {
          // E OS PARAMETROS ??
          addGenChannel(chanName, chanType, null);
        } else {
          addChannel(chanName, chanType);
        }
      }
    }
  }


}
