<?php

class ModelShippingCorreiosApi extends correiosUtils
{
  private $valor_max = 10000;  // máximo valor declarado, em reais
  private $altura_max = 105;    // todas as medidas em cm
  private $largura_max = 105;
  private $comprimento_max = 105;
  private $lado_max = 105; //Medida utilizada para a validação dos volumes, estava setada com 50 e por esse motivo não estava calculando
  private $cubagem_max;
  private $cubagem_min;
  private $altura_min = 2;
  private $largura_min = 11;
  private $comprimento_min = 16;
  private $soma_dim_max = 200;  // medida máxima das somas da altura, largura, comprimento
  private $soma_dim_min = 1;
  private $peso_max = 30.0;  // padrão, em kg
  private $peso_min = 0.300;        // em kg
  private $nCdServico = array();
  private $servicos = array();
  private $url = '';
  private $quote_data = array();
  private $cep_destino;
  private $cep_origem;
  private $contrato_codigo = '';
  private $contrato_senha = '';
  private $mensagem_erro = array();
  private $correios = array();
  private $data_volumes = array();

  private function setDataVolumes($data_volumes,$pesoMax,$servico=null) {
    if  ($servico != null){
      $this->data_volumes[$servico][$pesoMax][] = $data_volumes;
    }else{
      $this->data_volumes[$pesoMax][] = $data_volumes;
    }
	}

  private function setPesoMax($peso)
  {
    $this->peso_max = $peso;
  }

  private function setServicos($servicos)
  {
    $this->servicos = $servicos;
  }

  private function validaCorreioSemValorAdd($code)
  {
    $this->load->type('tipocomum');
    $this->load->helper('ftr_geral');
    $correiosSemValorAdd = Type_CorreiosSemValorAdd::getEnumList();
    if (in_array($code, $correiosSemValorAdd)) {
      return true;
    } else {
      return false;
    }
  }

  private function validaCorreioSemPeriodoAdd($code)
  {
    $this->load->type('tipocomum');
    $this->load->helper('ftr_geral');
    $correiosSemPeriodoAdd = Type_CorreiosSemPeriodoAdd::getEnumList();
    if (in_array($code, $correiosSemPeriodoAdd)) {
      return true;
    } else {
      return false;
    }
  }

  // função responsável pelo retorno à loja dos valores finais dos valores dos fretes
  public function getQuote($address, $pProduct = Array())
  {

    $this->load->language('shipping/correios');
    $this->load->type('tipocomum');
    $this->load->helper('ftr_geral');

    //feito a principio para skyhub Frete, mais pode ser utilizado para qualquer frete que
    //não tenha informações do estado...
    if ((!$address['zone_id']) && ($address['layout_id'] == Type_SendPostcode::Tp_Product)) {
      $status = true;
    } else {
      $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "zone_to_geo_zone WHERE geo_zone_id = '" . (int)$this->config->get('correios_geo_zone_id') . "' AND country_id = '" . (int)$address['country_id'] . "' AND (zone_id = '" . (int)$address['zone_id'] . "' OR zone_id = '0')");

      if (!$this->config->get('correios_geo_zone_id')) {
        $status = true;
      } elseif ($query->num_rows) {
        $status = true;
      } else {
        $status = false;
      }

    }

    $method_data = array();

    if ($status) {

      if (sizeof($pProduct) <= 0) {
        $produtos = $this->cart->getProducts();
      } else {
        $produtos = $pProduct;
      }

      $servicosCorreios = array();
      $servicosCorreios = $this->config->get('correios_servicos');
      if (!isset($servicosCorreios)) {
        $servicosCorreios = $this->verificaServicosExistentes();
      }


      $row = 0;

      foreach ($servicosCorreios as $servicoCorreios) {
        if ($servicoCorreios['status'] == 1) {
          $this->correios[$row]['codigo'] = trim($servicoCorreios['codigo']);
          $this->correios[$row]['nome'] = $servicoCorreios['nome'];
          $this->correios[$row]['pesomax'] = $servicoCorreios['pesomax'];
          $this->correios[$row]['contrato'] = $servicoCorreios['contrato'];
          $row = $row + 1;

        }
      }


      // obtém só a parte numérica do CEP
      $this->cep_origem = preg_replace("/[^0-9]/", '', $this->config->get('correios_postcode'));
      $this->cep_destino = preg_replace("/[^0-9]/", '', $address['postcode']);

      if (ftr_geral::isIsset($this->config->get('correios_tipo_calculo')) && ($this->config->get('correios_tipo_calculo') == Type_CorreiosTipoCalculo::Tp_CorreiosTpCalcVolume)) {
        $this->cubagem_max = pow(($this->soma_dim_max / 3), 3);
        $this->cubagem_min = pow(($this->soma_dim_min / 3), 3);
      }

      foreach ($this->correios as $correio) {
        if (trim($this->config->get('correios_contrato_codigo')) != "" && trim($this->config->get('correios_contrato_senha')) != "") {
          $this->nCdServico[$correio['pesomax']][] = $correio['codigo'];
          //Agrupa por peso por regras que serão aplicadas nas caixas, como não temos o peso da caixa agora, então precisa ser separados os serviços....
          //Ps. Normalmente devera ser o peso padrão(30), entãoa cabara sendo uma req.
          //também está feito dessa forma devido a frescura do correio de que só envia multiplas requisições quando o cliente tem contrato.
        } else {
          $this->nCdServico[] = array(
            'pesomax' => $correio['pesomax'],
            'codigo' => array($correio['codigo'])//Correio resolveu que não calcula mais multiplos serviços, só mudei aqui porque se mudarem de ideia de novo é só voltar o código
          );
        }
      }
      if (trim($this->config->get('correios_contrato_codigo')) != "" && trim($this->config->get('correios_contrato_senha')) != "") {
        $this->contrato_codigo = $this->config->get('correios_contrato_codigo');
        $this->contrato_senha = $this->config->get('correios_contrato_senha');
      }

      foreach ($this->nCdServico as $key => $servico) {
        /* Organizando e cotando as caixas por grupos de peso diferentes e naõ serviços, dessa forma não cria todas as caixas inumeras vezes desnecessariamente */

        if (isset($servico['codigo'])) {
          $this->setPesoMax($servico['pesomax']);
          $this->setServicos($servico['codigo']);
        } else {
          $this->setPesoMax($key);
          $this->setServicos($servico);
        }

        $caixas = $this->organizarEmCaixas($produtos);

        foreach ($caixas as $caixa) {
          if (!ftr_geral::isIsset($this->config->get('correios_tipo_calculo')) || ($this->config->get('correios_tipo_calculo') == Type_CorreiosTipoCalculo::Tp_CorreiosTpCalcDimensao)) {
            $volume = array(
              'weight' => $caixa['weight'],
              'width' => $caixa['width'],
              'height' => $caixa['height'],
              'lenght' => $caixa['length'],
              'cost_of_goods' => $this->getTotalCaixa($caixa['produtos'])
            );
          } else {
            $medida_lados = ($caixa['cubagem'] >= $this->cubagem_min) ? $this->raizCubica($caixa['cubagem']) : $this->raizCubica($this->cubagem_min);
            if ($medida_lados > 0) {
              $comp = ($medida_lados < $this->comprimento_min) ? $this->comprimento_min : $medida_lados;
              $larg = ($medida_lados < $this->largura_min) ? $this->largura_min : $medida_lados;
              $alt = ($medida_lados < $this->altura_min) ? $this->altura_min : $medida_lados;
            }
            $volume = array(
              'weight' => $caixa['peso'],
              'width' => $larg,
              'height' => $alt,
              'lenght' => $comp,
              'cost_of_goods' => $this->getTotalCaixa($caixa['produtos'])
            );
          }

          if (isset($servico['codigo'])) {
            $this->setDataVolumes($volume,$servico['pesomax'],$servico['codigo'][0]);
          } else {
            $this->setDataVolumes($volume,$key);
          }

          $this->setQuoteData($caixa);
        }
      }

      // ajustes finais
      if ($this->quote_data) {
        $valor_adicional = (is_numeric($this->config->get('correios_adicional'))) ? $this->config->get('correios_adicional') : 0;

        foreach ($this->quote_data as $codigo => $data) {
          // soma o valor adicional ao valor final do frete - não aplicado ao Sedex a Cobrar
          if (!$this->validaCorreioSemValorAdd($codigo)) {
            $new_cost = $this->quote_data[$codigo]['cost'] + ($this->quote_data[$codigo]['cost'] * ($valor_adicional / 100));
            // novo custo
            $this->quote_data[$codigo]['cost'] = $new_cost;
            // novo texto
            $this->quote_data[$codigo]['text'] = $this->currency->format($new_cost);
          } else {
            // zera o valor do frete do Sedex a Cobrar para evitar de ser adiconado ao valor do carrinho
            $this->quote_data[$codigo]['cost'] = 0;
          }
          $this->quote_data[$codigo]['cost'] = round((float)$this->quote_data[$codigo]['cost'], 2, PHP_ROUND_HALF_UP);

          //Os volumes são calculados com base no peso e não no serviço, para não fazer a organização de caixas multiplas vezes desnecessariamente.
          if ( trim($this->config->get('correios_contrato_codigo')) != "" && trim($this->config->get('correios_contrato_senha')) != "") {
            $this->quote_data[$codigo]['volumes_info'] = $this->data_volumes[$this->quote_data[$codigo]['peso_max']];
          }else{
            $this->quote_data[$codigo]['volumes_info'] = $this->data_volumes[$codigo][$this->quote_data[$codigo]['peso_max']];
          }
        }

        $method_data = array(
          'code' => 'correios',
          'title' => $this->language->get('text_title'),
          'quote' => $this->quote_data,
          'sort_order' => $this->config->get('correios_sort_order'),
          'error' => false
        );

        return $method_data;
      } else if (!empty($this->mensagem_erro)) {
        $method_data = array(
          'code' => 'correios',
          'title' => $this->language->get('text_title'),
          'quote' => $this->quote_data,
          'sort_order' => $this->config->get('correios_sort_order'),
          'error' => implode('<br />', $this->mensagem_erro)
        );
      }
    }


  }

  // obtém os dados dos fretes para os produtos da caixa
  private function setQuoteData($caixa)
  {
    $volume = array();

    // obtém o valor total da caixa
    if ($this->config->get('correios_declarar_valor') == 'n') {
      $total_caixa = 0;
    } else {
      $total_caixa = $this->getTotalCaixa($caixa['produtos']);
      $total_caixa = ($total_caixa > $this->valor_max) ? $this->valor_max : $total_caixa;
      if (ftr_geral::isIsset($this->config->get('correios_minimo_declarar')) && ($this->config->get('correios_minimo_declarar') <> '')) {
        $total_caixa = ($total_caixa > $this->config->get('correios_minimo_declarar')) ? $total_caixa : 0;
      } else {
        $total_caixa = ($total_caixa > 0) ? $total_caixa : 0;
      }
    }

    // fazendo a chamada ao site dos Correios e obtendo os dados
    if (!ftr_geral::isIsset($this->config->get('correios_tipo_calculo')) || ($this->config->get('correios_tipo_calculo') == Type_CorreiosTipoCalculo::Tp_CorreiosTpCalcDimensao)) {
      list($weight, $height, $width, $length) = $this->ajustarDimensoes($caixa);
      $servicos = $this->getServicos($weight, $total_caixa, $length, $width, $height);
    } else {
      $servicos = $this->getServicos($caixa['peso'], $total_caixa, 0, 0, 0, $caixa['cubagem']);
    }

    //Serviços calculados de acordo com o peso máximo
    foreach ($servicos as $servico) {

      // o site dos Correios retornou os dados sem erros.

      $valor_frete_sem_adicionais =
      ($servico['Valor'] != "" ? $servico['Valor'] : 0) -
      ($servico['ValorAvisoRecebimento'] != "" ? $servico['ValorAvisoRecebimento'] : 0) -
      ($servico['ValorMaoPropria'] != "" ? $servico['ValorMaoPropria'] : 0) -
      ($servico['ValorValorDeclarado'] != "" ? $servico['ValorValorDeclarado'] : 0);


      //if($servico['Erro'] == 0 && $valor_frete_sem_adicionais > 0) {
      if ($valor_frete_sem_adicionais > 0) {

        // subtrai do valor do frete as opções desabilitadas nas configurações do módulo - 'declarar valor' é obrigatório para sedex a cobrar
        $cost = $servico['Valor'];

        // o valor do frete para a caixa atual é somado ao valor total já calculado para outras caixas
        if (isset($this->quote_data[$servico['Codigo']])) {
          $cost += $this->quote_data[$servico['Codigo']]['cost'];
        }


        foreach ($this->correios as $correio) {
          if ($correio['codigo'] == $servico['Codigo']) {
            $textServico = $correio['nome'];
          }
        }

        if ($this->validaCorreioSemValorAdd($servico['Codigo'])) {
          $title = sprintf($textServico . $this->language->get('text_periodoValorEntrega'), $servico['PrazoEntrega'], $this->currency->format($cost));
          $text = $this->currency->format($cost);
        } else {
          $title = sprintf($textServico . $this->language->get('text_periodoEntrega'), $servico['PrazoEntrega']);
          $text = $this->currency->format($cost);
        }

        $this->quote_data[$servico['Codigo']] = array(
          'code' => 'correios.' . $servico['Codigo'],
          'title' => $title,
          'days' => $servico['PrazoEntrega'],
          'cost' => round((float)$cost, 2, PHP_ROUND_HALF_UP),
          'peso_max' => $this->peso_max,
          'text' => $text
        );
      } // grava no log de erros do OpenCart a mensagem de erro retornado pelos Correios
      else {
        $this->mensagem_erro[] = $servico['Codigo'] . ': ' . $servico['MsgErro'];
        $this->log->write($servico['Codigo'] . ': ' . $servico['MsgErro']);
      }
    }
  }

  // prepara a url de chamada ao site dos Correios
  private function setUrl($peso, $valor, $comp, $larg, $alt, $maopropria, $aviso, $medida_lados = 0)
  {

    if ($medida_lados > 0) {
      $comp = ($medida_lados < $this->comprimento_min) ? $this->comprimento_min : $medida_lados;
      $larg = ($medida_lados < $this->largura_min) ? $this->largura_min : $medida_lados;
      $alt = ($medida_lados < $this->altura_min) ? $this->altura_min : $medida_lados;
    }

    $medida_lados = str_replace('.', ',', $medida_lados);
    $peso = str_replace('.', ',', $peso);

    $this->url = [$peso, $comp, $larg, $alt, $maopropria, $valor, $aviso];
  }

  // conecta ao sites dos Correios e obtém o arquivo XML com os dados do frete
  private function getXML($url)
  {

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);

    $result = curl_exec($ch);

    if (!$result) {
      $this->log->write(curl_error($ch));
      $this->log->write($this->language->get('error_conexao'));
      $result = curl_exec($ch);

      if ($result) {
        $this->log->write($this->language->get('text_sucesso'));
      } else {
        $this->log->write(curl_error($ch));
        $this->log->write($this->language->get('error_reconexao'));
      }
    }

    curl_close($ch);

    $result = str_replace('&amp;lt;sup&amp;gt;&amp;amp;reg;&amp;lt;/sup&amp;gt;', '', $result);
    $result = str_replace('&amp;lt;sup&amp;gt;&amp;amp;trade;&amp;lt;/sup&amp;gt;', '', $result);
    $result = str_replace('**', '', $result);
    $result = str_replace("\r\n", '', $result);
    $result = str_replace('\"', '"', $result);

    return $result;
  }

  // faz a chamada e lê os dados no arquivo XML retornado pelos Correios
  public function getServicos($peso, $valor, $comp, $larg, $alt, $cubagem = 0)
  {

    $dados = array();

    // troca o separador decimal de ponto para vírgula nos dados a serem enviados para os Correios
    $valor = round($valor);
    $valor_adicional_medida = 0;
    $medida_lados = 0;
    if ($cubagem == 0) {
      $comp = str_replace('.', ',', $comp);
      $larg = str_replace('.', ',', $larg);
      $alt = str_replace('.', ',', $alt);
      $peso = str_replace('.', ',', $peso);
      // ajusta a url de chamada
      $this->setUrl($peso, $valor, $comp, $larg, $alt, $this->config->get('correios_mao_propria'), $this->config->get('correios_aviso_recebimento'));
    } else {
      // troca o separador decimal de ponto para vírgula nos dados a serem enviados para os Correios
      $peso = ($peso >= $this->peso_min) ? $peso : $this->peso_min;
      // total é arredondado pois algumas vezes o WebService dos Correios não aceita centavos
      $valor = round($valor);
      // medida dos lados da caixa é a raiz cúbica da cubagem
      $medida_lados = ($cubagem >= $this->cubagem_min) ? $this->raizCubica($cubagem) : $this->raizCubica($this->cubagem_min);
      // ajusta a url de chamada
      $this->setUrl($peso, $valor, 0, 0, 0, $this->config->get('correios_mao_propria'), $this->config->get('correios_aviso_recebimento'), $medida_lados);
    }

    if ((($this->config->get('correios_medida_para_val_adicional') != null) && (trim($this->config->get('correios_medida_para_val_adicional') <> ''))) &&
      (($this->config->get('correios_val_adicional_medida') != null) && (trim($this->config->get('correios_val_adicional_medida') <> '')))) {
      //Valor adicional cobrado pelo correio apra medidas excedentes
      $medidaLimite = $this->config->get('correios_medida_para_val_adicional');
      if ($comp >= $medidaLimite || $larg >= $medidaLimite || $alt >= $medidaLimite || $medida_lados >= $medidaLimite) {
        $valor_adicional_medida = $this->config->get('correios_val_adicional_medida');
      }
    }

    $resultPrazo = [];

    for($countLot = 0; $countLot < ceil(count($this->servicos)/5) ; $countLot++ ){
      $prazo = array(
        "idLote"=> $countLot +1,
        "parametrosPrazo"=> $this->getServices($countLot)
      );

      $prazo = $this->getCorreiosPrazo($prazo);

      if($prazo){
        $resultPrazo = array_merge($resultPrazo, $prazo);
      }
    }

    foreach ($resultPrazo as $prazoValue) {
      $preco = array(
        'coProduto' => $prazoValue->coProduto,
        'cepDestino' => $this->cep_destino,
        'cepOrigem' => $this->cep_origem,
        'psObjeto' => $this->url[0],
        'tpObjeto' => '2',
        'comprimento' => $this->url[1],
        'largura' => $this->url[2],
        'altura' => $this->url[3],
        'servicosAdicionais' => $this->getServicosAdicionais($prazoValue->coProduto),
        'vlDeclarado' => $this->url[5],
      );

      $resultPreco = $this->getCorreiosPreco($preco);

      //TODO Correios - Validar preço e fazer preenchimento.
      if(isset($prazoValue->msgs) or isset($resultPreco->msgs)){
        $this->log->write('Correios: '.(isset($prazoValue->msgs)? $prazoValue->msgs[0]: '').' / '. (isset($resultPreco->msgs)? $resultPreco->msgs[0]: ''));
      }

      // obtendo o prazo adicional a ser somado com o dos Correios
      if($prazoValue->coProduto != '40215' || $prazoValue->coProduto != '40290'){
        $prazo_adicional = (is_numeric($this->config->get('correios_prazo_adicional'))) ? intval($this->config->get('correios_prazo_adicional')) : 0;
        $prazo_num = intval($prazo_adicional) ? intval($prazo_adicional) : '0';
      };

      $dados[$prazoValue->coProduto] = array(
        "Codigo" => isset($prazoValue->coProduto)? $prazoValue->coProduto :'0',
        "Valor" => str_replace(',', '.', isset($resultPreco->pcFinal)? $resultPreco->pcFinal : '0'),
        "PrazoEntrega" => (isset($prazoValue->prazoEntrega)?$prazoValue->prazoEntrega + $prazo_num : '0'),
        "Erro" => (isset($prazoValue->msgs) or isset($resultPreco->msgs)) ? true : false,
        "MsgErro" => (isset($prazoValue->msgs)? $prazoValue->msgs[0]: '').' / '. (isset($resultPreco->msgs)? $resultPreco->msgs[0]: ''),
        "ValorMaoPropria" => (isset($resultPreco->pcTotalServicosAdicionais)) ? str_replace(',', '.', $resultPreco->pcTotalServicosAdicionais) : 0,
        "ValorAvisoRecebimento" =>  0,
        "ValorValorDeclarado" =>  0
      );
    }

    return $dados;
  }

  private function getServicosAdicionais($coProduto){
    $listOpcionais = [];
    $list = [];
    $listObriga = [];

    $listValues = $this->gerServicosObrigatorios($coProduto);

    $listObriga = array_filter($listValues,function($value){
      if($value->coIndicador === '01'){
        return $value;
      }
    });

    $list = array_filter($listValues,function($value){
      if($value->coIndicador === '02'){
        return $value;
      }
    });

    $listOpcionais = array_filter($listValues,function($value){
      if($value->coIndicador === '03'){
        return $value;
      }
    });



    $code = [];

    if($this->config->get('correios_mao_propria') == Type_DsDn::Tp_Sim){
      if(in_array(Type_servicosAdicionais::Tp_correios_mao_propria, array_column($listOpcionais,'codigo'))) {
        array_push($code, Type_servicosAdicionais::Tp_correios_mao_propria);
      }
    }

    if($this->config->get('correios_declarar_valor') == Type_DsDn::Tp_Sim){
      if(in_array(Type_servicosAdicionais::Tp_correios_vlr_declarado_na, array_column($listOpcionais,'codigo'))){
        array_push($code,Type_servicosAdicionais::Tp_correios_vlr_declarado_na);
      }else if(in_array(Type_servicosAdicionais::Tp_correios_declarado_avista, array_column($listOpcionais,'codigo'))){
        array_push($code,Type_servicosAdicionais::Tp_correios_declarado_avista);
      }else if(in_array(Type_servicosAdicionais::Tp_correios_declarado_standard, array_column($listOpcionais,'codigo'))){
        array_push($code,Type_servicosAdicionais::Tp_correios_declarado_standard);
      }else if(in_array(Type_servicosAdicionais::Tp_correios_declarado_mini, array_column($listOpcionais,'codigo'))){
        array_push($code,Type_servicosAdicionais::Tp_correios_declarado_mini);
      }else if(in_array(Type_servicosAdicionais::Tp_correios_declarado_Expre_RFID, array_column($listOpcionais,'codigo'))){
        array_push($code,Type_servicosAdicionais::Tp_correios_declarado_Expre_RFID);
      }else if(in_array(Type_servicosAdicionais::Tp_correios_declarado_stand_RFID, array_column($listOpcionais,'codigo'))){
        array_push($code,Type_servicosAdicionais::Tp_correios_declarado_stand_RFID);
      }
    }

    if($this->config->get('correios_aviso_recebimento') == Type_DsDn::Tp_Sim){
      if(in_array(Type_servicosAdicionais::Tp_correios_aviso_recebimento, array_column($listOpcionais,'codigo')) and ($this->config->get('correios_aviso_recebimento_eletr') == Type_DsDn::Tp_Nao)){
        array_push($code,Type_servicosAdicionais::Tp_correios_aviso_recebimento);
      }else{
        if(in_array(Type_servicosAdicionais::Tp_correios_recebimento_eletr, array_column($listOpcionais,'codigo'))){
          array_push($code,Type_servicosAdicionais::Tp_correios_recebimento_eletr);
        }
      }
    }

//    if($list){
//      $code = array_merge($code, array_column($list,'codigo'));
//    }

    if($listObriga){
      $code = array_merge($code, array_column($listObriga,'codigo'));
    }

    return implode(',',$code);

  }

  private function getServices($countLot){

    $parametrosPrazo = [];

    foreach ( $this->servicos as $key => $value){
      if($key >= ((($countLot+1) * 5) - 5) and $key < ((($countLot+1) * 5))){
        array_push($parametrosPrazo, array(
          "coProduto"=> $value,
          "nuRequisicao"=> ($key +1),
          "cepOrigem"=> $this->cep_origem,
          "cepDestino"=> $this->cep_destino,
          "dataPostagem"=> $this->db->dataAtual()
        ));
      }

    }
    return $parametrosPrazo;
  }

  // retorna a dimensão em centímetros
  private function getDimensaoEmCm($unidade_id, $dimensao)
  {

    if (is_numeric($dimensao)) {
      $length_class_product_query = $this->db->query("SELECT * FROM " . DB_PREFIX . "length_class mc LEFT JOIN " . DB_PREFIX . "length_class_description mcd ON (mc.length_class_id = mcd.length_class_id) WHERE mcd.language_id = '" . (int)$this->config->get('config_language_id') . "' AND mc.length_class_id =  '" . (int)$unidade_id . "'");

      if (isset($length_class_product_query->row['unit'])) {
        if ($length_class_product_query->row['unit'] == 'mm') {
          return $dimensao / 10;
        }
      }
    }
    return $dimensao;
  }

  // retorna o peso em quilogramas
  private function getPesoEmKg($unidade_id, $peso)
  {

    if (is_numeric($peso)) {
      $weight_class_product_query = $this->db->query("SELECT * FROM " . DB_PREFIX . "weight_class wc LEFT JOIN " . DB_PREFIX . "weight_class_description wcd ON (wc.weight_class_id = wcd.weight_class_id) WHERE wcd.language_id = '" . (int)$this->config->get('config_language_id') . "' AND wc.weight_class_id =  '" . (int)$unidade_id . "'");

      if (isset($weight_class_product_query->row['unit'])) {
        if ($weight_class_product_query->row['unit'] == 'g') {
          return ($peso / 1000);
        }
      }
    }
    return $peso;
  }

  // pré-validação das dimensões e peso do produto
  private function validar($produto)
  {

    if (!is_numeric($produto['height']) || !is_numeric($produto['width']) || !is_numeric($produto['length']) || !is_numeric($produto['weight'])) {
      $this->log->write(sprintf($this->language->get('error_dim'), $produto['name']));
      return false;
    }

    $altura = $produto['height'];
    $largura = $produto['width'];
    $comprimento = $produto['length'];
    $peso = $produto['weight'];

    if ($altura > $this->altura_max || $largura > $this->largura_max || $comprimento > $this->comprimento_max) {
      $this->log->write(sprintf($this->language->get('error_dim_limite'), $this->comprimento_max, $this->largura_max, $this->altura_max, $produto['name'], $comprimento, $largura, $altura));
      return false;
    }

    $soma_dim = $altura + $largura + $comprimento;
    if ($soma_dim > $this->soma_dim_max) {
      $this->log->write(sprintf($this->language->get('error_dim_soma'), $this->soma_dim_max, $produto['name'], $soma_dim));
      return false;
    }

    if ($peso > $this->peso_max) {
      $this->log->write(sprintf($this->language->get('error_peso'), $this->peso_max, $produto['name'], $peso));
      return false;
    }

    return true;
  }

  private function validarPorVolume($produto)
  {

    if (empty($produto['height'])) {
      $produto['height'] = $this->altura_min;
    }
    if (empty($produto['width'])) {
      $produto['width'] = $this->largura_min;
    }
    if (empty($produto['length'])) {
      $produto['length'] = $this->comprimento_min;
    }
    if (empty($produto['weight'])) {
      $produto['weight'] = $this->peso_min;
    }

    $cubagem = (float)$produto['height'] * (float)$produto['width'] * (float)$produto['length'];

    if ($produto['weight'] > $this->peso_max || $cubagem > $this->cubagem_max || $produto['height'] > $this->lado_max || $produto['width'] > $this->lado_max || $produto['length'] > $this->lado_max) {
      $this->log->write(sprintf($this->language->get('error_limites'), $produto['name']));

      return false;
    }

    return true;
  }



  // 'empacota' os produtos do carrinho em caixas com dimensões e peso dentro dos limites definidos pelos Correios
  // algoritmo desenvolvido por: Thalles Cardoso <thallescard@gmail.com>
  private function organizarEmCaixas($produtos)
  {

    $caixas = array();

    foreach ($produtos as $prod) {

      $prod_copy = $prod;


      // muda-se a quantidade do produto para incrementá-la em cada caixa
      $prod_copy['quantity'] = 1;

      // todas as dimensões da caixa serão em cm e kg
      $prod_copy['width'] = $this->getDimensaoEmCm($prod_copy['length_class_id'], $prod_copy['width']);
      $prod_copy['height'] = $this->getDimensaoEmCm($prod_copy['length_class_id'], $prod_copy['height']);
      $prod_copy['length'] = $this->getDimensaoEmCm($prod_copy['length_class_id'], $prod_copy['length']);

      // O peso do produto não é unitário como a dimensão. É multiplicado pela quantidade. Se quisermos o peso unitário, teremos que dividir pela quantidade.
      $prod_copy['weight'] = $this->getPesoEmKg($prod_copy['weight_class_id'], $prod_copy['weight']) / $prod['quantity'];

      $prod_copy['length_class_id'] = $this->config->get('select_comprimento');
      $prod_copy['weight_class_id'] = $this->config->get('select_peso');

      $cx_num = 0;


      if (($prod_copy['comp_progressiva'] > 0) || ($prod_copy['larg_progressiva'] > 0) || ($prod_copy['alt_progressiva'] > 0) && ($prod['quantity'] > 1)) {
        $prodquantidade = $prod['quantity'] - 1; //-1 por não conta o item principal
        $prod_copy['height'] = $prod_copy['height'] + (($prodquantidade) * $prod_copy['alt_progressiva']);
        $prod_copy['length'] = $prod_copy['length'] + (($prodquantidade) * $prod_copy['comp_progressiva']);
        $prod_copy['width'] = $prod_copy['width'] + (($prodquantidade) * $prod_copy['larg_progressiva']);
        $prod['quantity'] = 1; // caso o produto seja empilhado, seta para um pois ira adicionalo como se fosse uma unidade na caixa
      }

      if (!ftr_geral::isIsset($this->config->get('correios_tipo_calculo')) || ($this->config->get('correios_tipo_calculo') == Type_CorreiosTipoCalculo::Tp_CorreiosTpCalcDimensao)) {
        for ($i = 1; $i <= $prod['quantity']; $i++) {

          // valida as dimensões do produto com as dos Correios
          if ($this->validar($prod_copy)) {

            // cria-se a caixa caso ela não exista.
            isset($caixas[$cx_num]['weight']) ? true : $caixas[$cx_num]['weight'] = 0;
            isset($caixas[$cx_num]['height']) ? true : $caixas[$cx_num]['height'] = 0;
            isset($caixas[$cx_num]['width']) ? true : $caixas[$cx_num]['width'] = 0;
            isset($caixas[$cx_num]['length']) ? true : $caixas[$cx_num]['length'] = 0;

            $new_width = $caixas[$cx_num]['width'] + $prod_copy['width'];
            $new_height = $caixas[$cx_num]['height'] + $prod_copy['height'];
            $new_length = $caixas[$cx_num]['length'] + $prod_copy['length'];
            $new_weight = $caixas[$cx_num]['weight'] + $prod_copy['weight'];

            $cabe_do_lado = ($new_width <= $this->largura_max) && $this->somaDimDentroLimite($caixas, $prod_copy, $cx_num, 'lado');

            $cabe_no_fundo = ($new_length <= $this->comprimento_max) && $this->somaDimDentroLimite($caixas, $prod_copy, $cx_num, 'fundo');

            $cabe_em_cima = ($new_height <= $this->altura_max) && $this->somaDimDentroLimite($caixas, $prod_copy, $cx_num, 'cima');

            $peso_dentro_limite = ($new_weight <= $this->peso_max);

            // o produto cabe na caixa
            if (($cabe_do_lado || $cabe_no_fundo || $cabe_em_cima) && $peso_dentro_limite) {

              // já existe o mesmo produto na caixa, assim incrementa-se a sua quantidade

              if (isset($caixas[$cx_num]['produtos'][$prod_copy['key']])) {
                $caixas[$cx_num]['produtos'][$prod_copy['key']]['quantity']++;
              } // adiciona o novo produto
              else {
                $caixas[$cx_num]['produtos'][$prod_copy['key']] = $prod_copy;
              }

              // aumenta-se o peso da caixa
              $caixas[$cx_num]['weight'] += $prod_copy['weight'];

              // ajusta-se as dimensões da nova caixa
              if ($cabe_do_lado) {
                $caixas[$cx_num]['width'] += $prod_copy['width'];

                // a caixa vai ficar com a altura do maior produto que estiver nela
                $caixas[$cx_num]['height'] = max($caixas[$cx_num]['height'], $prod_copy['height']);

                // a caixa vai ficar com o comprimento do maior produto que estiver nela
                $caixas[$cx_num]['length'] = max($caixas[$cx_num]['length'], $prod_copy['length']);
              } else if ($cabe_no_fundo) {
                $caixas[$cx_num]['length'] += $prod_copy['length'];

                // a caixa vai ficar com a altura do maior produto que estiver nela
                $caixas[$cx_num]['height'] = max($caixas[$cx_num]['height'], $prod_copy['height']);

                // a caixa vai ficar com a largura do maior produto que estiver nela
                $caixas[$cx_num]['width'] = max($caixas[$cx_num]['width'], $prod_copy['width']);

              } else if ($cabe_em_cima) {
                $caixas[$cx_num]['height'] += $prod_copy['height'];

                //a caixa vai ficar com a altura do maior produto que estiver nela
                $caixas[$cx_num]['width'] = max($caixas[$cx_num]['width'], $prod_copy['width']);

                //a caixa vai ficar com a largura do maior produto que estiver nela
                $caixas[$cx_num]['length'] = max($caixas[$cx_num]['length'], $prod_copy['length']);
              }
            } // tenta adicionar o produto que não coube em uma nova caixa
            else {
              $cx_num++;
              $i--;
            }
          } // produto não tem as dimensões/peso válidos então abandona sem calcular o frete.
          else {
            $caixas = array();
            break 2;  // sai dos dois foreach
          }
        }
      } else {

        for ($i = 1; $i <= $prod['quantity']; $i++) {

          // valida as dimensões do produto com as dos Correios
          if ($this->validarPorVolume($prod_copy)) {

            // cria-se a caixa caso ela não exista.
            isset($caixas[$cx_num]['peso']) ? true : $caixas[$cx_num]['peso'] = 0;
            isset($caixas[$cx_num]['cubagem']) ? true : $caixas[$cx_num]['cubagem'] = 0;

            $peso = $caixas[$cx_num]['peso'] + $prod_copy['weight'];
            $cubagem = $caixas[$cx_num]['cubagem'] + ($prod_copy['width'] * $prod_copy['height'] * $prod_copy['length']);

            $peso_dentro_limite = ($peso <= $this->peso_max);
            $cubagem_dentro_limite = ($cubagem <= $this->cubagem_max);


            // o produto dentro do peso e dimensões estabelecidos pelos Correios
            if ($peso_dentro_limite && $cubagem_dentro_limite) {

              // já existe o mesmo produto na caixa, assim incrementa-se a sua quantidade
              if (isset($caixas[$cx_num]['produtos'][$prod_copy['key']])) {
                $caixas[$cx_num]['produtos'][$prod_copy['key']]['quantity']++;
              } // adiciona o novo produto
              else {
                $caixas[$cx_num]['produtos'][$prod_copy['key']] = $prod_copy;
              }

              $caixas[$cx_num]['peso'] = $peso;
              $caixas[$cx_num]['cubagem'] = $cubagem;
            } // tenta adicionar o produto que não coube em uma nova caixa
            else {
              $cx_num++;
              $i--;
            }
          } // produto não tem as dimensões/peso válidos então abandona sem calcular o frete.
          else {
            $caixas = array();
            break 2;  // sai dos dois foreach
          }
        }
      }

    }
    return $caixas;
  }

  // retorna o valor total dos prodtos na caixa
  private function getTotalCaixa($products)
  {
    $total = 0;

    foreach ($products as $product) {
      $total += $this->currency->format(($product['price'] * $product['quantity']), '', '', false);
    }
    return $total;
  }

  private function ajustarDimensoes($caixa)
  {

    // a altura não pode ser maior que o comprimento, assim inverte-se as dimensões
    $height = $caixa['height'];
    $width = $caixa['width'];
    $length = $caixa['length'];
    $weight = $caixa['weight'];

    // se dimensões menores que a permitida, ajusta para o padrão
    if ($height < $this->altura_min) {
      $height = $this->altura_min;
    }
    if ($width < $this->largura_min) {
      $width = $this->largura_min;
    }
    if ($length < $this->comprimento_min) {
      $length = $this->comprimento_min;
    }
    if ($weight < $this->peso_min) {
      $weight = $this->peso_min;
    }
    if ($height > $length) {
      $temp = $height;
      $height = $length;
      $length = $temp;
    }

    return array($weight, $height, $width, $length);
  }

  private function somaDimDentroLimite($caixas, $prod_copy, $cx_num, $orientacao)
  {

    if ($orientacao == 'lado') {
      $width = $caixas[$cx_num]['width'] + $prod_copy['width'];
      $height = max($caixas[$cx_num]['height'], $prod_copy['height']);
      $length = max($caixas[$cx_num]['length'], $prod_copy['length']);
    } elseif ($orientacao == 'fundo') {
      $length = $caixas[$cx_num]['length'] + $prod_copy['length'];
      $height = max($caixas[$cx_num]['height'], $prod_copy['height']);
      $width = max($caixas[$cx_num]['width'], $prod_copy['width']);
    } elseif ($orientacao == 'cima') {
      $height = $caixas[$cx_num]['height'] + $prod_copy['height'];
      $width = max($caixas[$cx_num]['width'], $prod_copy['width']);
      $length = max($caixas[$cx_num]['length'], $prod_copy['length']);
    } else {
      $width = $caixas[$cx_num]['width'];
      $height = $caixas[$cx_num]['height'];
      $length = $caixas[$cx_num]['length'];
    }
    $dentroLimite = ($width + $height + $length) <= $this->soma_dim_max;

    return $dentroLimite;
  }

  private function raizCubica($n)
  {
    return pow($n, 1 / 3);
  }

  /////Retrocompatibilidade
  private function verificaServicosExistentes()
  {
    $this->load->type('tipocomum');
    $this->load->helper('ftr_geral');
    $servicosNew = array();
    $row = 0;
    foreach (Type_CorreiosServico::getEnumList() as $key => $value) {
      $servicosNew[$row]['codigo'] = $key;
      $servicosNew[$row]['nome'] = $value;
      if (strpos(strtoupper($servicosNew[$row]['nome']), 'SEM')) {
        $servicosNew[$row]['contrato'] = Type_DsimDnao::Tp_Nao;
      } else {
        $servicosNew[$row]['contrato'] = Type_DsimDnao::Tp_Sim;
      }

      if ((strpos($servicosNew[$row]['nome'], '10')) || (strpos($servicosNew[$row]['nome'], '12')) || (strpos(strtoupper($servicosNew[$row]['nome']), 'HOJE'))) {
        $servicosNew[$row]['pesomax'] = 10;
      } else {
        $servicosNew[$row]['pesomax'] = 30;
      }

      $servicosNew[$row]['status'] = $this->validateOldCode($key);
      $row = $row + 1;
    }
    return $servicosNew;
  }

  private function validateOldCode($code)
  {
    $row = $this->db->query("Select * from " . DB_PREFIX . "setting where `key` = 'correios_" . $code . "' limit 1")->row;

    if ($row) {
      return 1;
    } else {
      return 0;
    }
  }
  /////Retrocompatibilidade


}
