<?php

namespace NFePHP\NFe;

/**
 * Classe principal para a comunicação com a SEFAZ
 *
 * @category  NFePHP
 * @package   NFePHP\NFe\ToolsNFe
 * @copyright Copyright (c) 2008-2015
 * @license   http://www.gnu.org/licenses/lesser.html LGPL v3
 * @author    Roberto L. Machado <linux.rlm at gmail dot com>
 * @link      http://github.com/nfephp-org/nfephp for the canonical source repository
 */

use NFePHP\Common\Base\BaseTools;
use NFePHP\Common\DateTime\DateTime;
use NFePHP\Common\LotNumber\LotNumber;
use NFePHP\Common\Strings\Strings;
use NFePHP\Common\Files;
use NFePHP\Common\Exception;
use NFePHP\Common\Dom\Dom;
use NFePHP\NFe\ReturnNFe;
use NFePHP\NFe\MailNFe;
use NFePHP\NFe\IdentifyNFe;
use NFePHP\Common\Dom\ValidXsd;
use NFePHP\Extras;

if (!defined('NFEPHP_ROOT')) {
    define('NFEPHP_ROOT', dirname(dirname(dirname(__FILE__))));
}

class ToolsNFe extends BaseTools
{
    /**
     * errrors
     *
     * @var string
     */
    public $errors = array();
    /**
     * soapDebug
     *
     * @var string
     */
    public $soapDebug = '';
    /**
     * urlPortal
     * Instância do WebService
     *
     * @var string
     */
    protected $urlPortal = 'http://www.portalfiscal.inf.br/nfe';
    /**
     * aLastRetEvent
     *
     * @var array
     */
    private $aLastRetEvent = array();
    
    /**
     * Define se salva as mensagens dos eventos em arquivo
     *
     * @var bool
     */
    private $bSalvarMensagensEvento  = true;
    /**
     * setModelo
     *
     * Ajusta o modelo da NFe 55 ou 65
     *
     * @param string $modelo
     */
    public function setModelo($modelo = '55')
    {
        //força pelo menos um modelo correto
        if ($modelo != '55' && $modelo != '65') {
            $modelo = '55';
        }
        $this->modelo = $modelo;
    }
    
    /**
     * getModelo
     * Retorna o modelo de NFe atualmente setado
     *
     * @return string
     */
    public function getModelo()
    {
        return $this->modelo;
    }
    
    /**
     * ativaContingencia
     * Ativa a contingencia SVCAN ou SVCRS conforme a
     * sigla do estado ou EPEC
     *
     * @param  string $siglaUF
     * @param  string $motivo
     * @param  string $tipo
     * @return bool
     */
    public function ativaContingencia($siglaUF = '', $motivo = '', $tipo = '')
    {
        if ($siglaUF == '' || $motivo == '') {
            return false;
        }
        if ($this->enableSVCAN || $this->enableSVCRS || $this->enableEPEC) {
            return true;
        }
        $this->motivoContingencia = $motivo;
        $this->tsContingencia = time(); // mktime() necessita de paramentos...
        $ctgList = array(
            'AC'=>'SVCAN',
            'AL'=>'SVCAN',
            'AM'=>'SVCAN',
            'AP'=>'SVCRS',
            'BA'=>'SVCRS',
            'CE'=>'SVCRS',
            'DF'=>'SVCAN',
            'ES'=>'SVCRS',
            'GO'=>'SVCRS',
            'MA'=>'SVCRS',
            'MG'=>'SVCAN',
            'MS'=>'SVCRS',
            'MT'=>'SVCRS',
            'PA'=>'SVCRS',
            'PB'=>'SVCAN',
            'PE'=>'SVCRS',
            'PI'=>'SVCRS',
            'PR'=>'SVCRS',
            'RJ'=>'SVCAN',
            'RN'=>'SVCRS',
            'RO'=>'SVCAN',
            'RR'=>'SVCAN',
            'RS'=>'SVCAN',
            'SC'=>'SVCAN',
            'SE'=>'SVCAN',
            'SP'=>'SVCAN',
            'TO'=>'SVCAN'
        );
        $ctg = $ctgList[$siglaUF];
        
        $this->enableSVCAN = false;
        $this->enableSVCRS = false;
        $this->enableEPEC = false;

        if ($tipo == 'EPEC') {
            $this->enableEPEC = true;
        } else {
            if ($ctg == 'SVCAN') {
                $this->enableSVCAN = true;
            } elseif ($ctg == 'SVCRS') {
                $this->enableSVCRS = true;
            }
        }
        $aCont = array(
            'motivo' => $this->motivoContingencia,
            'ts' => $this->tsContingencia,
            'SVCAN' => $this->enableSVCAN,
            'SVCRS' => $this->enableSVCRS,
            'EPEC' => $this->enableEPEC
        );
        $strJson = json_encode($aCont);
        $filename = NFEPHP_ROOT
            . DIRECTORY_SEPARATOR
            . 'config'
            . DIRECTORY_SEPARATOR
            . $this->aConfig['cnpj']
            . '_contingencia.json';
        file_put_contents($filename, $strJson);
        return true;
    }
    
    /**
     * desativaContingencia
     * Desliga opção de contingência
     *
     * @return boolean
     */
    public function desativaContingencia()
    {
        $this->enableSVCAN = false;
        $this->enableSVCRS = false;
        $this->enableEPEC = false;
        $this->tsContingencia = 0;
        $this->motivoContingencia = '';
        $filename = NFEPHP_ROOT
            . DIRECTORY_SEPARATOR
            . 'config'
            . DIRECTORY_SEPARATOR
            . $this->aConfig['cnpj']
            . '_contingencia.json';
        return Files\FilesFolders::removeFile($filename);
    }
    
    /**
     * imprime
     * Imprime o documento eletrônico (NFe, CCe, Inut.)
     *
     * @param  string $pathXml
     * @param  string $pathDestino
     * @param  string $printer
     * @return string
     */
    public function imprime($pathXml = '', $pathDestino = '', $printer = '')
    {
        //TODO : falta implementar esse método para isso é necessária a classe
        //PrintNFe
        return "$pathXml $pathDestino $printer";
    }
    
    /**
     * enviaMail
     * Envia a NFe por email aos destinatários
     * Caso $aMails esteja vazio serão obtidos os email do destinatário  e
     * os emails que estiverem registrados nos campos obsCont do xml
     *
     * @param  string  $pathXml
     * @param  array   $aMails
     * @param  string  $templateFile path completo ao arquivo template html do corpo do email
     * @param  boolean $comPdf       se true o sistema irá renderizar o DANFE e anexa-lo a mensagem
     * @param  string  $pathPdf
     * @return boolean
     * @throws Exception\RuntimeException
     */
    public function enviaMail($pathXml = '', $aMails = array(), $templateFile = '', $comPdf = false, $pathPdf = '')
    {
        $mail = new MailNFe($this->aMailConf);
        // Se não for informado o caminho do PDF, monta um através do XML
        if ($comPdf && $this->modelo == '55' && $pathPdf == '') {
            $docxml = Files\FilesFolders::readFile($pathXml);
            $danfe = new Extras\Danfe($docxml, 'P', 'A4', $this->aDocFormat['pathLogoFile'], 'I', '');
            $id = $danfe->montaDANFE();
            $pathPdf = $this->aConfig['pathNFeFiles']
                . DIRECTORY_SEPARATOR
                . $this->ambiente
                . DIRECTORY_SEPARATOR
                . 'pdf'
                . DIRECTORY_SEPARATOR
                . $id . '-danfe.pdf';
            $pdf = $danfe->printDANFE($pathPdf, 'F');
        }
        if ($mail->envia($pathXml, $aMails, $comPdf, $pathPdf) === false) {
            throw new Exception\RuntimeException('Email não enviado. '.$mail->error);
        }
        return true;
    }
    
    /**
     * addB2B
     * Adiciona tags de comunicação B2B, especialmente ANFAVEA
     *
     * @param  string $pathNFefile
     * @param  string $pathB2Bfile
     * @param  string $tagB2B
     * @return string
     * @throws Exception\InvalidArgumentException
     * @throws Exception\RuntimeException
     */
    public function addB2B($pathNFefile = '', $pathB2Bfile = '', $tagB2B = '')
    {
        if (! is_file($pathNFefile) || ! is_file($pathB2Bfile)) {
            $msg = "Algum dos arquivos não foi localizado no caminho indicado ! $pathNFefile ou $pathB2Bfile";
            throw new Exception\InvalidArgumentException($msg);
        }
        if ($tagB2B == '') {
            //padrão anfavea
            $tagB2B = 'NFeB2BFin';
        }
        $docnfe = new Dom();
        $docnfe->loadXMLFile($pathNFefile);
        $nodenfe = $docnfe->getNode('nfeProc', 0);
        if ($nodenfe == '') {
            $msg = "O arquivo indicado como NFe não está protocolado ou não é uma NFe!!";
            throw new Exception\RuntimeException($msg);
        }
        //carrega o arquivo B2B
        $docb2b = new Dom();
        $docb2b->loadXMLFile($pathNFefile);
        $nodeb2b = $docnfe->getNode($tagB2B, 0);
        if ($nodeb2b == '') {
            $msg = "O arquivo indicado como B2B não contên a tag requerida!!";
            throw new Exception\RuntimeException($msg);
        }
        //cria a NFe processada com a tag do protocolo
        $procb2b = new Dom();
        //cria a tag nfeProc
        $nfeProcB2B = $procb2b->createElement('nfeProcB2B');
        $procb2b->appendChild($nfeProcB2B);
        //inclui a tag NFe
        $node1 = $procb2b->importNode($nodenfe, true);
        $nfeProcB2B->appendChild($node1);
        //inclui a tag NFeB2BFin
        $node2 = $procb2b->importNode($nodeb2b, true);
        $nfeProcB2B->appendChild($node2);
        //salva o xml como string em uma variável
        $nfeb2bXML = $procb2b->saveXML();
        //remove as informações indesejadas
        $nfeb2bXMLString = str_replace(array("\n","\r","\s"), '', $nfeb2bXML);
        return (string) $nfeb2bXMLString;
    }
    
    /**
     * addProtocolo
     * Adiciona o protocolo de autorização de uso da NFe
     * NOTA: exigência da SEFAZ, a nota somente é válida com o seu respectivo protocolo
     *
     * @param  string  $pathNFefile
     * @param  string  $pathProtfile
     * @param  boolean $saveFile
     * @return string
     * @throws Exception\RuntimeException
     */
    public function addProtocolo($pathNFefile = '', $pathProtfile = '', $saveFile = false)
    {
        //carrega a NFe
        $docnfe = new Dom();
        
        if (file_exists($pathNFefile)) {
            //carrega o XML pelo caminho do arquivo informado
            $docnfe->loadXMLFile($pathNFefile);
        } else {
            //carrega o XML pelo conteúdo
            $docnfe->loadXMLString($pathNFefile);
        }
        
        $nodenfe = $docnfe->getNode('NFe', 0);
        if ($nodenfe == '') {
            $msg = "O arquivo indicado como NFe não é um xml de NFe!";
            throw new Exception\RuntimeException($msg);
        }
        if ($docnfe->getNode('Signature') == '') {
            $msg = "A NFe não está assinada!";
            throw new Exception\RuntimeException($msg);
        }
        //carrega o protocolo
        $docprot = new Dom();
        
        if (file_exists($pathProtfile)) {
            //carrega o XML pelo caminho do arquivo informado
            $docprot->loadXMLFile($pathProtfile);
        } else {
            //carrega o XML pelo conteúdo
            $docprot->loadXMLString($pathProtfile);
        }
        
        $nodeprots = $docprot->getElementsByTagName('protNFe');
        if ($nodeprots->length == 0) {
            $msg = "O arquivo indicado não contem um protocolo de autorização!";
            throw new Exception\RuntimeException($msg);
        }
        //carrega dados da NFe
        $tpAmb = $docnfe->getNodeValue('tpAmb');
        $anomes = date(
            'Ym',
            DateTime::convertSefazTimeToTimestamp($docnfe->getNodeValue('dhEmi'))
        );
        $infNFe = $docnfe->getNode("infNFe", 0);
        $versao = $infNFe->getAttribute("versao");
        $chaveId = $infNFe->getAttribute("Id");
        $chaveNFe = preg_replace('/[^0-9]/', '', $chaveId);
        $digValueNFe = $docnfe->getNodeValue('DigestValue');
        //carrega os dados do protocolo
        for ($i = 0; $i < $nodeprots->length; $i++) {
            $nodeprot = $nodeprots->item($i);
            $protver = $nodeprot->getAttribute("versao");
            $chaveProt = $nodeprot->getElementsByTagName("chNFe")->item(0)->nodeValue;
            $digValueProt = ($nodeprot->getElementsByTagName("digVal")->length)
                ? $nodeprot->getElementsByTagName("digVal")->item(0)->nodeValue
                : '';
            $infProt = $nodeprot->getElementsByTagName("infProt")->item(0);
            if ($digValueNFe == $digValueProt && $chaveNFe == $chaveProt) {
                break;
            }
        }
        if ($digValueNFe != $digValueProt) {
            $msg = "Inconsistência! O DigestValue da NFe não combina com o do digVal do protocolo indicado!";
            throw new Exception\RuntimeException($msg);
        }
        if ($chaveNFe != $chaveProt) {
            $msg = "O protocolo indicado pertence a outra NFe. Os números das chaves não combinam !";
            throw new Exception\RuntimeException($msg);
        }
        //cria a NFe processada com a tag do protocolo
        $procnfe = new \DOMDocument('1.0', 'utf-8');
        $procnfe->formatOutput = false;
        $procnfe->preserveWhiteSpace = false;
        //cria a tag nfeProc
        $nfeProc = $procnfe->createElement('nfeProc');
        $procnfe->appendChild($nfeProc);
        //estabele o atributo de versão
        $nfeProcAtt1 = $nfeProc->appendChild($procnfe->createAttribute('versao'));
        $nfeProcAtt1->appendChild($procnfe->createTextNode($protver));
        //estabelece o atributo xmlns
        $nfeProcAtt2 = $nfeProc->appendChild($procnfe->createAttribute('xmlns'));
        $nfeProcAtt2->appendChild($procnfe->createTextNode($this->urlPortal));
        //inclui a tag NFe
        $node = $procnfe->importNode($nodenfe, true);
        $nfeProc->appendChild($node);
        //cria tag protNFe
        $protNFe = $procnfe->createElement('protNFe');
        $nfeProc->appendChild($protNFe);
        //estabele o atributo de versão
        $protNFeAtt1 = $protNFe->appendChild($procnfe->createAttribute('versao'));
        $protNFeAtt1->appendChild($procnfe->createTextNode($versao));
        //cria tag infProt
        $nodep = $procnfe->importNode($infProt, true);
        $protNFe->appendChild($nodep);
        //salva o xml como string em uma variável
        $procXML = $procnfe->saveXML();
        //remove as informações indesejadas
        $procXML = Strings::clearProt($procXML);
        if ($saveFile) {
            $filename = "{$chaveNFe}-protNFe.xml";
            $this->zGravaFile(
                'nfe',
                $tpAmb,
                $filename,
                $procXML,
                'enviadas'.DIRECTORY_SEPARATOR.'aprovadas',
                $anomes
            );
        }
        return $procXML;
    }

    /**
     * addCancelamento
     * Adiciona a tga de cancelamento a uma NFe já autorizada
     * NOTA: não é requisito da SEFAZ, mas auxilia na identificação das NFe que foram canceladas
     *
     * @param  string $pathNFefile
     * @param  string $pathCancfile
     * @param  bool   $saveFile
     * @return string
     * @throws Exception\RuntimeException
     */
    public function addCancelamento($pathNFefile = '', $pathCancfile = '', $saveFile = false)
    {
        $procXML = '';
        //carrega a NFe
        $docnfe = new Dom();
        $docnfe->loadXMLFile($pathNFefile);
        $nodenfe = $docnfe->getNode('NFe', 0);
        if ($nodenfe == '') {
            $msg = "O arquivo indicado como NFe não é um xml de NFe!";
            throw new Exception\RuntimeException($msg);
        }
        $proNFe = $docnfe->getNode('protNFe');
        if ($proNFe == '') {
            $msg = "A NFe não está protocolada ainda!!";
            throw new Exception\RuntimeException($msg);
        }
        $chaveNFe = $proNFe->getElementsByTagName('chNFe')->item(0)->nodeValue;
        //$nProtNFe = $proNFe->getElementsByTagName('nProt')->item(0)->nodeValue;
        $tpAmb = $docnfe->getNodeValue('tpAmb');
        $anomes = date(
            'Ym',
            DateTime::convertSefazTimeToTimestamp($docnfe->getNodeValue('dhEmi'))
        );
        //carrega o cancelamento
        //pode ser um evento ou resultado de uma consulta com multiplos eventos
        $doccanc = new Dom();
        $doccanc->loadXMLFile($pathCancfile);
        $retEvento = $doccanc->getElementsByTagName('retEvento')->item(0);
        $eventos = $retEvento->getElementsByTagName('infEvento');
        foreach ($eventos as $evento) {
            //evento
            $cStat = $evento->getElementsByTagName('cStat')->item(0)->nodeValue;
            $tpAmb = $evento->getElementsByTagName('tpAmb')->item(0)->nodeValue;
            $chaveEvento = $evento->getElementsByTagName('chNFe')->item(0)->nodeValue;
            $tpEvento = $evento->getElementsByTagName('tpEvento')->item(0)->nodeValue;
            //$nProtEvento = $evento->getElementsByTagName('nProt')->item(0)->nodeValue;
            //verifica se conferem os dados
            //cStat = 135 ==> evento homologado
            //cStat = 136 ==> vinculação do evento à respectiva NF-e prejudicada
            //cStat = 155 ==> Cancelamento homologado fora de prazo
            //tpEvento = 110111 ==> Cancelamento
            //chave do evento == chave da NFe
            //protocolo do evneto ==  protocolo da NFe
            if (($cStat == '135' || $cStat == '136' || $cStat == '155')
                && $tpEvento == '110111'
                && $chaveEvento == $chaveNFe
            ) {
                $proNFe->getElementsByTagName('cStat')->item(0)->nodeValue = '101';
                $proNFe->getElementsByTagName('xMotivo')->item(0)->nodeValue = 'Cancelamento de NF-e homologado';
                $procXML = $docnfe->saveXML();
                //remove as informações indesejadas
                $procXML = Strings::clearProt($procXML);
                if ($saveFile) {
                    $filename = "$chaveNFe-protNFe.xml";
                    $this->zGravaFile(
                        'nfe',
                        $tpAmb,
                        $filename,
                        $procXML,
                        'canceladas',
                        $anomes
                    );
                }
                break;
            }
        }
        return (string) $procXML;
    }
    
    /**
     * verificaValidade
     * Verifica a validade de uma NFe recebida
     *
     * @param  string $pathXmlFile
     * @param  array  $aRetorno
     * @return boolean
     * @throws Exception\InvalidArgumentException
     */
    public function verificaValidade($pathXmlFile = '', &$aRetorno = array())
    {
        $aRetorno = array();
        if (!file_exists($pathXmlFile)) {
            $msg = "Arquivo não localizado!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        //carrega a NFe
        $xml = Files\FilesFolders::readFile($pathXmlFile);
        $this->oCertificate->verifySignature($xml, 'infNFe');
        //obtem o chave da NFe
        $docnfe = new Dom();
        $docnfe->loadXMLFile($pathXmlFile);
        $tpAmb = $docnfe->getNodeValue('tpAmb');
        $chNFe  = $docnfe->getChave('infNFe');
        $this->sefazConsultaChave($chNFe, $tpAmb, $aRetorno);
        if ($aRetorno['cStat'] != '100' && $aRetorno['cStat'] != '150') {
            return false;
        }
        return true;
    }
    
    /**
     * assina
     * Assina uma NFe
     *
     * @param  string  $xml
     * @param  boolean $saveFile
     * @return string
     * @throws Exception\RuntimeException
     */
    public function assina($xml = '', $saveFile = false)
    {
        $xmlSigned = $this->assinaDoc($xml, 'nfe', 'infNFe', $saveFile);
        $dom = new Dom();
        $dom->loadXMLString($xmlSigned);
        $modelo = $dom->getValue($dom, 'mod');
        $oldmod = $this->modelo;
        $this->modelo = $modelo;
        if ($this->modelo == 65) {
            //descomentar essa linha após 03/11/2015 conforme NT 2015.002
            //ou quando for habilitada essa TAG no XML da NFCe
            //para incluir o QRCode no corpo da NFCe
            $xmlSigned = $this->zPutQRTag($dom, $saveFile);
        }
        $this->modelo = $oldmod;
        return $xmlSigned;
    }
    
    /**
     * zPutQRTag
     * Monta a URI para o QRCode e coloca a tag
     * no xml já assinado
     *
     * @param  Dom $dom
     * @return string
     * NOTA: O Campo QRCode está habilitado para uso a partir de
     *       01/10/2015 homologação
     *       03/11/2015 Produção
     */
    protected function zPutQRTag(Dom $dom, $saveFile)
    {
        //pega os dados necessários para a montagem da URI a partir do xml
        $nfe = $dom->getNode('NFe');
        $ide = $dom->getNode('ide');
        $dest = $dom->getNode('dest');
        $icmsTot = $dom->getNode('ICMSTot');
        $signedInfo  = $dom->getNode('SignedInfo');
        $chNFe = $dom->getChave('infNFe');
        $cUF = $dom->getValue($ide, 'cUF');
        $tpAmb = $dom->getValue($ide, 'tpAmb');
        $dhEmi = $dom->getValue($ide, 'dhEmi');
        $cDest = '';
        if (!empty($dest)) {
            //pode ser CNPJ , CPF ou idEstrageiro
            $cDest = $dom->getValue($dest, 'CNPJ');
            if ($cDest == '') {
                $cDest = $dom->getValue($dest, 'CPF');
                if ($cDest == '') {
                    $cDest = $dom->getValue($dest, 'idEstrangeiro');
                }
            }
        }
        $vNF = $dom->getValue($icmsTot, 'vNF');
        $vICMS = $dom->getValue($icmsTot, 'vICMS');
        $digVal = $dom->getValue($signedInfo, 'DigestValue');
        $token = $this->aConfig['tokenNFCe'];
        $idToken = $this->aConfig['tokenNFCeId'];
        $versao = '100';
        /*
         *Pega a URL para consulta do QRCode do estado emissor, 
         *essa url está em nfe_ws3_mode65.xml, em tese essa url 
         *NÃO É uma WebService, é simplismente uma página para 
         *consulta do QRCode via parametros GET, percebe-se que 
         *em todas as SEFAZ o endereço de consulta do QRCode se
         *difere do padrão de endereço das WS. 
         *Esse é um serviço para ser utilizado pelo consumidor...
         *NOTA: Sem o endereço de consulta não é possível gerar o QR-Code!!!
        */
        //carrega serviço
        $servico = 'NfeConsultaQR';
        $siglaUF = $this->zGetSigla($cUF);
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );
        if ($this->urlService == '') {
            $this->errors[] = "A consulta por QRCode não está disponível na SEFAZ $siglaUF!!!";
            return $dom->saveXML();
        }
        $url = $this->urlService;
        //usa a função zMakeQRCode para gerar a string da URI
        $qrcode = $this->zMakeQRCode(
            $chNFe,
            $url,
            $tpAmb,
            $dhEmi,
            $vNF,
            $vICMS,
            $digVal,
            $token,
            $cDest,
            $idToken,
            $versao
        );
        if ($qrcode == '') {
            return $dom->saveXML();
        }
        //inclui a TAG NFe/infNFeSupl com o qrcode
        $infNFeSupl = $dom->createElement("infNFeSupl");
        $nodeqr = $infNFeSupl->appendChild($dom->createElement('qrCode'));
        $nodeqr->appendChild($dom->createCDATASection($qrcode));
        $signature = $dom->getElementsByTagName('Signature')->item(0);
        $nfe->insertBefore($infNFeSupl, $signature);
        $dom->formatOutput = false;
        $xmlSigned = $dom->saveXML();
        //salva novamente o xml assinado e agora com o QRCode
        if ($saveFile) {
            $anomes = date(
                'Ym',
                DateTime::convertSefazTimeToTimestamp($dhEmi)
            );
            $filename = "$chNFe-nfe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $xmlSigned, 'assinadas', $anomes);
        }
        //retorna a string com o xml assinado e com o QRCode
        return $xmlSigned;
    }
    
    /**
     * sefazEnviaLote
     * Solicita a autorização de uso de Lote de NFe
     *
     * @param    array   $aXml
     * @param    string  $tpAmb
     * @param    string  $idLote
     * @param    array   $aRetorno
     * @param    int     $indSinc
     * @param    boolean $compactarZip
     * @return   string
     * @throws   Exception\InvalidArgumentException
     * @throws   Exception\RuntimeException
     * @internal function zLoadServico (Common\Base\BaseTools)
     */
    public function sefazEnviaLote(
        $aXml,
        $tpAmb = '2',
        $idLote = '',
        &$aRetorno = array(),
        $indSinc = 0,
        $compactarZip = false,
        $salvarMensagens = true
    ) {
        $sxml = $aXml;
        
        if (empty($aXml)) {
            $msg = "Pelo menos uma NFe deve ser informada.";
            throw new Exception\InvalidArgumentException($msg);
        }
        if (is_array($aXml)) {
            if (count($aXml) > 1) {
                //multiplas nfes, não pode ser sincrono
                $indSinc = 0;
            }
            $sxml = implode("", $sxml);
        }
        $sxml = preg_replace("/<\?xml.*\?>/", "", $sxml);
        $siglaUF = $this->aConfig['siglaUF'];
        
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        if ($idLote == '') {
            $idLote = LotNumber::geraNumLote(15);
        }
        //carrega serviço
        $servico = 'NFeAutorizacao';
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );
        
        /*
        if ($this->urlService == '') {
            $msg = "O envio de lote não está disponível na SEFAZ $siglaUF!!!";
            throw new Exception\RuntimeException($msg);
        }
        */
        //montagem dos dados da mensagem SOAP
        $cons = "<enviNFe xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
                . "<idLote>$idLote</idLote>"
                . "<indSinc>$indSinc</indSinc>"
                . "$sxml"
                . "</enviNFe>";
        //valida a mensagem com o xsd
        //validar mensagem com xsd
        //if (! $this->validarXml($cons)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        //montagem dos dados da mensagem SOAP
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";
        $method = $this->urlMethod;
        if ($compactarZip) {
            $gzdata = base64_encode(gzencode($cons, 9, FORCE_GZIP));
            $body = "<nfeDadosMsgZip xmlns=\"$this->urlNamespace\">$gzdata</nfeDadosMsgZip>";
            $method = $this->urlMethod."Zip";
        }
        //envia a solicitação via SOAP
        $this->urlHeader = '';
        
        try{
        $retorno = $this->oSoap->send($this->urlService, $this->urlNamespace, $this->urlHeader, $body, $method);
        }catch (\Exception $e) {
            var_dump($e->getMessage());
        }
        
        $this->soapDebug = $this->oSoap->soapDebug;
        //salva mensagens
        // if ($salvarMensagens) {
        
            $lastMsg = $this->oSoap->lastMsg;
            $filename = "$idLote-enviNFe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
            
            $filename = "$idLote-retEnviNFe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        // }
        
        //tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        //caso o envio seja recebido com sucesso mover a NFe da pasta
        //das assinadas para a pasta das enviadas

        return (string) $retorno;
    }
    
    /**
     * sefazConsultaRecibo
     * Consulta a situação de um Lote de NFe enviadas pelo recibo desse envio
     *
     * @param    string $recibo
     * @param    string $tpAmb
     * @param    array  $aRetorno
     * @return   string
     * @throws   Exception\InvalidArgumentException
     * @throws   Exception\RuntimeException
     * @internal function zLoadServico (Common\Base\BaseTools)
     */
    public function sefazConsultaRecibo310($recibo = '', $tpAmb = '2', &$aRetorno = array(), $saveMensagens = true)
    {
        if ($recibo == '') {
            $msg = "Deve ser informado um recibo.";
            throw new Exception\InvalidArgumentException($msg);
        }
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        $siglaUF = $this->aConfig['siglaUF'];
        //carrega serviço
        $servico = 'NfeRetAutorizacao';
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );
        if ($this->urlService == '') {
            $msg = "A consulta de NFe não está disponível na SEFAZ $siglaUF!!!";
            throw new Exception\RuntimeException($msg);
        }
        $cons = "<consReciNFe xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
            . "<tpAmb>$tpAmb</tpAmb>"
            . "<nRec>$recibo</nRec>"
            . "</consReciNFe>";
        //validar mensagem com xsd
        //if (! $this->validarXml($cons)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        //montagem dos dados da mensagem SOAP
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";
        
		//envia a solicitação via SOAP
        $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
        );
        $this->soapDebug = $this->oSoap->soapDebug;
        //salva mensagens
        if ($saveMensagens) {
            $lastMsg = $this->oSoap->lastMsg;
            $filename = "$recibo-consReciNFe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
            $filename = "$recibo-retConsReciNFe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        }
        //tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        //podem ser retornados nenhum, um ou vários protocolos
        //caso existam protocolos protocolar as NFe e movelas-las para a
        //pasta enviadas/aprovadas/anomes
        return (string) $retorno;
    }




    public function sefazConsultaRecibo($recibo = '', $tpAmb = '2', &$aRetorno = array(), $saveMensagens = true)
    {
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        /*
        if ($cnpj == '') {
            $cnpj = $this->aConfig['cnpj'];
        }
        */

        $siglaUF = $this->aConfig['siglaUF'];
        //carrega serviço
        $servico = 'NFeRetAutorizacao';
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );

        if ($this->urlService == '') {
            $msg = "O status não está disponível na SEFAZ !!!";
            throw new Exception\RuntimeException($msg);
        }

        $cons = "<consReciNFe xmlns=\"$this->urlPortal\" versao=\"4.00\">"
            . "<tpAmb>$tpAmb</tpAmb>"
            . "<nRec>$recibo</nRec>"
            . "</consReciNFe>";
            
        //validar mensagem com xsd
        //if (! $this->validarXml($cons)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        //montagem dos dados da mensagem SOAP
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";

        $this->urlHeader = '';

        /*
        $this->urlHeader = '<nfeCabecMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NfeRetAutorizacao"></nfeCabecMsg>';
        header('Content-type: application/json');
        header('Content-type: text/html');
        */
        
        try{
            //consome o webservice e verifica o retorno do SOAP
            $retorno = $this->oSoap->send(
                $this->urlService,
                $this->urlNamespace,
                $this->urlHeader,
                $body,
                $this->urlMethod
            );
        }catch (\Exception $e) {
            var_dump($e->getMessage());
        }
        
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
    
        $filename = "_BODY-nfeRetAutorizacaoLote_Send.txt";
        $this->zGravaFile('nfe', $tpAmb, $filename, $body);
        
        $filename = "$recibo-nfeRetAutorizacaoLote_Return.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        
        //tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        return (string) $retorno;
    }


    
    /**
     * sefazConsultaChave
     * Consulta o status da NFe pela chave de 44 digitos
     *
     * @param    string $chave
     * @param    string $tpAmb
     * @param    array  $aRetorno
     * @return   string
     * @throws   Exception\InvalidArgumentException
     * @throws   Exception\RuntimeException
     * @internal function zLoadServico (Common\Base\BaseTools)
     */
    public function sefazConsultaChave($chave = '', $tpAmb = '2', &$aRetorno = array(), $salvaMensagens = true)
    {
        $chNFe = preg_replace('/[^0-9]/', '', $chave);
        if (strlen($chNFe) != 44) {
            $msg = "Uma chave de 44 dígitos da NFe deve ser passada.";
            throw new Exception\InvalidArgumentException($msg);
        }
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        $cUF = substr($chNFe, 0, 2);
        
        $siglaUF = $this->zGetSigla($cUF);
        //carrega serviço
        $servico = 'NFeConsultaProtocolo';
        
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );
        
        if ($this->urlService == '') {
            $msg = "A consulta de NFe não está disponível na SEFAZ $siglaUF!!!";
            throw new Exception\RuntimeException($msg);
        }
        $cons = "<consSitNFe xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
                . "<tpAmb>$tpAmb</tpAmb>"
                . "<xServ>CONSULTAR</xServ>"
                . "<chNFe>$chNFe</chNFe>"
                . "</consSitNFe>";
        //validar mensagem com xsd
        //if (! $this->validarXml($cons)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        //montagem dos dados da mensagem SOAP
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";
        //envia a solicitação via SOAP
        
        try{
            $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
             );
        }catch (\Exception $e) {
            var_dump($e->getMessage());
        }
        
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
        
        //salva mensagens
        if ($salvaMensagens) {
            $filename = "$chNFe-consSitNFe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
            
            $filename = "$chNFe-retConsSitNFe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        }
        //tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
                
        return (string) $retorno;
    }

    /**
     * sefazInutiliza
     * Solicita a inutilização de uma ou uma sequencia de NFe
     * de uma determinada série
     *
     * @param    integer $nSerie
     * @param    integer $nIni
     * @param    integer $nFin
     * @param    string  $xJust
     * @param    string  $tpAmb
     * @param    array   $aRetorno
     * @return   string
     * @internal param string $modelo
     * @internal function zLoadServico (Common\Base\BaseTools)
     */
    public function sefazInutiliza(
        $nSerie = 1,
        $nIni = 0,
        $nFin = 0,
        $xJust = '',
        $tpAmb = '2',
        &$aRetorno = array(),
        $salvarMensagens = true
    ) {
        $xJust = Strings::cleanString($xJust);
        $nSerie = (integer) $nSerie;
        $nIni = (integer) $nIni;
        $nFin = (integer) $nFin;
        $this->zValidParamInut($xJust, $nSerie, $nIni, $nFin);
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        //monta serviço
        $siglaUF = $this->aConfig['siglaUF'];
        //carrega serviço
        $servico = 'NFeInutilizacao';
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );
        if ($this->urlService == '') {
            $msg = "A inutilização não está disponível na SEFAZ $siglaUF!!!";
            throw new Exception\RuntimeException($msg);
        }
        //montagem dos dados da mensagem SOAP
        $cnpj = $this->aConfig['cnpj'];
        $sAno = (string) date('y');
        $sSerie = str_pad($nSerie, 3, '0', STR_PAD_LEFT);
        $sInicio = str_pad($nIni, 9, '0', STR_PAD_LEFT);
        $sFinal = str_pad($nFin, 9, '0', STR_PAD_LEFT);
        $idInut = "ID".$this->urlcUF.$sAno.$cnpj.$this->modelo.$sSerie.$sInicio.$sFinal;
        //limpa os caracteres indesejados da justificativa
        $xJust = Strings::cleanString($xJust);
        //montagem do corpo da mensagem
        $cons = "<inutNFe xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
                . "<infInut Id=\"$idInut\">"
                . "<tpAmb>$tpAmb</tpAmb>"
                . "<xServ>INUTILIZAR</xServ>"
                . "<cUF>$this->urlcUF</cUF>"
                . "<ano>$sAno</ano>"
                . "<CNPJ>$cnpj</CNPJ>"
                . "<mod>$this->modelo</mod>"
                . "<serie>$nSerie</serie>"
                . "<nNFIni>$nIni</nNFIni>"
                . "<nNFFin>$nFin</nNFFin>"
                . "<xJust>$xJust</xJust>"
                . "</infInut></inutNFe>";
        //assina a lsolicitação de inutilização
        $signedMsg = $this->oCertificate->signXML($cons, 'infInut');
        $signedMsg = Strings::clearXml($signedMsg, true);
        //valida a mensagem com o xsd
        //if (! $this->zValidMessage($cons, 'nfe', 'inutNFe', $version)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$signedMsg</nfeDadosMsg>";
        //envia a solicitação via SOAP
        $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
        );
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
        //salva mensagens
        if ($salvarMensagens) {
            $filename = "$sAno-$this->modelo-$sSerie-".$sInicio."_".$sFinal."-inutNFe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
            $filename = "$sAno-$this->modelo-$sSerie-".$sInicio."_".$sFinal."-retInutNFe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        }
        
        //tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        if ($aRetorno['cStat'] == '102') {
            $retorno = $this->zAddProtMsg('ProcInutNFe', 'inutNFe', $signedMsg, 'retInutNFe', $retorno);
            if ($salvarMensagens) {
                $filename = "$sAno-$this->modelo-$sSerie-".$sInicio."_".$sFinal."-procInutNFe.xml";
                $this->zGravaFile('nfe', $tpAmb, $filename, $retorno, 'inutilizadas');
            }
        }
        return (string) $retorno;
    }
    
    /**
     * zAddProtMsg
     *
     * @param  string $tagproc
     * @param  string $tagmsg
     * @param  string $xmlmsg
     * @param  string $tagretorno
     * @param  string $xmlretorno
     * @return string
     */
    protected function zAddProtMsg($tagproc, $tagmsg, $xmlmsg, $tagretorno, $xmlretorno)
    {
        $doc = new Dom();
        $doc->loadXMLString($xmlmsg);
        $nodedoc = $doc->getNode($tagmsg, 0);
        $procver = $nodedoc->getAttribute("versao");
        $procns = $nodedoc->getAttribute("xmlns");
        
        $doc1 = new Dom();
        $doc1->loadXMLString($xmlretorno);
        $nodedoc1 = $doc1->getNode($tagretorno, 0);
        
        $proc = new \DOMDocument('1.0', 'utf-8');
        $proc->formatOutput = false;
        $proc->preserveWhiteSpace = false;
        //cria a tag nfeProc
        $procNode = $proc->createElement($tagproc);
        $proc->appendChild($procNode);
        //estabele o atributo de versão
        $procNodeAtt1 = $procNode->appendChild($proc->createAttribute('versao'));
        $procNodeAtt1->appendChild($proc->createTextNode($procver));
        //estabelece o atributo xmlns
        $procNodeAtt2 = $procNode->appendChild($proc->createAttribute('xmlns'));
        $procNodeAtt2->appendChild($proc->createTextNode($procns));
        //inclui a tag inutNFe
        $node = $proc->importNode($nodedoc, true);
        $procNode->appendChild($node);
        //inclui a tag retInutNFe
        $node = $proc->importNode($nodedoc1, true);
        $procNode->appendChild($node);
        //salva o xml como string em uma variável
        $procXML = $proc->saveXML();
        //remove as informações indesejadas
        $procXML = Strings::clearProt($procXML);
        return $procXML;
    }
    
    /**
     * zValidParamInut
     *
     * @param  string $xJust
     * @param  int    $nSerie
     * @param  int    $nIni
     * @param  int    $nFin
     * @throws Exception\InvalidArgumentException
     */
    private function zValidParamInut($xJust, $nSerie, $nIni, $nFin)
    {
        $msg = '';
        //valida dos dados de entrada
        if (strlen($xJust) < 15 || strlen($xJust) > 255) {
            $msg = "A justificativa deve ter entre 15 e 255 digitos!!";
        } elseif ($nSerie < 0 || $nSerie > 999) {
            $msg = "O campo serie está errado: $nSerie!!";
        } elseif ($nIni < 1 || $nIni > 1000000000) {
            $msg = "O campo numero inicial está errado: $nIni!!";
        } elseif ($nFin < 1 || $nFin > 1000000000) {
            $msg = "O campo numero final está errado: $nFin!!";
        } elseif ($this->enableSVCRS || $this->enableSVCAN) {
            $msg = "A inutilização não pode ser feita em contingência!!";
        }
        if ($msg != '') {
            throw new Exception\InvalidArgumentException($msg);
        }
    }
    
    /**
     * sefazCadastro
     * Busca os dados cadastrais de um emitente de NFe
     * NOTA: Nem todas as Sefaz disponibilizam esse serviço
     *
     * @param    string $siglaUF  sigla da UF da empresa que queremos consultar
     * @param    string $tpAmb
     * @param    string $cnpj     numero do CNPJ da empresa a ser consultada
     * @param    string $iest     numero da Insc. Est. da empresa a ser consultada
     * @param    string $cpf      CPF da pessoa física a ser consultada
     * @param    array  $aRetorno aRetorno retorno da resposta da SEFAZ em array
     * @return   string XML de retorno do SEFAZ
     * @throws   Exception\RuntimeException
     * @throws   Exception\InvalidArgumentException
     * @internal function zLoadServico (Common\Base\BaseTools)
     */
    public function sefazCadastro($siglaUF = '', $tpAmb = '2', $cnpj = '', $iest = '', $cpf = '', &$aRetorno = array())
    {
        $cnpj = preg_replace('/[^0-9]/', '', $cnpj);
        $cpf = preg_replace('/[^0-9]/', '', $cpf);
        $iest = trim($iest);
        //se nenhum critério é satisfeito
        if ($cnpj == '' && $iest == '' && $cpf == '') {
            //erro nao foi passado parametro de filtragem
            $msg = "Na consulta de cadastro, pelo menos um desses dados deve ser"
                    . " fornecido CNPJ, CPF ou IE !!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        //selecionar o criterio de filtragem CNPJ ou IE ou CPF
        if ($cnpj != '') {
            $filtro = "<CNPJ>$cnpj</CNPJ>";
            $txtFile = "CNPJ_$cnpj";
        } elseif ($iest != '') {
            $filtro = "<IE>$iest</IE>";
            $txtFile = "IE_$iest";
        } else {
            $filtro = "<CPF>$cpf</CPF>";
            $txtFile = "CPF_$cpf";
        }
        //carrega serviço
        $servico = 'NFeConsultaCadastro';
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );
        if ($this->urlService == '') {
            $msg = "A consulta de Cadastros não está disponível na SEFAZ $siglaUF!!!";
            throw new Exception\RuntimeException($msg);
        }
        $cons = "<ConsCad xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
            . "<infCons>"
            . "<xServ>CONS-CAD</xServ>"
            . "<UF>$siglaUF</UF>"
            . "$filtro</infCons></ConsCad>";
        //validar mensagem com xsd
        //if (! $this->validarXml($cons)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        //montagem dos dados da mensagem SOAP
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";
        //envia a solicitação via SOAP
        $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
        );
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
        //salva mensagens
        $filename = "$txtFile-consCad.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
        $filename = "$txtFile-retConsCad.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        //tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        return (string) $retorno;
    }

    /**
     * sefazStatus
     * Verifica o status do serviço da SEFAZ/SVC
     * NOTA : Este serviço será removido no futuro, segundo da Receita/SEFAZ devido
     * ao excesso de mau uso !!!
     *
     * @param    string $siglaUF  sigla da unidade da Federação
     * @param    string $tpAmb    tipo de ambiente 1-produção e 2-homologação
     * @param    array  $aRetorno parametro passado por referencia contendo a resposta da consulta em um array
     * @return   mixed string XML do retorno do webservice, ou false se ocorreu algum erro
     * @throws   Exception\RuntimeException
     * @internal function zLoadServico (Common\Base\BaseTools)
     */
    public function sefazStatus($siglaUF = '', $tpAmb = '2', &$aRetorno = array())
    {
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        if ($siglaUF == '') {
            $siglaUF = $this->aConfig['siglaUF'];
        }
        //carrega serviço
        $servico = 'NFeStatusServico';
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );
        if ($this->urlService == '') {
            $msg = "O status não está disponível na SEFAZ $siglaUF!!!";
            throw new Exception\RuntimeException($msg);
        }
        $cons = "<consStatServ xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
            . "<tpAmb>$tpAmb</tpAmb><cUF>$this->urlcUF</cUF>"
            . "<xServ>STATUS</xServ></consStatServ>";
        //valida mensagem com xsd
        //validar mensagem com xsd
        //if (! $this->validarXml($cons)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        //montagem dos dados da mensagem SOAP
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";
        
        //consome o webservice e verifica o retorno do SOAP
        $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
        );
        
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
        $datahora = date('Ymd_His');
        $filename = $siglaUF."_"."$datahora-consStatServ.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
        $filename = $siglaUF."_"."$datahora-retConsStatServ.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        //tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        return (string) $retorno;
    }

    /**
     * sefazDistDFe
     * Serviço destinado à distribuição de informações
     * resumidas e documentos fiscais eletrônicos de interesse de um ator.
     *
     * @param    string  $fonte        sigla da fonte dos dados 'AN' e para alguns casos pode ser 'RS' e para alguns casos pode ser 'RS'
     *                      e para alguns casos pode ser 'RS'
     * @param    string  $tpAmb        tiupo de ambiente
     * @param    string  $cnpj
     * @param    integer $ultNSU       ultimo numero NSU que foi consultado
     * @param    integer $numNSU       numero de NSU que se quer consultar
     * @param    array   $aRetorno     array com os dados do retorno
     * @param    boolean $descompactar se true irá descompactar os dados retornados,
     *        se não os dados serão retornados da forma que foram recebidos
     * @return   string contento o xml retornado pela SEFAZ
     * @internal function zLoadServico (Common\Base\BaseTools)
     */
    public function sefazDistDFe(
        $fonte = 'AN',
        $tpAmb = '2',
        $cnpj = '',
        $ultNSU = 0,
        $numNSU = 0,
        &$aRetorno = array()
    ) {
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        $siglaUF = $this->aConfig['siglaUF'];
        if ($cnpj == '') {
            $cnpj = $this->aConfig['cnpj'];
        }
        //carrega serviço
        $servico = 'NfeDistribuicaoDFe';
        $this->zLoadServico(
            'nfe',
            $servico,
            $fonte,
            $tpAmb
        );
        if ($this->urlService == '') {
            $msg = "A distribuição de documento DFe não está disponível na SEFAZ $fonte!!!";
            throw new Exception\RuntimeException($msg);
        }
        $cUF = self::getcUF($siglaUF);
        $ultNSU = str_pad($ultNSU, 15, '0', STR_PAD_LEFT);
        $tagNSU = "<distNSU><ultNSU>$ultNSU</ultNSU></distNSU>";
        if ($numNSU != 0) {
            $numNSU = str_pad($numNSU, 15, '0', STR_PAD_LEFT);
            $tagNSU = "<consNSU><NSU>$numNSU</NSU></consNSU>";
        }
        //monta a consulta
        $cons = "<distDFeInt xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
            . "<tpAmb>$tpAmb</tpAmb>"
            . "<cUFAutor>$cUF</cUFAutor>"
            . "<CNPJ>$cnpj</CNPJ>$tagNSU</distDFeInt>";
        //validar mensagem com xsd
        //if (! $this->validarXml($cons)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        //montagem dos dados da mensagem SOAP
        $body = "<nfeDistDFeInteresse xmlns=\"$this->urlNamespace\">"
            . "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>"
            . "</nfeDistDFeInteresse>";
        //envia dados via SOAP e verifica o retorno este webservice não requer cabeçalho
        $this->urlHeader = '';
        $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
        );
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
        //salva mensagens
        $tipoNSU = (int) ($numNSU != 0 ? $numNSU : $ultNSU);
        $datahora = date('Ymd_His');
        $filename = "$tipoNSU-$datahora-distDFeInt.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
        $filename = "$tipoNSU-$datahora-retDistDFeInt.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        //tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        return (string) $retorno;
    }

    /**
     * sefazCCe
     * Solicita a autorização da Carta de Correção
     *
     * @param  string $chNFe
     * @param  string $tpAmb
     * @param  string $xCorrecao
     * @param  int    $nSeqEvento
     * @param  array  $aRetorno
     * @return array
     * @throws Exception\InvalidArgumentException
     */
    public function sefazCCe($chNFe = '', $tpAmb = '2', $xCorrecao = '', $nSeqEvento = 1, &$aRetorno = array())
    {
        //limpa chave
        $chNFe = preg_replace('/[^0-9]/', '', $chNFe);
        $xCorrecao = Strings::cleanString($xCorrecao);
        $nSeqEvento = (integer) $nSeqEvento;
        if (strlen($chNFe) != 44) {
            $msg = "A chave deve ter 44 dígitos!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        if (strlen($xCorrecao) < 15 || strlen($xCorrecao) > 1000) {
            $msg = "A correção deve ter entre 15 e 1000 caracteres!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        if ($nSeqEvento < 1 || $nSeqEvento > 20) {
            $msg = "O número sequencial do evento deve ser entre 1 e 20!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        $siglaUF = $this->zGetSigla(substr($chNFe, 0, 2));
        $tpEvento = '110110';
        $xCondUso = "A Carta de Correcao e disciplinada pelo paragrafo "
                . "1o-A do art. 7o do Convenio S/N, de 15 de dezembro de 1970 "
                . "e pode ser utilizada para regularizacao de erro ocorrido "
                . "na emissao de documento fiscal, desde que o erro nao esteja "
                . "relacionado com: I - as variaveis que determinam o valor "
                . "do imposto tais como: base de calculo, aliquota, diferenca "
                . "de preco, quantidade, valor da operacao ou da prestacao; "
                . "II - a correcao de dados cadastrais que implique mudanca "
                . "do remetente ou do destinatario; "
                . "III - a data de emissao ou de saida.";
        $tagAdic = "<xCorrecao>$xCorrecao</xCorrecao><xCondUso>$xCondUso</xCondUso>";
        $retorno = $this->zSefazEvento($siglaUF, $chNFe, $tpAmb, $tpEvento, $nSeqEvento, $tagAdic);
        $aRetorno = $this->aLastRetEvent;
        return $retorno;
    }
    
    /**
     * sefazEPP
     * Solicita pedido de prorrogação do prazo de retorno de produtos de uma
     * NF-e de remessa para industrialização por encomenda com suspensão do ICMS
     * em operações interestaduais
     *
     * @param  string  $chNFe
     * @param  string  $tpAmb
     * @param  integer $nSeqEvento
     * @param  string  $nProt
     * @param  array   $itens
     * @param  array   $aRetorno
     * @return string
     * @throws Exception\InvalidArgumentException
     */
    public function sefazEPP(
        $chNFe = '',
        $tpAmb = '2',
        $nSeqEvento = 1,
        $nProt = '',
        $itens = array(),
        &$aRetorno = array()
    ) {
        $chNFe = preg_replace('/[^0-9]/', '', $chNFe);
        if (empty($itens)) {
            $msg = "Devem ser passados os itens e as quantidades da NFe que será prorrogada!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        if (empty($nProt)) {
            $msg = "Deve ser passado o numero do protocolo de autorização da "
                . "NFe que terá o Pedido de prorrogação!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        $siglaUF = $this->zGetSigla(substr($chNFe, 0, 2));
        $tpEvento = '111500';
        if ($nSeqEvento == 2) {
            $tpEvento = '111501';
        }
        $tagAdic = "<nProt>$nProt</nProt><itemPedido>";
        foreach ($itens as $item) {
            $tagAdic .= "<itemPedido numItem=\"".$item[0]."\"><qtdeItem>".$item[1]."</qtdeItem><itemPedido>";
        }
        $retorno = $this->zSefazEvento($siglaUF, $chNFe, $tpAmb, $tpEvento, $nSeqEvento, $tagAdic);
        $aRetorno = $this->aLastRetEvent;
        return $retorno;
    }
    
    /**
     * sefazECPP
     * Solicita o cancelamento do pedido de prorrogação do prazo de retorno
     * de produtos de uma NF-e de remessa para industrialização por encomenda
     * com suspensão do ICMS em operações interestaduais
     *
     * @param  string  $chNFe
     * @param  string  $tpAmb
     * @param  integer $nSeqEvento
     * @param  string  $nProt
     * @param  array   $aRetorno
     * @return string
     * @throws Exception\InvalidArgumentException
     */
    public function sefazECPP(
        $chNFe = '',
        $tpAmb = '2',
        $nSeqEvento = 1,
        $nProt = '',
        &$aRetorno = array()
    ) {
        $chNFe = preg_replace('/[^0-9]/', '', $chNFe);
        if (empty($chNFe)) {
            $msg = "Deve ser passada a chave da NFe referente ao Pedido de "
                . "prorrogação que será Cancelado!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        if (empty($nProt)) {
            $msg = "Deve ser passado o numero do protocolo de autorização do "
                . "Pedido de prorrogação que será Cancelado!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        $siglaUF = $this->zGetSigla(substr($chNFe, 0, 2));
        $tpEvento = '111502';
        $origEvent = '111500';
        if ($nSeqEvento == 2) {
            $tpEvento = '111503';
            $origEvent = '111501';
        }
        $sSeqEvento = str_pad($nSeqEvento, 2, "0", STR_PAD_LEFT);
        $idPedidoCancelado = "ID$origEvent$chNFe$sSeqEvento";
        $tagAdic = "<idPedidoCancelado>$idPedidoCancelado</idPedidoCancelado><nProt>$nProt</nProt>";
        $retorno = $this->zSefazEvento($siglaUF, $chNFe, $tpAmb, $tpEvento, $nSeqEvento, $tagAdic);
        $aRetorno = $this->aLastRetEvent;
        return $retorno;
    }
    
    /**
     * sefazEPEC
     * Solicita autorização em contingência EPEC
     * TODO: terminar esse método
     *
     * @param  string|array $aXml
     * @param  string       $tpAmb
     * @param  string       $siglaUF
     * @param  array        $aRetorno
     * @return string
     * @throws Exception\InvalidArgumentException
     */
    public function sefazEPEC($aXml, $tpAmb = '2', $siglaUF = 'AN', &$aRetorno = array())
    {
        //na nfe deve estar indicado a entrada em contingencia da data hora e o motivo
        //caso contrario ignorar a solicitação de EPEC
        if (! is_array($aXml)) {
            $aXml[] = $aXml; //se não é um array converte
        }
        if (count($aXml) > 20) {
            $msg = "O limite é de 20 NFe em um lote EPEC, você está passando [".count($aXml)."]";
            throw new Exception\InvalidArgumentException($msg);
        }
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        //carrega serviço
        $servico = 'RecepcaoEPEC';
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );
        if ($this->urlService == '') {
            $msg = "A recepção de EPEC não está disponível na SEFAZ !!!";
            throw new Exception\RuntimeException($msg);
        }
        $aRetorno = array();
        $cnpj = $this->aConfig['cnpj'];
        $aRet = $this->zTpEv($tpEvento);
        $descEvento = $aRet['desc'];
        $cOrgao = '91';
        $tpEvento = '110140'; //EPEC
        $datEv = '';
        $numLote = LotNumber::geraNumLote();
        foreach ($aXml as $xml) {
            $dat = $this->zGetInfo($xml);
            if ($dat['dhCont'] == '' || $dat['xJust'] == '') {
                $msg = "Somente é possivel enviar para EPEC as notas emitidas "
                        . "em contingência com a data/hora e justificativa da contingência.";
                throw new Exception\InvalidArgumentException($msg);
            }
            $sSeqEvento = str_pad('1', 2, "0", STR_PAD_LEFT);
            $eventId = "ID".$tpEvento.$chNFe.$sSeqEvento;
            $chNFe = $dat['chave'];
            $dhEvento = DateTime::convertTimestampToSefazTime();
            $mensagem = "<evento xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
                . "<infEvento Id=\"$eventId\">"
                . "<cOrgao>$cOrgao</cOrgao>"
                . "<tpAmb>$tpAmb</tpAmb>"
                . "<CNPJ>$cnpj</CNPJ>"
                . "<chNFe>$chNFe</chNFe>"
                . "<dhEvento>$dhEvento</dhEvento>"
                . "<tpEvento>$tpEvento</tpEvento>"
                . "<nSeqEvento>1</nSeqEvento>"
                . "<verEvento>$this->urlVersion</verEvento>"
                . "<detEvento versao=\"$this->urlVersion\">"
                . "<descEvento>$descEvento</descEvento>"
                . "<cOrgaoAutor>".$dat['cOrgaoAutor']."</cOrgaoAutor>"
                . "<tpAutor>".$dat['tpAutor']."</tpAutor>"
                . "<verAplic>$this->verAplic</verAplic>"
                . "<dhEmi>".$dat['dhEmi']."</dhEmi>"
                . "<tpNF>".$dat['tpNF']."</tpNF>"
                . "<IE>".$dat['IE']."</IE>"
                . "<dest>"
                . "<UF>".$dat['UF']."</UF> ";
            if ($dat['CNPJ'] != '') {
                $mensagem .= "<CNPJ>.".$dat['CNPJ']."</CNPJ>";
            } elseif ($dat['CPF'] != '') {
                $mensagem .= "<CPF>".$dat['CPF']."</CPF>";
            } else {
                $mensagem .= "<idEstrangeiro>".$dat['idEstrangeiro']."</idEstrangeiro>";
            }
            if ($dat['IEdest'] != '') {
                $mensagem .= "<IE>".$dat['IEdest']."</IE>";
            }
            $mensagem .= "</dest>"
                . "<vNF>".$dat['vNF']."</vNF>"
                . "<vICMS>".$dat['vICMS']."</vICMS>"
                . "<vST>".$dat['vST']."</vST>"
                . "</detEvento>"
                . "</infEvento>"
                . "</evento>";
                //assinatura dos dados
                $signedMsg = $this->oCertificate->signXML($mensagem, 'infEvento');
                $signedMsg = Strings::clearXml($signedMsg, true);
                $datEv .= $signedMsg;
        }
        $cons = "<envEvento xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
            . "<idLote>$numLote</idLote>"
            . "$datEv"
            . "</envEvento>";
        //valida mensagem com xsd
        //no caso do evento nao tem xsd organizado, esta fragmentado
        //e por vezes incorreto por isso essa validação está desabilitada
        //if (! $this->zValidMessage($cons, 'nfe', 'envEvento', $version)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";
        //envia a solicitação via SOAP
        $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
        );
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
        //salva mensagens
        $filename = "$numLote-envEpec.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
        $filename = "$numLote-retEnvEpec.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        //tratar dados de retorno
        //TODO : incluir nos xml das NF o protocolo EPEC
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        return $retorno;
    }
    
    /**
     * zGetInfo
     * Busca informações do XML
     * para uso no sefazEPEC
     *
     * @param  string $xml
     * @return array
     */
    protected function zGetInfo($xml)
    {
        $dom = new Dom();
        $dom->loadXMLString($xml);
        $ide = $dom->getNode('ide');
        $emit = $dom->getNode('emit');
        $dest = $dom->getNode('dest');
        $enderDest = $dest->getElementsByTagName('enderDest')->item(0);
        $icmsTot = $dom->getNode('ICMSTot');
        $resp = array(
            'chave' => $dom->getChave('infNFe'),
            'dhCont' => $dom->getValue($ide, 'dhCont'),
            'xJust' => $dom->getValue($ide, 'xJust'),
            'cOrgaoAutor' => $dom->getValue($ide, 'cUF'),
            'tpAutor' => '1',
            'dhEmi' => $dom->getValue($ide, 'dhEmi'),
            'tpNF' => $dom->getValue($ide, 'tpNF'),
            'IE' => $dom->getValue($emit, 'IE'),
            'UF' => $dom->getValue($enderDest, 'UF'),
            'CNPJ' => $dom->getValue($dest, 'CNPJ'),
            'CPF' => $dom->getValue($dest, 'CPF'),
            'idEstrangeiro' => $dom->getValue($dest, 'idEstrangeiro'),
            'IEdest' => $dom->getValue($dest, 'IE'),
            'vNF' => $dom->getValue($icmsTot, 'vNF'),
            'vICMS' => $dom->getValue($icmsTot, 'vICMS'),
            'vST'=> $dom->getValue($icmsTot, 'vST')
        );
        return $resp;
    }
    
    /**
     * sefazCancela
     * Solicita o cancelamento da NFe
     *
     * @param  string $chNFe
     * @param  string $tpAmb
     * @param  string $xJust
     * @param  string $nProt
     * @param  array  $aRetorno
     * @return string
     * @throws Exception\InvalidArgumentException
     */
    public function sefazCancela($chNFe = '', $tpAmb = '2', $xJust = '', $nProt = '', &$aRetorno = array())
    {
        $chNFe = preg_replace('/[^0-9]/', '', $chNFe);
        $nProt = preg_replace('/[^0-9]/', '', $nProt);
        $xJust = Strings::cleanString($xJust);
        //validação dos dados de entrada
        if (strlen($chNFe) != 44) {
            $msg = "Uma chave de NFe válida não foi passada como parâmetro $chNFe.";
            throw new Exception\InvalidArgumentException($msg);
        }
        if ($nProt == '') {
            $msg = "Não foi passado o numero do protocolo!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        if (strlen($xJust) < 15 || strlen($xJust) > 255) {
            $msg = "A justificativa deve ter pelo menos 15 digitos e no máximo 255!!";
            throw new Exception\InvalidArgumentException($msg);
        }
        $siglaUF = $this->zGetSigla(substr($chNFe, 0, 2));
        //estabelece o codigo do tipo de evento CANCELAMENTO
        $tpEvento = '110111';
        $nSeqEvento = 1;
        //monta mensagem
        $tagAdic = "<nProt>$nProt</nProt><xJust>$xJust</xJust>";
        $retorno = $this->zSefazEvento($siglaUF, $chNFe, $tpAmb, $tpEvento, $nSeqEvento, $tagAdic);
        $aRetorno = $this->aLastRetEvent;
        return $retorno;
    }
    
    /**
     * sefazManifesta
     * Solicita o registro da manifestação de destinatário
     *
     * @param  string $chNFe
     * @param  string $tpAmb
     * @param  string $xJust
     * @param  string $tpEvento
     * @param  array  $aRetorno
     * @return string
     * @throws Exception\InvalidArgumentException
     */
    public function sefazManifesta($chNFe = '', $tpAmb = '2', $xJust = '', $tpEvento = '', &$aRetorno = array())
    {
        $chNFe = preg_replace('/[^0-9]/', '', $chNFe);
        $tpEvento = preg_replace('/[^0-9]/', '', $tpEvento);
        $tagAdic = '';
        switch ($tpEvento) {
            case '210200':
                //210200 – Confirmação da Operação
                break;
            case '210210':
                //210210 – Ciência da Operação
                break;
            case '210220':
                //210220 – Desconhecimento da Operação
                break;
            case '210240':
                //210240 – Operação não Realizada
                if (strlen($xJust) < 15 ||  strlen($xJust) > 255) {
                    $msg = "É obrigatória uma justificativa com 15 até 255 caracteres!!";
                    throw new Exception\InvalidArgumentException($msg);
                }
                $xJust = Strings::cleanString($xJust);
                $tagAdic = "<xJust>$xJust</xJust>";
                break;
            default:
                $msg = "Esse código de tipo de evento não consta!! $tpEvento";
                throw new Exception\InvalidArgumentException($msg);
        }
        $siglaUF = 'AN';
        $nSeqEvento = '1';
        $retorno = $this->zSefazEvento($siglaUF, $chNFe, $tpAmb, $tpEvento, $nSeqEvento, $tagAdic);
        $aRetorno = $this->aLastRetEvent;
        return $retorno;
    }
    
    /**
     * sefazDownload
     * Solicita o download de NFe já manifestada
     *
     * @param  string $chNFe
     * @param  string $tpAmb
     * @param  string $cnpj
     * @param  array  $aRetorno
     * @return string
     * @throws Exception\RuntimeException
     */
    public function sefazDownload($chNFe = '', $tpAmb = '', $cnpj = '', &$aRetorno = array())
    {
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        if ($cnpj == '') {
            $cnpj = $this->aConfig['cnpj'];
        }
        //carrega serviço
        $servico = 'NfeDownloadNF';
        $this->zLoadServico(
            'nfe',
            $servico,
            'AN',
            $tpAmb
        );
        if ($this->urlService == '') {
            $msg = "O status não está disponível na SEFAZ !!!";
            throw new Exception\RuntimeException($msg);
        }
        $cons = "<downloadNFe xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
                . "<tpAmb>$tpAmb</tpAmb>"
                . "<xServ>DOWNLOAD NFE</xServ>"
                . "<CNPJ>$cnpj</CNPJ>"
                . "<chNFe>$chNFe</chNFe>"
                . "</downloadNFe>";
        //validar mensagem com xsd
        //if (! $this->validarXml($cons)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        //montagem dos dados da mensagem SOAP
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";
        //consome o webservice e verifica o retorno do SOAP
        $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
        );
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
        $filename = "$chNFe-downnfe.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
        $filename = "$chNFe-retDownnfe.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        //tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        return (string) $retorno;
    }
	
	/* 02/05/2018 11:28 - Edival - Fazer download da Nfe pelo certificado */
	public function sefazDownloadXML($chNFe = '', $tpAmb = '', $cnpj = '', &$aRetorno = array())
    {
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        if ($cnpj == '') {
            $cnpj = $this->aConfig['cnpj'];
        }
        //carrega serviço
        $servico = 'NfeDistribuicaoDFe';
        $this->zLoadServico(
            'nfe',
            $servico,
            'AN',
            $tpAmb
        );
        if ($this->urlService == '') {
            $msg = "O status não está disponível na SEFAZ !!!";
            throw new Exception\RuntimeException($msg);
        }
		
		$cUF = '35';
		
		/* CONSULTOU CORRETAMENTE PELA NSU
		$cons = "<distDFeInt xmlns=\"$this->urlPortal\" versao=\"1.01\">"
            . "<tpAmb>$tpAmb</tpAmb>"
            . "<cUFAutor>$cUF</cUFAutor>"
			. "<CNPJ>$cnpj</CNPJ>"
			. "<distNSU><ultNSU>$chNSU</ultNSU></distNSU></distDFeInt>";
		*/
		$cons = "<distDFeInt xmlns=\"$this->urlPortal\" versao=\"1.01\">"
            . "<tpAmb>$tpAmb</tpAmb>"
            . "<cUFAutor>$cUF</cUFAutor>"
			. "<CNPJ>$cnpj</CNPJ>"
			. "<consChNFe><chNFe>$chNFe</chNFe></consChNFe></distDFeInt>";
		
		//montagem dos dados da mensagem SOAP
        // $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";
		
		$body = "<nfeDistDFeInteresse xmlns=\"$this->urlNamespace\">"
				. "<nfeDadosMsg>$cons</nfeDadosMsg>"
				. "</nfeDistDFeInteresse>";

				
		$this->urlHeader = '';
			
        //consome o webservice e verifica o retorno do SOAP
        $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
        );
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
		
        $filename = "$chNFe-downxml.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
        
		$filename = "$chNFe-retDownxml.xml";
        $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
        
		//tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        return (string) $retorno;
    }
	
	
	
    
    /**
     * sefazManutencaoCsc
     * Manutenção do Código de Segurança do Contribuinte (Antigo Token)
     *
     * @param    int    $indOp
     * @param    string $tpAmb
     * @param    string $raizCNPJ
     * @param    string $idCsc
     * @param    string $codigoCsc
     * @param    array  $aRetorno
     * @return   string
     * @throws   Exception\InvalidArgumentException
     * @throws   Exception\RuntimeException
     * @internal function zLoadServico (Common\Base\BaseTools)
     */
    public function sefazManutencaoCsc($indOp = '', $tpAmb = '2', $raizCNPJ = '', $idCsc = '', $codigoCsc = '', $saveXml = false, &$aRetorno = array())
    {
        if ($codigoCsc == '') {
            $codigoCsc = $this->aConfig['tokenNFCe'];
        }
        if ($idCsc == '') {
            $idCsc = $this->aConfig['tokenNFCeId'];
        }
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        if (!is_numeric($indOp)) {
            $msg = "A operação deve ser informada.";
            throw new Exception\InvalidArgumentException($msg);
        } else {
            if ($indOp == 3 && ($idCsc == '' || $codigoCsc == '')) {
                $msg = "Para Revogação de CSC, é necessário informar o Código e ID do CSC que deseja revogar.";
                throw new Exception\InvalidArgumentException($msg);
            }
        }
        if ($raizCNPJ == '') {
            $raizCNPJ = substr($this->aConfig['cnpj'], 0, -6);
        } else {
            if (strlen($raizCNPJ)!=8) {
                $msg = "raizCNPJ: Deve ser os 08 primeiros dígitos do CNPJ.";
                throw new Exception\InvalidArgumentException($msg);
            }
        }
        $siglaUF = $this->aConfig['siglaUF'];
        //carrega serviço
        $servico = 'CscNFCe';
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );
        if ($this->urlService == '') {
            $msg = "A manutenção do código de segurança do contribuinte de NFC-e não está disponível na SEFAZ $siglaUF!!!";
            throw new Exception\RuntimeException($msg);
        }
        
        if ($indOp==3) {
            $cons = "<admCscNFCe versao=\"$this->urlVersion\" xmlns=\"$this->urlPortal\">"
            . "<tpAmb>$tpAmb</tpAmb>"
            . "<indOp>$indOp</indOp>"
            . "<raizCNPJ>$raizCNPJ</raizCNPJ>"
            . "<dadosCsc>"
            .   "<idCsc>$idCsc</idCsc>"
            .   "<codigoCsc>$codigoCsc</codigoCsc>"
            . "</dadosCsc>"
            . "</admCscNFCe>";
        } else {
            $cons = "<admCscNFCe versao=\"$this->urlVersion\" xmlns=\"$this->urlPortal\">"
            . "<tpAmb>$tpAmb</tpAmb>"
            . "<indOp>$indOp</indOp>"
            . "<raizCNPJ>$raizCNPJ</raizCNPJ>"
            . "</admCscNFCe>";
        }
        
        //montagem dos dados da mensagem SOAP
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";
        
        //envia a solicitação via SOAP
        $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
        );
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
        
        //salva mensagens
        if ($saveXml) {
            $filename = "$raizCNPJ-$indOp-admCscNFCe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg, 'csc');
            $filename = "$raizCNPJ-$indOp-retAdmCscNFCe.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $retorno, 'csc');
        }
        
        //tratar dados de retorno
        $aRetorno = ReturnNFe::readReturnSefaz($servico, $retorno);
        return (string) $retorno;
    }
    
    /**
     * validarXml
     * Valida qualquer xml do sistema NFe com seu xsd
     * NOTA: caso não exista um arquivo xsd apropriado retorna false
     *
     * @param  string $xml path ou conteudo do xml
     * @return boolean
     */
    public function validarXml($xml = '')
    {
        $aResp = array();
        $schem = IdentifyNFe::identificar($xml, $aResp);
        if ($schem == '') {
            return true;
        }
        $xsdFile = $aResp['Id'].'_v'.$aResp['versao'].'.xsd';
        $xsdPath = NFEPHP_ROOT.DIRECTORY_SEPARATOR .
            'schemes' .
            DIRECTORY_SEPARATOR .
            'NFe' .
            DIRECTORY_SEPARATOR .
            $this->aConfig['schemesNFe'] .
            DIRECTORY_SEPARATOR .
            $xsdFile;
        if (! is_file($xsdPath)) {
            $this->errors[] = "O arquivo XSD $xsdFile não foi localizado.";
            return false;
        }
        if (! ValidXsd::validar($aResp['xml'], $xsdPath)) {
            $this->errors[] = ValidXsd::$errors;
            return false;
        }
        return true;
    }
    
    /**
     * zSefazEvento
     *
     * @param    string $siglaUF
     * @param    string $chNFe
     * @param    string $tpAmb
     * @param    string $tpEvento
     * @param    string $nSeqEvento
     * @param    string $tagAdic
     * @return   string
     * @throws   Exception\RuntimeException
     * @internal function zLoadServico (Common\Base\BaseTools)
     */
    protected function zSefazEvento(
        $siglaUF = '',
        $chNFe = '',
        $tpAmb = '2',
        $tpEvento = '',
        $nSeqEvento = '1',
        $tagAdic = ''
    ) {
        if ($tpAmb == '') {
            $tpAmb = $this->aConfig['tpAmb'];
        }
        //carrega serviço
        $servico = 'RecepcaoEvento';
        $this->zLoadServico(
            'nfe',
            $servico,
            $siglaUF,
            $tpAmb
        );
        if ($this->urlService == '') {
            $msg = "A recepção de eventos não está disponível na SEFAZ $siglaUF!!!";
            throw new Exception\RuntimeException($msg);
        }
        $aRet = $this->zTpEv($tpEvento);
        $aliasEvento = $aRet['alias'];
        $descEvento = $aRet['desc'];
        $cnpj = $this->aConfig['cnpj'];
        $dhEvento = (string) str_replace(' ', 'T', date('Y-m-d H:i:sP'));
        $sSeqEvento = str_pad($nSeqEvento, 2, "0", STR_PAD_LEFT);
        $eventId = "ID".$tpEvento.$chNFe.$sSeqEvento;
        $cOrgao = $this->urlcUF;
        if ($siglaUF == 'AN') {
            $cOrgao = '91';
        }
        $mensagem = "<evento xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
            . "<infEvento Id=\"$eventId\">"
            . "<cOrgao>$cOrgao</cOrgao>"
            . "<tpAmb>$tpAmb</tpAmb>"
            . "<CNPJ>$cnpj</CNPJ>"
            . "<chNFe>$chNFe</chNFe>"
            . "<dhEvento>$dhEvento</dhEvento>"
            . "<tpEvento>$tpEvento</tpEvento>"
            . "<nSeqEvento>$nSeqEvento</nSeqEvento>"
            . "<verEvento>$this->urlVersion</verEvento>"
            . "<detEvento versao=\"$this->urlVersion\">"
            . "<descEvento>$descEvento</descEvento>"
            . "$tagAdic"
            . "</detEvento>"
            . "</infEvento>"
            . "</evento>";
        //assinatura dos dados
        $signedMsg = $this->oCertificate->signXML($mensagem, 'infEvento');
        $signedMsg = Strings::clearXml($signedMsg, true);
        $numLote = LotNumber::geraNumLote();
        $cons = "<envEvento xmlns=\"$this->urlPortal\" versao=\"$this->urlVersion\">"
            . "<idLote>$numLote</idLote>"
            . "$signedMsg"
            . "</envEvento>";
        //valida mensagem com xsd
        //no caso do evento nao tem xsd organizado, esta fragmentado
        //e por vezes incorreto por isso essa validação está desabilitada
        //if (! $this->zValidMessage($cons, 'nfe', 'envEvento', $version)) {
        //    $msg = 'Falha na validação. '.$this->error;
        //    throw new Exception\RuntimeException($msg);
        //}
        $body = "<nfeDadosMsg xmlns=\"$this->urlNamespace\">$cons</nfeDadosMsg>";
        //envia a solicitação via SOAP
        $retorno = $this->oSoap->send(
            $this->urlService,
            $this->urlNamespace,
            $this->urlHeader,
            $body,
            $this->urlMethod
        );
        $lastMsg = $this->oSoap->lastMsg;
        $this->soapDebug = $this->oSoap->soapDebug;
        //salva mensagens
        //tratar dados de retorno
        $this->aLastRetEvent = ReturnNFe::readReturnSefaz($servico, $retorno);
        if ($this->getSalvarMensagensEvento()) {
            $filename = "$chNFe-$aliasEvento-envEvento.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $lastMsg);
            $filename = "$chNFe-$aliasEvento-retEnvEvento.xml";
            $this->zGravaFile('nfe', $tpAmb, $filename, $retorno);
            if ($this->aLastRetEvent['cStat'] == '128') {
                if ($this->aLastRetEvent['evento'][0]['cStat'] == '135'
                    || $this->aLastRetEvent['evento'][0]['cStat'] == '136'
                    || $this->aLastRetEvent['evento'][0]['cStat'] == '155'
                ) {
                    $pasta = 'eventos'; //default
                    if ($aliasEvento == 'CancNFe') {
                        $pasta = 'canceladas';
                        $filename = "$chNFe-$aliasEvento-procEvento.xml";
                    } elseif ($aliasEvento == 'CCe') {
                        $pasta = 'cartacorrecao';
                        $filename = "$chNFe-$aliasEvento-$nSeqEvento-procEvento.xml";
                    }
                    $retorno = $this->zAddProtMsg('procEventoNFe', 'evento', $signedMsg, 'retEvento', $retorno);
                    $this->zGravaFile('nfe', $tpAmb, $filename, $retorno, $pasta);
                }
            }
        }
        
        return (string) $retorno;
    }
    
    /**
     * zTpEv
     *
     * @param  string $tpEvento
     * @return array
     * @throws Exception\RuntimeException
     */
    private function zTpEv($tpEvento = '')
    {
        //montagem dos dados da mensagem SOAP
        switch ($tpEvento) {
            case '110110':
                //CCe
                $aliasEvento = 'CCe';
                $descEvento = 'Carta de Correcao';
                break;
            case '110111':
                //cancelamento
                $aliasEvento = 'CancNFe';
                $descEvento = 'Cancelamento';
                break;
            case '110140':
                //EPEC
                //emissão em contingência EPEC
                $aliasEvento = 'EPEC';
                $descEvento = 'EPEC';
                break;
            case '111500':
            case '111501':
                //EPP
                //Pedido de prorrogação
                $aliasEvento = 'EPP';
                $descEvento = 'Pedido de Prorrogacao';
                break;
            case '111502':
            case '111503':
                //ECPP
                //Cancelamento do Pedido de prorrogação
                $aliasEvento = 'ECPP';
                $descEvento = 'Cancelamento de Pedido de Prorrogacao';
                break;
            case '210200':
                //Confirmacao da Operacao
                $aliasEvento = 'EvConfirma';
                $descEvento = 'Confirmacao da Operacao';
                break;
            case '210210':
                //Ciencia da Operacao
                $aliasEvento = 'EvCiencia';
                $descEvento = 'Ciencia da Operacao';
                break;
            case '210220':
                //Desconhecimento da Operacao
                $aliasEvento = 'EvDesconh';
                $descEvento = 'Desconhecimento da Operacao';
                break;
            case '210240':
                //Operacao não Realizada
                $aliasEvento = 'EvNaoRealizada';
                $descEvento = 'Operacao nao Realizada';
                break;
            default:
                $msg = "O código do tipo de evento informado não corresponde a "
                . "nenhum evento estabelecido.";
                throw new Exception\RuntimeException($msg);
        }
        return array('alias' => $aliasEvento, 'desc' => $descEvento);
    }
    
    /**
    * getTimestampCert
    * Retorna o timestamp para a data de vencimento do Certificado
     *
    * @return int
    */
    public function getTimestampCert()
    {
        return $this->oCertificate->expireTimestamp;
    }
    
    /**
     * getImpostosIBPT
     * Consulta o serviço do IBPT para obter os impostos ao consumidor
     * conforme Lei 12.741/2012
     *
     * @param  string $ncm
     * @param  string $exTarif
     * @param  string $siglaUF
     * @return array Array (
     *                 [Codigo] => 60063100
     *                 [UF] => SP
     *                 [EX] => 0
     *                 [Descricao] => Outs.tecidos de malha de fibras sinteticas, crus ou branqueados
     *                 [Nacional] => 13.45
     *                 [Estadual] => 18
     *                 [Importado] => 16.14
     *               )
     */
    public function getImpostosIBPT($ncm = '', $exTarif = '0', $siglaUF = '')
    {
        if ($siglaUF == '') {
            $siglaUF = $this->aConfig['siglaUF'];
        }
        $cnpj = $this->aConfig['cnpj'];
        $tokenIBPT = $this->aConfig['tokenIBPT'];
        if ($ncm == '' || $tokenIBPT == '' || $cnpj == '') {
            return array();
        }
        return $this->oSoap->getIBPTProd(
            $cnpj,
            $tokenIBPT,
            $ncm,
            $siglaUF,
            $exTarif
        );
    }
       
    /**
     * zMakeQRCode
     * Cria a chave do QR Code a ser usado na NFCe
     *
     * @param  string $chNFe
     * @param  string $url
     * @param  string $tpAmb
     * @param  string $dhEmi
     * @param  string $vNF
     * @param  string $vICMS
     * @param  string $digVal
     * @param  string $token
     * @param  string $cDest
     * @param  string $idToken
     * @param  string $versao
     * @return string
     */
    protected function zMakeQRCode(
        $chNFe,
        $url,
        $tpAmb,
        $dhEmi,
        $vNF,
        $vICMS,
        $digVal,
        $token = '',
        $cDest = '',
        $idToken = '000001',
        $versao = '100'
    ) {
        if ($token == '') {
            return '';
        }
        $dhHex = self::zStr2Hex($dhEmi);
        $digHex = self::zStr2Hex($digVal);
        
        $seq = '';
        $seq .= 'chNFe=' . $chNFe;
        $seq .= '&nVersao=' . $versao;
        $seq .= '&tpAmb=' . $tpAmb;
        if ($cDest != '') {
            $seq .= '&cDest=' . $cDest;
        }
        $seq .= '&dhEmi=' . strtolower($dhHex);
        $seq .= '&vNF=' . $vNF;
        $seq .= '&vICMS=' . $vICMS;
        $seq .= '&digVal=' . strtolower($digHex);
        $seq .= '&cIdToken=' . $idToken;
        //o hash code é calculado com o Token incluso
        $hash = sha1($seq.$token);
        $seq .= '&cHashQRCode='. strtoupper($hash);
        if (strpos($url, '?') === false) {
            $url = $url.'?';
        }
        $seq = $url.$seq;
        return $seq;
    }
    
    /**
     * zStr2Hex
     * Converte string para haxadecimal ASCII
     *
     * @param  string $str
     * @return string
     */
    protected static function zStr2Hex($str)
    {
        if ($str == '') {
            return '';
        }
        $hex = "";
        $iCount = 0;
        do {
            $hex .= sprintf("%02x", ord($str{$iCount}));
            $iCount++;
        } while ($iCount < strlen($str));
        return $hex;
    }
    
    public function getSalvarMensagensEvento()
    {
        return $this->bSalvarMensagensEvento;
    }
    
    /**
     * Se verdade gera os arquivos de logs do envio e resposta da requisição
     *
     * @param bool $salvarMensagensEvento
     */
    public function setSalvarMensagensEvento($salvarMensagensEvento)
    {
        $this->bSalvarMensagensEvento = $salvarMensagensEvento;
    }
    
    public function getLastMsg()
    {
        return $this->oSoap->lastMsg;
    }
}
