# -*- encoding: utf-8 -*-
# @author Kivanio Barbosa
module Brcobranca
  module Boleto
    # Classe base para todas as classes de boletos
    class Base
      extend Template::Base

      # Configura gerador de arquivo de boleto e código de barras.
      extend define_template(Brcobranca.configuration.gerador)
      include define_template(Brcobranca.configuration.gerador)

      # Validações do Rails 3
      include ActiveModel::Validations

      # <b>REQUERIDO</b>: Número do convênio/contrato do cliente junto ao banco emissor
      attr_accessor :convenio
      # <b>REQUERIDO</b>: Tipo de moeda utilizada (Real(R$) e igual a 9)
      attr_accessor :moeda
      # <b>REQUERIDO</b>: Carteira utilizada
      attr_accessor :carteira
      # <b>OPCIONAL</b>: Variacao da carteira(opcional para a maioria dos bancos)
      attr_accessor :variacao
      # <b>OPCIONAL</b>: Data de processamento do boleto, geralmente igual a data_documento
      attr_accessor :data_processamento
      # <b>REQUERIDO</b>: Número de dias a vencer
      attr_accessor :dias_vencimento
      # <b>REQUERIDO</b>: Quantidade de boleto(padrão = 1)
      attr_accessor :quantidade
      # <b>REQUERIDO</b>: Valor do boleto
      attr_accessor :valor
      # <b>REQUERIDO</b>: Número da agencia sem <b>Digito Verificador</b>
      attr_accessor :agencia
      # <b>REQUERIDO</b>: Número da conta corrente sem <b>Digito Verificador</b>
      attr_accessor :conta_corrente
      # <b>REQUERIDO</b>: Nome do proprietario da conta corrente
      attr_accessor :cedente
      # <b>REQUERIDO</b>: Documento do proprietario da conta corrente (CPF ou CNPJ)
      attr_accessor :documento_cedente
      # <b>OPCIONAL</b>: Número sequencial utilizado para identificar o boleto
      attr_accessor :numero_documento
      # <b>REQUERIDO</b>: Símbolo da moeda utilizada (R$ no brasil)
      attr_accessor :especie
      # <b>REQUERIDO</b>: Tipo do documento (Geralmente DM que quer dizer Duplicata Mercantil)
      attr_accessor :especie_documento
      # <b>REQUERIDO</b>: Data em que foi emitido o boleto
      attr_accessor :data_documento
      # <b>OPCIONAL</b>: Código utilizado para identificar o tipo de serviço cobrado
      attr_accessor :codigo_servico
      # <b>OPCIONAL</b>: Utilizado para mostrar alguma informação ao sacado
      attr_accessor :instrucao1
      # <b>OPCIONAL</b>: Utilizado para mostrar alguma informação ao sacado
      attr_accessor :instrucao2
      # <b>OPCIONAL</b>: Utilizado para mostrar alguma informação ao sacado
      attr_accessor :instrucao3
      # <b>OPCIONAL</b>: Utilizado para mostrar alguma informação ao sacado
      attr_accessor :instrucao4
      # <b>OPCIONAL</b>: Utilizado para mostrar alguma informação ao sacado
      attr_accessor :instrucao5
      # <b>OPCIONAL</b>: Utilizado para mostrar alguma informação ao sacado
      attr_accessor :instrucao6
      # <b>OPCIONAL</b>: Utilizado para mostrar alguma informação ao sacado
      attr_accessor :instrucao7
      # <b>REQUERIDO</b>: Informação sobre onde o sacado podera efetuar o pagamento
      attr_accessor :local_pagamento
      # <b>REQUERIDO</b>: Informa se o banco deve aceitar o boleto após o vencimento ou não( S ou N, quase sempre S)
      attr_accessor :aceite
      # <b>REQUERIDO</b>: Nome da pessoa que receberá o boleto
      attr_accessor :sacado
      # <b>OPCIONAL</b>: Endereco da pessoa que receberá o boleto
      attr_accessor :sacado_endereco
      # <b>REQUERIDO</b>: Documento da pessoa que receberá o boleto
      attr_accessor :sacado_documento

      # Validações
      validates_presence_of :agencia, :conta_corrente, :moeda, :especie_documento, :especie, :aceite, :numero_documento, :message => "não pode estar em branco."
      validates_numericality_of :convenio, :agencia, :conta_corrente, :numero_documento, :message => "não é um número.", :allow_nil => true

      # Nova instancia da classe Base
      # @param [Hash] campos
      def initialize(campos={})
        padrao = {
          :moeda => "9", :data_documento => Date.today, :dias_vencimento => 1, :quantidade => 1,
          :especie_documento => "DM", :especie => "R$", :aceite => "S", :valor => 0.0,
          :local_pagamento => "QUALQUER BANCO ATÉ O VENCIMENTO"
        }

        campos = padrao.merge!(campos)
        campos.each do |campo, valor|
          send "#{campo}=", valor
        end

        yield self if block_given?
      end

      # Logotipo do banco
      # @return [Path] Caminho para o arquivo de logotipo do banco.
      def logotipo
        File.join(File.dirname(__FILE__),'..','arquivos','logos',"#{class_name}.jpg")
      end

      # Dígito verificador do banco
      # @return [Integer] 1 caracteres numéricos.
      def banco_dv
        self.banco.modulo11_9to2
      end

      # Código da agencia
      # @return [String] 4 caracteres numéricos.
      def agencia=(valor)
        @agencia = valor.to_s.rjust(4,'0') unless valor.nil?
      end

      # Dígito verificador da agência
      # @return [Integer] 1 caracteres numéricos.
      def agencia_dv
        self.agencia.modulo11_9to2
      end

      # Dígito verificador da conta corrente
      # @return [Integer] 1 caracteres numéricos.
      def conta_corrente_dv
        self.conta_corrente.modulo11_9to2
      end

      # Dígito verificador do nosso número
      # @return [Integer] 1 caracteres numéricos.
      def nosso_numero_dv
        self.numero_documento.modulo11_9to2
      end

      # @abstract Deverá ser sobreescrito para cada banco.
      def nosso_numero_boleto
        raise Brcobranca::NaoImplementado.new("Sobreescreva este método na classe referente ao banco que você esta criando")
      end

      # @abstract Deverá ser sobreescrito para cada banco.
      def agencia_conta_boleto
        raise Brcobranca::NaoImplementado.new("Sobreescreva este método na classe referente ao banco que você esta criando")
      end

      # Valor total do documento: <b>quantidate * valor</b>
      # @return [Float]
      def valor_documento
        self.quantidade.to_f * self.valor.to_f
      end

      # Data de vencimento baseado na <b>data_documento + dias_vencimento</b>
      #
      # @return [Date]
      # @raise [ArgumentError] Caso {#data_documento} esteja em branco.
      def data_vencimento
        raise ArgumentError, "data_documento não pode estar em branco." unless self.data_documento
        return self.data_documento unless self.dias_vencimento
        (self.data_documento + self.dias_vencimento.to_i)
      end

      # Fator de vencimento calculado com base na data de vencimento do boleto.
      # @return [String] 4 caracteres numéricos.
      def fator_vencimento
        self.data_vencimento.fator_vencimento
      end

      # Número da conta corrente
      # @return [String] 7 caracteres numéricos.
      def conta_corrente=(valor)
        @conta_corrente = valor.to_s.rjust(7,'0') unless valor.nil?
      end

      # Codigo de barras do boleto
      #
      # O codigo de barra para cobrança contém 44 posições dispostas da seguinte forma:<br/>
      # Posição |Tamanho |Conteúdo<br/>
      # 01 a 03 | 3  | Identificação do Banco<br/>
      # 04 a 04 | 1  | Código da Moeda (Real = 9, Outras=0)<br/>
      # 05 a 05 | 1  |  Dígito verificador do Código de Barras<br/>
      # 06 a 09 | 4  | Fator de Vencimento (Vide Nota)<br/>
      # 10 a 19 | 10 |  Valor<br/>
      # 20 a 44 | 25 |  Campo Livre - As posições do campo livre ficam a critério de cada Banco arrecadador.<br/>
      #
      # @raise [Brcobranca::BoletoInvalido] Caso as informações fornecidas não sejam suficientes ou sejam inválidas.
      # @return [String] código de barras formado por 44 caracteres numéricos.
      def codigo_barras
        raise Brcobranca::BoletoInvalido.new(self) unless self.valid?
        codigo = codigo_barras_primeira_parte #18 digitos
        codigo << codigo_barras_segunda_parte #25 digitos
        if codigo =~ /^(\d{4})(\d{39})$/
          codigo_dv = codigo.modulo11_2to9
          codigo = "#{$1}#{codigo_dv}#{$2}"
          codigo
        else
          raise Brcobranca::BoletoInvalido.new(self)
        end
      end

      # Monta a segunda parte do código de barras, que é específico para cada banco.
      #
      # @abstract Deverá ser sobreescrito para cada banco.
      def codigo_barras_segunda_parte
        raise Brcobranca::NaoImplementado.new("Sobreescreva este método na classe referente ao banco que você esta criando")
      end

      private

      # Monta a primeira parte do código de barras, que é a mesma para todos banco.
      # @return [String] 18 caracteres numéricos.
      def codigo_barras_primeira_parte
        "#{self.banco}#{self.moeda}#{self.fator_vencimento}#{valor_documento_formatado}"
      end

      # Valor total do documento
      # @return [String] 10 caracteres numéricos.
      def valor_documento_formatado
        self.valor_documento.limpa_valor_moeda.to_s.rjust(10,'0')
      end

      # Nome da classe do boleto
      # @return [String]
      def class_name
        self.class.to_s.split("::").last.downcase
      end

    end
  end
end