<?php

namespace Cnab\Remessa\Cnab400;

class Arquivo implements \Cnab\Remessa\IArquivo
{
    public $header;
    public $trailer;
    public $detalhes = array();
    private $_data_gravacao;
    private $_data_geracao;
    public $banco;
    public $codigo_banco;
    public $configuracao = array();
    public $layout_versao;
    const   QUEBRA_LINHA = "\r\n";

    public function __construct($codigo_banco, $layout_versao = null)
    {
        $this->codigo_banco = $codigo_banco;
        $this->layout_versao = $layout_versao;
        $this->banco = \Cnab\Banco::getBanco($this->codigo_banco);
        //$this->data_gravacao = date('dmY');
    }

    public function configure(array $params)
    {
        $campos = array(
            'data_geracao', 'data_gravacao', 'nome_fantasia', 'razao_social', 'cnpj', 'logradouro', 'numero', 'bairro',
            'cidade', 'uf', 'cep', 'agencia', 'conta'
        );

        switch ($this->codigo_banco) {
            case \Cnab\Banco::CEF:
                $campos[] = 'operacao';
                $campos[] = 'codigo_cedente';
                $campos[] = 'codigo_cedente_dac';
            case \Cnab\Banco::BRADESCO:
                $campos[] = 'codigo_cedente';
                $campos[] = 'sequencial_remessa';
                break;
            case \Cnab\Banco::BANCO_DO_BRASIL:
                $campos[] = 'agencia_dac';
                $campos[] = 'conta_dac';
                $campos[] = 'numero_sequencial';
                $campos[] = 'numero_convenio';                
                break;                
            default:
                $campos[] = 'conta_dac';
                break;
        }

        foreach ($campos as $campo) {
            if (array_key_exists($campo, $params)) {
                if (strpos($campo, 'data_') === 0 && !($params[$campo] instanceof \DateTime)) {
                    throw new \Exception("config '$campo' need to be instance of DateTime");
                }
                $this->configuracao[$campo] = $params[$campo];
            } else {
                throw new \Exception('Configuração "'.$campo.'" need to be set');
            }
        }

        foreach ($campos as $key) {
            if (!array_key_exists($key, $params)) {
                throw new Exception('Configuração "'.$key.'" dont exists');
            }
        }

        $this->data_geracao = $this->configuracao['data_geracao'];
        $this->data_gravacao = $this->configuracao['data_gravacao'];

        $this->header = new Header($this);

        $this->header->codigo_banco = $this->banco['codigo_do_banco'];
        $this->header->nome_banco = $this->banco['nome_do_banco'];
        $this->header->agencia = $this->configuracao['agencia'];
        $this->header->conta = $this->configuracao['conta'];

        switch ($this->codigo_banco) {
            case \Cnab\Banco::CEF:
                $this->header->codigo_cedente = $this->configuracao['codigo_cedente'];
            case \Cnab\Banco::BRADESCO:
                $this->header->codigo_cedente = $this->configuracao['codigo_cedente'];
                $this->header->sequencial_remessa = $this->configuracao['sequencial_remessa'];
                $this->header->razao_social = $this->configuracao['razao_social'];
                break;
            case \Cnab\Banco::BANCO_DO_BRASIL:
                $this->header->agencia_dv = $this->configuracao['agencia_dac'];
                $this->header->conta_dv = $this->configuracao['conta_dac'];
                $this->header->numero_sequencial = $this->configuracao['numero_sequencial'];
                $this->header->convenio_lider = $this->configuracao['numero_convenio'];                
                break;                    
            default:
                $this->header->conta_dv = $this->configuracao['conta_dac'];
                break;
        }

        $this->header->nome_empresa = $this->configuracao['nome_fantasia'];
        $this->header->data_geracao = $this->configuracao['data_geracao']->format('dmy');
    }

    public function insertDetalhe(array $boleto, $tipo = 'remessa')
    {
        $dateVencimento = $boleto['data_vencimento'] instanceof \DateTime ? $boleto['data_vencimento'] : new \DateTime($boleto['data_vencimento']);
        $dateCadastro = $boleto['data_cadastro']   instanceof \DateTime ? $boleto['data_cadastro']   : new \DateTime($boleto['data_cadastro']);

        $detalhe = new Detalhe($this);
        $complementos = array();

        if ($tipo == 'remessa') {
            $detalhe->codigo_ocorrencia = !empty($boleto['codigo_de_ocorrencia']) ? $boleto['codigo_de_ocorrencia'] : '1';

            $detalhe->codigo_inscricao = 2;
            $detalhe->numero_inscricao = $this->prepareText($this->configuracao['cnpj'], '.-/');

            if (\Cnab\Banco::BRADESCO == $this->codigo_banco) {
                $detalhe->codigo_cedente = $this->header->codigo_cedente;
                $detalhe->digito_nosso_numero = $boleto['digito_nosso_numero'];
            } else if (\Cnab\Banco::CEF == $this->codigo_banco) {
                $detalhe->codigo_cedente = $this->header->codigo_cedente;
                $detalhe->taxa_de_permanencia = $boleto['taxa_de_permanencia'];
                $detalhe->mensagem = $boleto['mensagem'];
                $detalhe->data_multa = $boleto['data_multa'];
                $detalhe->valor_multa = $boleto['valor_multa'];
            } else if (\Cnab\Banco::BANCO_DO_BRASIL == $this->codigo_banco) {
                $detalhe->agencia = $this->header->agencia;
                $detalhe->agencia_dv = $this->header->agencia_dv;
                $detalhe->conta = $this->header->conta;
                $detalhe->conta_dv = $this->header->conta_dv;
                $detalhe->numero_convenio = $boleto['numero_convenio'];
                $detalhe->variacao_carteira = $boleto['variacao_carteira'];
                $detalhe->tipo_cobranca = (isset($boleto['tipo_cobranca']) ?: '');

            } else {
                $detalhe->agencia = $this->header->agencia;
                $detalhe->conta = $this->header->conta;
                $detalhe->conta_dv = $this->header->conta_dv;
                $detalhe->codigo_instrucao = '0';
                $detalhe->qtde_moeda = '0'; # Este campo deverá ser preenchido com zeros caso a moeda seja o Real.
                $detalhe->codigo_carteira = 'I';
                $detalhe->uso_banco = '';
                $detalhe->data_mora = $boleto['data_multa'];

                if ($boleto['valor_multa'] > 0) {
                    /*
                    // Não está presente na documentação disponibilizada no site
                    // os valores de multa devem ser configurados com o gerente da sua conta
                    $detalheMulta = new DetalheMulta($this);
                    if(@$boleto['tipo_multa'] == 'porcentagem')
                        $detalheMulta->codigo_multa = 2;
                    else if(!@$boleto['tipo_multa'] || $boleto['tipo_multa'] == 'valor')
                        $detalheMulta->codigo_multa = 1;
                    else
                        throw new Exception('tipo de multa inválido, deve ser "porcentagem" ou "valor"');
                    $detalheMulta->data_multa = $boleto['data_multa'];
                    $detalheMulta->valor_multa = $boleto['valor_multa'];
                    $complementos[] = $detalheMulta;
                    */
                }
            }

            /*
               Deve ser preenchido na remessa somente quando utilizados, na posição 109-110, os códigos de
               ocorrência 35 – Cancelamento de Instrução e 38 – Cedente não concorda com alegação do sacado. Para
               os demais códigos de ocorrência este campo deverá ser preenchido com zeros.
            */
            $detalhe->uso_empresa = isset($boleto['uso_empresa']) 
                                  ? $boleto['uso_empresa'] 
                                  : $boleto['nosso_numero'];
            $detalhe->nosso_numero = $boleto['nosso_numero'];

            $detalhe->numero_carteira = $boleto['carteira'];
            $detalhe->numero_documento = $boleto['numero_documento'];
            $detalhe->vencimento = $dateVencimento->format('dmy');
            $detalhe->valor_titulo = $boleto['valor'];
            $detalhe->aceite = empty($boleto['aceite']) ? 'N' : $boleto['aceite'];
            $detalhe->instrucao1 = $boleto['instrucao1'];
            $detalhe->instrucao2 = $boleto['instrucao2'];
            $detalhe->especie = $boleto['especie'];
            $detalhe->data_emissao = $dateCadastro->format('dmy');

            $sacado_tipo = @$boleto['sacado_tipo'] or $sacado_tipo = 'cpf';

            if ($sacado_tipo == 'cnpj') {
                $detalhe->sacado_codigo_inscricao = '2';
                /*
                 * @todo Trocar espécie
                 */
                $detalhe->sacado_numero_inscricao = $this->prepareText($boleto['sacado_cnpj'], '.-/');
                $detalhe->nome = $this->prepareText($boleto['sacado_razao_social']);
            } else {
                $detalhe->sacado_codigo_inscricao = '1';
                /*
                 * @todo Trocar espécie
                 */
                $detalhe->sacado_numero_inscricao = $this->prepareText($boleto['sacado_cpf'], '.-/');
                $detalhe->nome = $this->prepareText($boleto['sacado_nome']);
            }
            
            $detalhe->logradouro = $this->prepareText($boleto['sacado_logradouro']);
            $detalhe->bairro = $this->prepareText($boleto['sacado_bairro']);
            $detalhe->cep = str_replace('-', '', $boleto['sacado_cep']);
            $detalhe->cidade = $this->prepareText($boleto['sacado_cidade']);
            $detalhe->estado = $boleto['sacado_uf'];
            $detalhe->sacador = $this->prepareText($this->configuracao['nome_fantasia']);

            $detalhe->juros_um_dia = $boleto['juros_de_um_dia'];
            $detalhe->desconto_ate = $boleto['data_desconto'];
            $detalhe->valor_desconto = $boleto['valor_desconto'];
            $detalhe->prazo = $boleto['prazo'];
        } elseif ($tipo == 'baixa') {
            $detalhe->codigo_inscricao = '0';
            $detalhe->numero_inscricao = '0';
            $detalhe->agencia = $this->header->agencia;
            $detalhe->conta = $this->header->conta;
            $detalhe->conta_dac = $this->header->dac;
            $detalhe->codigo_instrucao = '0';
            /*
               Deve ser preenchido na remessa somente quando utilizados, na posição 109-110, os códigos de
               ocorrência 35 – Cancelamento de Instrução e 38 – Cedente não concorda com alegação do sacado. Para
               os demais códigos de ocorrência este campo deverá ser preenchido com zeros.
            */
            $detalhe->codigo_ocorrencia = $boleto['codigo_de_ocorrencia'];
            $detalhe->uso_empresa = $boleto['nosso_numero'];
            $detalhe->nosso_numero = $boleto['nosso_numero'];
            $detalhe->qtde_moeda = '0'; # Este campo deverá ser preenchido com zeros caso a moeda seja o Real.
            $detalhe->numero_carteira = $boleto['carteira'];
            $detalhe->codigo_carteira = 'I';
            $detalhe->uso_banco = '';
            $detalhe->numero_documento = $boleto['numero_documento'];
            $detalhe->vencimento = '0';
            $detalhe->valor_titulo = $boleto['valor'];
            $detalhe->aceite = ' ';
            $detalhe->data_emissao = '0';
            $detalhe->sacado_codigo_inscricao = '2';
            $detalhe->especie = ' ';
            $detalhe->sacado_numero_inscricao = '0';
            $detalhe->juros_um_dia = $boleto['juros_de_um_dia'];
            $detalhe->data_juros = $boleto['data_juros'];

            $detalhe->nome = ' ';
            $detalhe->logradouro = ' ';
            $detalhe->bairro = ' ';
            $detalhe->cep = '0';
            $detalhe->cidade = ' ';
            $detalhe->estado = ' ';
            $detalhe->sacador = ' ';
        } else {
            throw new Exception('Tipo de $detalhe desconhecido');
        }

        $detalhe->codigo_banco = $this->banco['codigo_do_banco'];

        $this->detalhes[] = $detalhe;

        foreach ($complementos as $complemento) {
            $this->detalhes[] = $complemento;
        }
    }

    public function listDetalhes()
    {
        return $this->detalhes;
    }

    private function prepareText($text, $remove = null)
    {
        $result = strtoupper($this->removeAccents(trim(html_entity_decode($text))));
        if ($remove) {
            $result = str_replace(str_split($remove), '', $result);
        }

        return $result;
    }

    private function removeAccents($string)
    {
        return preg_replace(
            array(
                    '/\xc3[\x80-\x85]/',
                    '/\xc3\x87/',
                    '/\xc3[\x88-\x8b]/',
                    '/\xc3[\x8c-\x8f]/',
                    '/\xc3([\x92-\x96]|\x98)/',
                    '/\xc3[\x99-\x9c]/',

                    '/\xc3[\xa0-\xa5]/',
                    '/\xc3\xa7/',
                    '/\xc3[\xa8-\xab]/',
                    '/\xc3[\xac-\xaf]/',
                    '/\xc3([\xb2-\xb6]|\xb8)/',
                    '/\xc3[\xb9-\xbc]/',
            ),
            str_split('ACEIOUaceiou', 1),
            $this->isUtf8($string) ? $string : utf8_encode($string)
        );
    }

    private function isUtf8($string)
    {
        return preg_match('%^(?:
                 [\x09\x0A\x0D\x20-\x7E]
                | [\xC2-\xDF][\x80-\xBF]
                | \xE0[\xA0-\xBF][\x80-\xBF]
                | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
                | \xED[\x80-\x9F][\x80-\xBF]
                | \xF0[\x90-\xBF][\x80-\xBF]{2}
                | [\xF1-\xF3][\x80-\xBF]{3}
                | \xF4[\x80-\x8F][\x80-\xBF]{2}
                )*$%xs',
                $string
        );
    }

    public function __set($name, $value)
    {
        if (strpos($name, 'data_') === 0) {
            if ($value instanceof \DateTime) {
                $property = "_$name";
                $this->$property = $value;
            } else {
                throw new InvalidArgumentException("$nome need to be instance of DateTime");
            }
        } else {
            throw new Exception("property '$name' dont exists");
        }
    }

    public function getText()
    {
        $numero_sequencial = 1;

        $this->header->numero_sequencial = $numero_sequencial++;

        // valida os dados
        if (!$this->header->validate()) {
            throw new \InvalidArgumentException($this->header->last_error);
        }

        $dados = $this->header->getEncoded().self::QUEBRA_LINHA;
        $this->trailer = new Trailer($this);
        foreach ($this->detalhes as $detalhe) {
            $detalhe->numero_sequencial = $numero_sequencial++;
            if (!$detalhe->validate()) {
                throw new \InvalidArgumentException($detalhe->last_error);
            }

            $dados .= $detalhe->getEncoded().self::QUEBRA_LINHA;
        }
        $this->trailer->numero_sequencial = $numero_sequencial++;

        if (!$this->trailer->validate()) {
            throw new \InvalidArgumentException($this->trailer->last_error);
        }

        $dados .= $this->trailer->getEncoded().self::QUEBRA_LINHA;

        return $dados;
    }

    public function countDetalhes()
    {
        return count($this->detalhes);
    }

    public function save($filename)
    {
        $text = $this->getText();
        file_put_contents($filename, $text);
    }
}
