<?php
class ModelShippingCorreios extends Model {

	private $debug = false;



	// máximo valor declarado, em reais
	private $valor_declarado_min       = 25;
	private $valor_declarado_pac_max   = 3000;
	private $valor_declarado_sedex_max = 10000;

	// peso mínimo e máximo
	private $peso_min = 0.3; // em kg

	private $peso_pac_sedex_max       = 30; // em kg
	private $peso_esedex_max          = 15; // em kg
	private $peso_sedex10_12_hoje_max = 10; // em kg

	// comprimento, largura ou altura individualmente não podem ser maiores que 105cm
	private $lados_max       = 105; // em cm
	private $lados_pacgf_max = 150; // em cm

	// tamanho para habilitar cobrança por manuseio especial por formato ou dimensão
	private $lado_manuseio_especial = 70; // em cm

	private $comprimento_min = 16; // em cm
	private $largura_min     = 11; // em cm
	private $altura_min      = 2; // em cm

	// cubagem mínima da caixa levando em conta uma medida de 16cm x 11cm x 2cm (C x L x A)
	private $cubagem_min = 352; // $comprimento_min * $largura_min * $altura_min = 16 * 11 * 2

	// medida padrão de cada lado da caixa, ou seja, 200cm / 3 = 66,66... onde 200 é o máximo da soma dos lados
	// cubagem máxima da caixa, ou seja, 66,66 ^ 3
	private $cubagem_max = 296296.296296; // pow((200/3), 3);

	// medida padrão de cada lado da caixa para grandes formatos, ou seja, 300cm / 3 = 100 onde 300 é o máximo da soma dos lados para grandes formatos
	// cubagem máxima da caixa para grandes formatos, ou seja, 100 ^ 3
	private $cubagem_pacgf_max = 1000000; // pow((300/3), 3);

	// esse limite do Sedex Hoje não tem em nenhum documento dos Correios e por tentativa e erro descobri um número próximo de 52cm de cada lado
	private $cubagem_sedex_hoje_max = 140608;



	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(
		// SEM CONTRATO

		'PAC OLD'                        => '41106',
		'41106'                          => 'PAC OLD',
		'PAC'                            => '04510',
		'04510'                          => 'PAC',

		'PAC Pagamento na Entrega OLD'   => '41262',
		'41262'                          => 'PAC Pagamento na Entrega OLD',
		'PAC Pagamento na Entrega'       => '04707',
		'04707'                          => 'PAC Pagamento na Entrega',

		'SEDEX OLD'                      => '40010',
		'40010'                          => 'SEDEX OLD',
		'SEDEX'                          => '04014',
		'04014'                          => 'SEDEX',

		'SEDEX Pagamento na Entrega OLD' => '40045',
		'40045'                          => 'SEDEX Pagamento na Entrega OLD',
		'SEDEX Pagamento na Entrega'     => '04065',
		'04065'                          => 'SEDEX Pagamento na Entrega',

		'SEDEX 10'                       => '40215',
		'40215'                          => 'SEDEX 10',

		'SEDEX 12'                       => '40169',
		'40169'                          => 'SEDEX 12',

		'SEDEX Hoje'                     => '40290',
		'40290'                          => 'SEDEX Hoje',

		// COM CONTRATO

		'PAC - contrato 1 OLD'                  => '41068',
		'41068'                                 => 'PAC - contrato 1 OLD',
		'PAC - contrato 1'                      => '04669',
		'04669'                                 => 'PAC - contrato 1',

		'PAC - contrato 2'                      => '04596',
		'04596'                                 => 'PAC - contrato 2',

		'PAC Grandes Formatos - contrato OLD'   => '41300',
		'41300'                                 => 'PAC Grandes Formatos - contrato OLD',
		'PAC Grandes Formatos - contrato'       => '04693',
		'04693'                                 => 'PAC Grandes Formatos - contrato',

		'SEDEX - contrato 1 OLD'                => '40096',
		'40096'                                 => 'SEDEX - contrato 1 OLD',
		'SEDEX - contrato 1'                    => '04162',
		'04162'                                 => 'SEDEX - contrato 1',

		'SEDEX - contrato 2'                    => '04553',
		'04553'                                 => 'SEDEX - contrato 2',

		'SEDEX - contrato 3'                    => '40436',
		'40436'                                 => 'SEDEX - contrato 3',

		'SEDEX - contrato 4'                    => '40444',
		'40444'                                 => 'SEDEX - contrato 4',

		'SEDEX - contrato 5'                    => '40568',
		'40568'                                 => 'SEDEX - contrato 5',

		'SEDEX - contrato 6'                    => '40606',
		'40606'                                 => 'SEDEX - contrato 6',

		'SEDEX Pagamento na Entrega - contrato' => '40126',
		'40126'                                 => 'SEDEX Pagamento na Entrega - contrato',

		'e-SEDEX'                               => '81019',
		'81019'                                 => 'e-SEDEX',

		'e-SEDEX Prioritario'                   => '81027',
		'81027'                                 => 'e-SEDEX Prioritario',

		'e-SEDEX Express'                       => '81035',
		'81035'                                 => 'e-SEDEX Express',

		'e-SEDEX grupo 1'                       => '81868',
		'81868'                                 => 'e-SEDEX grupo 1',

		'e-SEDEX grupo 2'                       => '81833',
		'81833'                                 => 'e-SEDEX grupo 2',

		'e-SEDEX grupo 3'                       => '81850',
		'81850'                                 => 'e-SEDEX grupo 3'
	);

	private $id_idioma_portugues;
	private $id_medida_centimetros;
	private $id_peso_kilogramas;

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

	private function setCubagemMax($cubagem) {
		$this->cubagem_max = $cubagem;
	}

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

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

	private function setLadosMax($medida) {
		$this->lados_max = $medida;
	}

	private function setValorDeclaradoMax($valor_declarado_max) {
		$this->valor_declarado_max = $valor_declarado_max;
	}

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

		$this->load->language('shipping/correios');

		if ($this->config->get('correios_status')) {
			$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;
			}

			// requer idioma portugues
			$query = $this->db->query("SELECT language_id FROM " . DB_PREFIX . "language WHERE directory = 'portuguese-br'");
			if ($query->num_rows) {
				$this->id_idioma_portugues = $query->row['language_id'];
			} else {
				$status = FALSE;
			}

			// requer unidade de medida cm
			$query = $this->db->query("SELECT length_class_id FROM " . DB_PREFIX . "length_class_description WHERE unit = 'cm' AND language_id = '" . (int)$this->id_idioma_portugues . "'");
			if ($query->num_rows) {
				$this->id_medida_centimetros = $query->row['length_class_id'];
			} else {
				$status = FALSE;
			}

			// requer unidade de peso kg
			$query = $this->db->query("SELECT weight_class_id FROM " . DB_PREFIX . "weight_class_description WHERE unit = 'kg' AND language_id = '" . (int)$this->id_idioma_portugues . "'");
			if ($query->num_rows) {
				$this->id_peso_kilogramas = $query->row['weight_class_id'];
			} else {
				$status = FALSE;
			}

		} else {
			$status = FALSE;
		}

		$method_data = array();

		$produtos = $this->removeProdutosSemFrete($this->cart->getProducts());

		if ($status && !empty($produtos)) {

			// 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']);

			// classes de serviços agrupados conforme suas propriedades. Cada classe é uma chamada ao WebService dos Correios
			$this->nCdServico['pac'] =  array(
				'peso_max'            => $this->peso_pac_sedex_max,
				'lados_max'           => $this->lados_max,
				'cubagem_max'         => $this->cubagem_max,
				'valor_declarado_max' => $this->valor_declarado_pac_max
			);
			$this->nCdServico['pacgf'] = array(
				'peso_max'            => $this->peso_pac_sedex_max,
				'lados_max'           => $this->lados_pacgf_max,
				'cubagem_max'         => $this->cubagem_pacgf_max,
				'valor_declarado_max' => $this->valor_declarado_pac_max
			);
			$this->nCdServico['sedex'] =  array(
				'peso_max'            => $this->peso_pac_sedex_max,
				'lados_max'           => $this->lados_max,
				'cubagem_max'         => $this->cubagem_max,
				'valor_declarado_max' => $this->valor_declarado_sedex_max
			);
			$this->nCdServico['esedex'] = array(
				'peso_max'            => $this->peso_esedex_max,
				'lados_max'           => $this->lados_max,
				'cubagem_max'         => $this->cubagem_max,
				'valor_declarado_max' => $this->valor_declarado_sedex_max
			);
			$this->nCdServico['sedex10_12'] = array(
				'peso_max'            => $this->peso_sedex10_12_hoje_max,
				'lados_max'           => $this->lados_max,
				'cubagem_max'         => $this->cubagem_max,
				'valor_declarado_max' => $this->valor_declarado_sedex_max
			);
			$this->nCdServico['sedex_hoje'] = array(
				'peso_max'            => $this->peso_sedex10_12_hoje_max,
				'lados_max'           => $this->lados_max,
				'cubagem_max'         => $this->cubagem_sedex_hoje_max,
				'valor_declarado_max' => $this->valor_declarado_sedex_max
			);

			$servicos['pac']        = array();
			$servicos['pacgf']      = array();
			$servicos['sedex']      = array();
			$servicos['esedex']     = array();
			$servicos['sedex10_12'] = array();
			$servicos['sedex_hoje'] = array();

			// serviços sem contrato
			if ($this->config->get('correios_' . $this->correios['PAC']) || $this->config->get('correios_' . $this->correios['PAC OLD'])) {
				$servicos['pac'][] = $this->correios['PAC'];
			}
			if ($this->config->get('correios_' . $this->correios['PAC Pagamento na Entrega']) || $this->config->get('correios_' . $this->correios['PAC Pagamento na Entrega OLD'])) {
				$servicos['pac'][] = $this->correios['PAC Pagamento na Entrega'];
			}
			if ($this->config->get('correios_' . $this->correios['SEDEX']) || $this->config->get('correios_' . $this->correios['SEDEX OLD'])) {
				$servicos['sedex'][] = $this->correios['SEDEX'];
			}
			if ($this->config->get('correios_' . $this->correios['SEDEX Pagamento na Entrega']) || $this->config->get('correios_' . $this->correios['SEDEX Pagamento na Entrega OLD'])) {
				$servicos['sedex'][] = $this->correios['SEDEX Pagamento na Entrega'];
			}
			if ($this->config->get('correios_' . $this->correios['SEDEX 10'])) {
				$servicos['sedex10_12'][] = $this->correios['SEDEX 10'];
			}
			if ($this->config->get('correios_' . $this->correios['SEDEX 12'])) {
				$servicos['sedex10_12'][] = $this->correios['SEDEX 12'];
			}
			if ($this->config->get('correios_' . $this->correios['SEDEX Hoje'])) {
				$servicos['sedex_hoje'][] = $this->correios['SEDEX Hoje'];
			}

			// serviços com contrato
			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');

				if ($this->config->get('correios_' . $this->correios['PAC - contrato 1']) || $this->config->get('correios_' . $this->correios['PAC - contrato 1 OLD'])) {
					$servicos['pac'][] = $this->correios['PAC - contrato 1'];
				}
				if ($this->config->get('correios_' . $this->correios['PAC - contrato 2'])) {
					$servicos['pac'][] = $this->correios['PAC - contrato 2'];
				}
				if ($this->config->get('correios_' . $this->correios['PAC Grandes Formatos - contrato']) || $this->config->get('correios_' . $this->correios['PAC Grandes Formatos - contrato OLD'])) {
					$servicos['pacgf'][] = $this->correios['PAC Grandes Formatos - contrato'];
				}
				if ($this->config->get('correios_' . $this->correios['SEDEX - contrato 1']) || $this->config->get('correios_' . $this->correios['SEDEX - contrato 1 OLD'])) {
					$servicos['sedex'][] = $this->correios['SEDEX - contrato 1'];
				}
				if ($this->config->get('correios_' . $this->correios['SEDEX - contrato 2'])) {
					$servicos['sedex'][] = $this->correios['SEDEX - contrato 2'];
				}
				if ($this->config->get('correios_' . $this->correios['SEDEX - contrato 3'])) {
					$servicos['sedex'][] = $this->correios['SEDEX - contrato 3'];
				}
				if ($this->config->get('correios_' . $this->correios['SEDEX - contrato 4'])) {
					$servicos['sedex'][] = $this->correios['SEDEX - contrato 4'];
				}
				if ($this->config->get('correios_' . $this->correios['SEDEX - contrato 5'])) {
					$servicos['sedex'][] = $this->correios['SEDEX - contrato 5'];
				}
				if ($this->config->get('correios_' . $this->correios['SEDEX - contrato 6'])) {
					$servicos['sedex'][] = $this->correios['SEDEX - contrato 6'];
				}
				if ($this->config->get('correios_' . $this->correios['SEDEX Pagamento na Entrega - contrato'])) {
					$servicos['sedex'][] = $this->correios['SEDEX Pagamento na Entrega - contrato'];
				}
				if ($this->config->get('correios_' . $this->correios['e-SEDEX'])) {
					$servicos['esedex'][] = $this->correios['e-SEDEX'];
				}
				if ($this->config->get('correios_' . $this->correios['e-SEDEX Prioritario'])) {
					$servicos['esedex'][] = $this->correios['e-SEDEX Prioritario'];
				}
				if ($this->config->get('correios_' . $this->correios['e-SEDEX Express'])) {
					$servicos['esedex'][] = $this->correios['e-SEDEX Express'];
				}
				if ($this->config->get('correios_' . $this->correios['e-SEDEX grupo 1'])) {
					$servicos['esedex'][] = $this->correios['e-SEDEX grupo 1'];
				}
				if ($this->config->get('correios_' . $this->correios['e-SEDEX grupo 2'])) {
					$servicos['esedex'][] = $this->correios['e-SEDEX grupo 2'];
				}
				if ($this->config->get('correios_' . $this->correios['e-SEDEX grupo 3'])) {
					$servicos['esedex'][] =  $this->correios['e-SEDEX grupo 3'];
				}
			}

			if ($servicos['pac'] || $servicos['pacgf'] || $servicos['sedex'] || $servicos['esedex'] || $servicos['sedex10_12'] || $servicos['sedex_hoje']) {
				$this->initXMLCache();
			}

			foreach ($this->nCdServico as $classe => $info) {
				if (count($servicos[$classe]) == 0) { continue; }

				$this->setPesoMax($info['peso_max']);
				$this->setLadosMax($info['lados_max']);
				$this->setCubagemMax($info['cubagem_max']);
				$this->setValorDeclaradoMax($info['valor_declarado_max']);
				$this->setServicos($servicos[$classe]);

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

				// descomente a linha abaixo para visualizar em arquivos as caixas
				// file_put_contents(DIR_LOGS . 'correios_caixa_' . $classe . '.txt', print_r($caixas, true));

				foreach ($caixas as $caixa) {
					$this->setQuoteData($caixa);
				}

				$this->resetServicos();
			}

			// ajustes finais
			if ($this->quote_data) {
				$total_compra = $this->cart->getSubTotal();

				$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 serviços de pagamento na entrega
					if ($codigo != $this->correios['SEDEX Pagamento na Entrega'] || $codigo != $this->correios['SEDEX Pagamento na Entrega - contrato'] || $codigo != $this->correios['PAC Pagamento na Entrega']) {

						$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
						if (version_compare(VERSION, '2.2') < 0) {
							$this->quote_data[$codigo]['text'] = $this->currency->format($this->tax->calculate($new_cost, $this->config->get('correios_tax_class_id'), $this->config->get('config_tax')));
						} else {
							$this->quote_data[$codigo]['text'] = $this->currency->format($this->tax->calculate($new_cost, $this->config->get('correios_tax_class_id'), $this->config->get('config_tax')), $this->session->data['currency']);
						}
					} else {
						// zera o valor do frete do serviço de pagamento na entrega para evitar de ser adiconado ao valor do carrinho
						$this->quote_data[$codigo]['cost'] = 0;
					}

					// frete grátis
					if (trim($this->config->get('correios_total_' . $codigo)) != '' && $total_compra >= $this->config->get('correios_total_' . $codigo)) {
						// zera o valor
						$this->quote_data[$codigo]['cost'] = 0;
						// novo texto
						$this->quote_data[$codigo]['text'] = $this->language->get('text_free');
					}
				}

				$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
				);

			} elseif (!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)
				);
			}
		}

		return $method_data;
	}

	// obtém os dados dos fretes para os produtos da caixa
	private function setQuoteData($caixa) {
		// fazendo a chamada ao site dos Correios e obtendo os dados
		$servicos = $this->getServicos($caixa);

		if ($this->debug) {
			$this->log->write('RESPONSE: '.print_r($servicos, true));
		}

		foreach ($servicos as $servico) {

			// o site dos Correios retornou os dados sem erros.
			$valor_frete_sem_adicionais = $servico['Valor'] - $servico['ValorAvisoRecebimento'] - $servico['ValorMaoPropria'] - $servico['ValorValorDeclarado'];

			if ($valor_frete_sem_adicionais > 0) {
				$cost = $servico['Valor'];

				// subtrai do valor do frete as opções desabilitadas nas configurações do módulo - 'declarar valor' é obrigatório para Sedex e PAC Pagamento na Entrega
				if ($this->config->get('correios_declarar_valor') == 'n' && (
					$servico['Codigo'] != $this->correios['SEDEX Pagamento na Entrega'] ||
					$servico['Codigo'] != $this->correios['SEDEX Pagamento na Entrega - contrato'] ||
					$servico['Codigo'] != $this->correios['PAC Pagamento na Entrega']
				)) {
					$cost -= $servico['ValorValorDeclarado'];
				}

				if ($this->config->get('correios_aviso_recebimento') == 'n') {
					$cost -= $servico['ValorAvisoRecebimento'];
				}

				if ($this->config->get('correios_mao_propria') == 'n') {
					$cost -= $servico['ValorMaoPropria'];
				}

				// 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'];
				}

				$title = sprintf($this->language->get('text_' . $servico['Codigo']), $servico['PrazoEntrega']);

				if (version_compare(VERSION, '2.2') < 0) {
					$text = $this->currency->format($this->tax->calculate($cost, $this->config->get('correios_tax_class_id'), $this->config->get('config_tax')));
				} else {
					$text = $this->currency->format($this->tax->calculate($cost, $this->config->get('correios_tax_class_id'), $this->config->get('config_tax')), $this->session->data['currency']);
				}

				if ($servico['Erro'] == '010' || $servico['Erro'] == '011') {
					$title .= ' <span style="color:#d00;background:#fff;">('. $servico['MsgErro'] .')</span>';
				}

				$this->quote_data[$servico['Codigo']] = array(
					'code'         => 'correios.' . $servico['Codigo'],
					'title'        => $title,
					'cost'         => $cost,
					'tax_class_id' => $this->config->get('correios_tax_class_id'),
					'text'         => $text
				);

			// grava no log de erros do OpenCart a mensagem de erro retornado pelos Correios
			} else {
				$this->mensagem_erro[] = $this->correios[$servico['Codigo']] . ': ' . $servico['MsgErro'];
				$this->log->write($this->correios[$servico['Codigo']] . ': [erro ' . $servico['Erro'] . '] [origem ' . $this->cep_origem . '] [destino ' . $this->cep_destino . '] ' . $servico['MsgErro']);
			}
		}
	}

	// prepara a url de chamada ao site dos Correios
	private function setUrl($valor, $peso, $comprimento, $largura, $altura) {

		$servicos = implode(',', $this->servicos);

		// $url = "http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx/CalcPrecoPrazo?"; // url alternativa disponibilizada pelos Correios.
		$url = "http://ws.correios.com.br/calculador/CalcPrecoPrazo.aspx?";
		$url .= "nCdEmpresa={$this->contrato_codigo}";
		$url .= "&sDsSenha={$this->contrato_senha}";
		$url .= "&sCepOrigem={$this->cep_origem}";
		$url .= "&sCepDestino={$this->cep_destino}";
		$url .= "&nVlPeso={$peso}";
		$url .= "&nCdFormato=1";
		$url .= "&nVlComprimento={$comprimento}";
		$url .= "&nVlLargura={$largura}";
		$url .= "&nVlAltura={$altura}";
		$url .= "&sCdMaoPropria=s";
		$url .= "&nVlValorDeclarado={$valor}";
		$url .= "&sCdAvisoRecebimento=s";
		$url .= "&nCdServico={$servicos}";
		$url .= "&nVlDiametro=0";
		$url .= "&StrRetorno=xml";
		$url .= "&nIndicaCalculo=3";

		$this->url = $url;

		if ($this->debug) {
			// $this->log->write(str_replace('?', "\n?", str_replace('&', "\n&", $this->url)));
			$this->log->write($this->url);
			$this->log->write('REQUEST: '.print_r(array(
				'nCdEmpresa'        => $this->contrato_codigo,
				'sDsSenha'          => $this->contrato_senha,
				'sCepOrigem'        => $this->cep_origem,
				'sCepDestino'       => $this->cep_destino,
				'nVlPeso'           => $peso,
				'nVlComprimento'    => $comprimento,
				'nVlLargura'        => $largura,
				'nVlAltura'         => $altura,
				'nVlValorDeclarado' => $valor,
				'nCdServico'        => $servicos,
			), true));
		}
	}

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

		$result = $this->getXMLCache($url);
		if ($result && $result != '') {
			return $result;
		}

		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_HEADER, false);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_FAILONERROR, true);

		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
		curl_setopt($ch, CURLOPT_TIMEOUT, 30);

		$result = curl_exec($ch);

		if (!$result) {
			$this->log->write('Correios: ' . curl_error($ch));
			$this->log->write('Correios: ' . $this->language->get('error_conexao'));
			// $this->log->write(str_replace('?', "\n?", str_replace('&', "\n&", $url)));
			$this->log->write($url);

			sleep(5);
			$result = curl_exec($ch);

			if ($result) {
				$this->log->write('Correios: ' . $this->language->get('text_sucesso'));
			} else {
				$this->log->write('Correios: ' . curl_error($ch));
				$this->log->write('Correios: ' . $this->language->get('error_reconexao'));
				// $this->log->write(str_replace('?', "\n?", str_replace('&', "\n&", $url)));
				$this->log->write($url);
			}
		}

		curl_close($ch);

		if ($result) {
			$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);
			$this->saveXMLCache($url, $result);
		}

		return $result;
	}

	private function initXMLCache() {
		// cria tabela caso não exista
		$this->db->query("
			CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "correios_cache` (
				`hash` varchar(32) NOT NULL,
				`url` varchar(500) NOT NULL,
				`result` text NOT NULL,
				`data` datetime NOT NULL,
				PRIMARY KEY (`hash`),
				KEY `data` (`data`)
			) ENGINE=InnoDB DEFAULT CHARSET=utf8;
		");
		$this->db->query("ALTER TABLE `oc_correios_cache` CHANGE `url` `url` VARCHAR(500) NOT NULL;");

		// limpa consultas antigas
		$this->db->query("DELETE FROM `" . DB_PREFIX . "correios_cache` WHERE data <= '". date('Y-m-d H:i:s', mktime(0, 0, 0, date('m'), date('d') - 1, date('Y'))) ."'");

		if ($this->debug) {
			$this->log->write('Correios: CACHE INICIADO');
		}
	}

	private function getXMLCache($url) {
		$urlHash = md5($url);

		// busca cache
		$query = $this->db->query("SELECT `result` FROM `" . DB_PREFIX . "correios_cache` WHERE `hash` = '{$urlHash}'");
		if ($query->num_rows) {
			$result = utf8_decode($query->row['result']);

			if ($this->debug) {
				$this->log->write('Correios: UTILIZA CACHE');
				// $this->log->write('Correios: ' . $result);
			}
		} else {
			$result = FALSE;

			if ($this->debug) {
				$this->log->write('Correios: NÃO ENCONTRADO NO CACHE');
			}
		}

		return $result;
	}

	private function saveXMLCache($url, $result) {
		$urlHash = md5($url);
		$url = $this->db->escape($url);
		$result = $this->db->escape(utf8_encode($result));
		$data = date('Y-m-d H:i:s');

		// salva cache
		$this->db->query("INSERT INTO `" . DB_PREFIX . "correios_cache`(`hash`, `url`, `result`, `data`) VALUES ('{$urlHash}','{$url}','{$result}','{$data}')");

		if ($this->debug) {
			$this->log->write('Correios: CACHE ATUALIZADO!');
		}
	}

	// faz a chamada e lê os dados no arquivo XML retornado pelos Correios
	// public function getServicos($total_caixa, $peso, $cubagem) {
	public function getServicos($caixa) {
		// obtém o valor total da caixa
		$total_caixa = $this->getTotalCaixa($caixa['produtos']);
		$total_caixa = ($total_caixa < $this->valor_declarado_min) ? $this->valor_declarado_min : $total_caixa;
		$total_caixa = ($total_caixa > $this->valor_declarado_max) ? $this->valor_declarado_max : $total_caixa;

		$peso = $caixa['peso'];
		$peso = ($peso < $this->peso_min) ? $this->peso_min : $peso;

		$cubagem = $caixa['cubagem'];
		$cubagem = ($cubagem < $this->cubagem_min) ? $this->cubagem_min : $cubagem;

		// medida dos lados da caixa é a raiz cúbica da cubagem
		$medida_lados = $this->raizCubica($cubagem);

		// pega o maior lado da caixa e assume como comprimento
		// depois utiliza para calcular as outras medias da caixa (largura e altura)
		$comprimento = max($caixa['maior_comprimento'], $medida_lados);
		$comprimento = $this->round_up($comprimento, 1);
		$comprimento = ($comprimento < $this->comprimento_min) ? $this->comprimento_min : $comprimento;

		$largura = sqrt($cubagem / $comprimento);
		$largura = max($caixa['maior_largura'], $largura);
		$largura = $this->round_up($largura, 1);
		$largura = ($largura < $this->largura_min) ? $this->largura_min : $largura;

		$altura = ($cubagem / $comprimento) / $largura;
		$altura = max($caixa['maior_altura'], $altura);
		$altura = $this->round_up($altura, 1);
		$altura = ($altura < $this->altura_min) ? $this->altura_min : $altura;

		// total é arredondado pois algumas vezes o WebService dos Correios não aceita centavos
		// $valor = round($total_caixa);
		$valor = number_format($total_caixa, 2, ',', '');

		// troca o separador decimal de ponto para vírgula nos dados a serem enviados para os Correios
		$peso         = str_replace('.', ',', $peso);
		$medida_lados = str_replace('.', ',', $medida_lados);
		$comprimento  = str_replace('.', ',', $comprimento);
		$largura      = str_replace('.', ',', $largura);
		$altura       = str_replace('.', ',', $altura);

		// ajusta a url de chamada
		$this->setUrl($valor, $peso, $comprimento, $largura, $altura);

		// faz a chamada e retorna o xml com os dados
		$xml = $this->getXML($this->url);

		if ($this->debug) {
			$this->log->write(preg_replace('/\n/', '', $xml));
		}

		$dados = array();

		// lendo o xml
		if ($xml) {
			$dom = new DOMDocument('1.0', 'ISO-8859-1');
			$dom->loadXml($xml);

			$servicos = $dom->getElementsByTagName('cServico');

			if ($servicos) {
				// obtendo o prazo adicional a ser somado com o dos Correios
				$prazo_adicional = is_numeric($this->config->get('correios_prazo_adicional')) ? intval($this->config->get('correios_prazo_adicional')) : 0;

				foreach ($servicos as $servico) {
					$Codigo = $servico->getElementsByTagName('Codigo')->item(0)->nodeValue;
					$Valor = str_replace(',', '.', $servico->getElementsByTagName('Valor')->item(0)->nodeValue);

					// Sedex 10, 12 e Sedex Hoje não tem prazo adicional
					$prazo_extra = ($Codigo == $this->correios['SEDEX 10'] || $Codigo == $this->correios['SEDEX Hoje'] || $Codigo == $this->correios['SEDEX 12']) ? 0 : $prazo_adicional;

					$PrazoEntrega = intval($servico->getElementsByTagName('PrazoEntrega')->item(0)->nodeValue) + $prazo_extra;

					$ValorSemAdicionais    = isset($servico->getElementsByTagName('ValorSemAdicionais')->item(0)->nodeValue)    ? str_replace(',', '.', $servico->getElementsByTagName('ValorSemAdicionais')->item(0)->nodeValue)    : 0;
					$ValorMaoPropria       = isset($servico->getElementsByTagName('ValorMaoPropria')->item(0)->nodeValue)       ? str_replace(',', '.', $servico->getElementsByTagName('ValorMaoPropria')->item(0)->nodeValue)       : 0;
					$ValorAvisoRecebimento = isset($servico->getElementsByTagName('ValorAvisoRecebimento')->item(0)->nodeValue) ? str_replace(',', '.', $servico->getElementsByTagName('ValorAvisoRecebimento')->item(0)->nodeValue) : 0;
					$ValorValorDeclarado   = isset($servico->getElementsByTagName('ValorValorDeclarado')->item(0)->nodeValue)   ? str_replace(',', '.', $servico->getElementsByTagName('ValorValorDeclarado')->item(0)->nodeValue)   : 0;

					$EntregaDomiciliar = $servico->getElementsByTagName('EntregaDomiciliar')->item(0)->nodeValue;
					$EntregaSabado = $servico->getElementsByTagName('EntregaSabado')->item(0)->nodeValue;
					$obsFim = isset($servico->getElementsByTagName('obsFim')->item(0)->nodeValue) ? $servico->getElementsByTagName('obsFim')->item(0)->nodeValue : '';

					$dados[$Codigo] = array(
						"Codigo"                => $Codigo,
						"Modalidade"            => $this->correios[$Codigo],
						"Valor"                 => $Valor,
						"PrazoEntrega"          => $PrazoEntrega,
						"ValorSemAdicionais"    => $ValorSemAdicionais,
						"ValorMaoPropria"       => $ValorMaoPropria,
						"ValorAvisoRecebimento" => $ValorAvisoRecebimento,
						"ValorValorDeclarado"   => $ValorValorDeclarado,
						"EntregaDomiciliar"     => $EntregaDomiciliar,
						"EntregaSabado"         => $EntregaSabado,
						"Erro"                  => $servico->getElementsByTagName('Erro')->item(0)->nodeValue,
						"MsgErro"               => $servico->getElementsByTagName('MsgErro')->item(0)->nodeValue,
						"obsFim"                => $obsFim,
					);
				}
			}
		}

		return $dados;
	}

	// retorna a dimensão em centímetros
	private function getDimensaoEmCm($unidade, $dimensao) {
		$dimensao = $this->length->convert($dimensao, $unidade, $this->id_medida_centimetros);
		return ($dimensao <= 0) ? 1 : $dimensao;
	}

	// retorna o peso em quilogramas
	private function getPesoEmKg($unidade, $peso) {
		$peso = $this->weight->convert($peso, $unidade, $this->id_peso_kilogramas);
		return $peso;
	}

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

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

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

			return false;
		}

		return true;
	}

	// 'empacota' os produtos do carrinho em caixas conforme peso, cubagem e dimensões limites dos Correios
	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
			$prod_copy['length'] = $this->getDimensaoEmCm($prod_copy['length_class_id'], $prod_copy['length']);
			$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']);

			// 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('config_length_class_id');
			$prod_copy['weight_class_id'] = $this->config->get('config_weight_class_id');

			$cx_num = 0;

			for ($i = 1; $i <= $prod['quantity']; $i++) {
				// valida as dimensões do produto conforme os limites dos Correios
				if ($this->validarDimensoesProduto($prod_copy)) {
					// cria a caixa caso ela não exista
					if (!isset($caixas[$cx_num])) {
						$caixas[$cx_num] = array();
						$caixas[$cx_num]['peso'] = 0;
						$caixas[$cx_num]['cubagem'] = 0;
						$caixas[$cx_num]['produtos'] = array();
						$caixas[$cx_num]['maior_comprimento'] = 0;
						$caixas[$cx_num]['maior_largura'] = 0;
						$caixas[$cx_num]['maior_altura'] = 0;
					}

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

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

					// a caixa está dentro do peso e dimensões estabelecidos pelos Correios
					if ($peso_dentro_limite && $cubagem_dentro_limite) {
						$key = (version_compare(VERSION, '2.1') < 0) ? 'key' : 'cart_id';

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

						$caixas[$cx_num]['peso'] = $peso;
						$caixas[$cx_num]['cubagem'] = $cubagem;

						$lados = array($prod_copy['length'], $prod_copy['width'], $prod_copy['height']);
						rsort($lados); // organiza os lados em ordem decrescente

						// guarda o tamanho do maior lado dentre os produtos desta caixa
						$maior_comprimento = $lados[0];
						if ($maior_comprimento > $caixas[$cx_num]['maior_comprimento']) {
							$caixas[$cx_num]['maior_comprimento'] = $maior_comprimento;
						}
						$maior_largura = $lados[1];
						if ($maior_largura > $caixas[$cx_num]['maior_largura']) {
							$caixas[$cx_num]['maior_largura'] = $maior_largura;
						}
						$maior_altura = $lados[2];
						if ($maior_altura > $caixas[$cx_num]['maior_altura']) {
							$caixas[$cx_num]['maior_altura'] = $maior_altura;
						}

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

		return $caixas;
	}

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

		foreach ($products as $product) {
			if (version_compare(VERSION, '2.2') < 0) {
				$total += $this->currency->format($this->tax->calculate($product['total'], $product['tax_class_id'], $this->config->get('config_tax')), '', '', false);
			} else {
				$total += $this->currency->format($this->tax->calculate($product['total'], $product['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency'], '', false);
			}
		}

		return $total;
	}

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

	private function round_up($n, $p = 0) {
		$precision = pow(10, $p);
		return ceil($n * $precision) / $precision;
	}

	private function round_down($n, $p = 0) {
		$precision = pow(10, $p);
		return floor($n * $precision) / $precision;
	}

	private function removeProdutosSemFrete($products) {
		foreach ($products as $key => $product) {
			if (!$product['shipping']) {
				unset($products[$key]);
			}
		}

		return $products;
	}
}
