<?php

namespace HwapX\Core\Library;

use F3;
use Prefab;

class Validator extends \Valitron\Validator {
  protected static $instance = null;
  protected $f3       = null;
  protected $lang     = 'en-us';
  protected $lang_dir = '';

  public function __construct($data = array(), $fields = array(), $lang = null, $langDir = null) {
    $this->f3       = F3::instance();

    parent::__construct($data, $fields, $lang, $langDir);

    static::$_ruleMessages['uniqueRecord'] = 'Já existe';
    static::$_ruleMessages['cnh'] = 'inválida';
  }

  public function validateUniqueRecord($field, $value, $params, $fields) {
    $table  = is_array($params) ? $params[0] : $params;
    $filter = is_array($params) && count($params) > 1 ? $params[1] : "$field = @$field AND NOT id <=> @id";

    $filterfields = array();

    preg_match_all('/(?<=@)[\w\d]+/', $filter, $filterfields);
    $filter = preg_replace('/@[\w\d]+/', '?', $filter);

    $filterfields = count($filterfields) ? $filterfields[0] : array();

    foreach($filterfields as $filterfield)
      $filtervalues[] = isset($fields[$filterfield]) ? $fields[$filterfield] : $this->f3->get("PARAMS.$filterfield");

    return !(bool)$this->f3->get('db')->exec("SELECT 1 FROM $table WHERE $filter", $filtervalues);
  }

  public static function validateCNH($field, $value) {
      if ((strlen($input = preg_replace('/[^\d]/', '', $value)) != 11)
          || (str_repeat($input[1], 11) == $input))
          return false;
      
      $dsc = 0;
      for ($i = 0, $j = 9, $v = 0; $i < 9; ++$i, --$j) {
          $v += (int)$input[$i] * $j;
      }
      if (($vl1 = $v % 11) >= 10) {
          $vl1 = 0;
          $dsc = 2;
      }
      for ($i = 0, $j = 1, $v = 0; $i < 9; ++$i, ++$j) {
          $v += (int)$input[$i] * $j;
      }
      $vl2 = ($x = ($v % 11)) >= 10 ? 0 : $x - $dsc;
      
      return $vl1.$vl2 == substr($input, -2);
  }

  public static function validateCPF($cpf) {
    if(empty($cpf))
          return true;

      $cpf = preg_replace('/\D/', '', $cpf);
      $cpf = str_pad($cpf, 11, '0', STR_PAD_LEFT);

      if (strlen($cpf) != 11) {
        return false;
      } elseif (str_repeat($cpf[0], 11) === $cpf) {
        return false;
      } else {
        for ($t = 9; $t < 11; $t++) {
          for ($d = 0, $c = 0; $c < $t; $c++) {
            $d += $cpf{$c} * (($t + 1) - $c);
          }

          $d = ((10 * $d) % 11) % 10;
          if ($cpf{$c} != $d) {
            return false;
          }
        }

        return true;
      }
  }

  public static function validateCNPJ($cnpj) {
    $cnpj = preg_replace('/[^0-9]/', '', (string) $cnpj);
    // Valida tamanho
    if (strlen($cnpj) != 14)
      return false;
    else if (str_repeat($cnpj[0], 14) === $cnpj) 
      return false;
    // Valida primeiro dígito verificador
    for ($i = 0, $j = 5, $soma = 0; $i < 12; $i++)
    {
      $soma += $cnpj{$i} * $j;
      $j = ($j == 2) ? 9 : $j - 1;
    }
    $resto = $soma % 11;
    if ($cnpj{12} != ($resto < 2 ? 0 : 11 - $resto))
      return false;
    // Valida segundo dígito verificador
    for ($i = 0, $j = 6, $soma = 0; $i < 13; $i++)
    {
      $soma += $cnpj{$i} * $j;
      $j = ($j == 2) ? 9 : $j - 1;
    }
    $resto = $soma % 11;
    return $cnpj{13} == ($resto < 2 ? 0 : 11 - $resto);
  }

  public static function instance() {
    if(self::$instance == null) {
      $lang     = F3::instance()->get('app.language');
      $lang_dir = realpath(F3::instance()->get('valitron.lang_dir'));

      self::langDir($lang_dir);
      self::lang($lang);

      self::addRule('password', function($field, $value, $params) {
          $empty   = $value === '********';
          $lower   = preg_match('/[a-zA-Z]/', $value);
          $special = preg_match('/[^a-zA-Z0-9]/', $value);//preg_match('/[*\\/-+.,)\(\)&%$#@!?]/', $value);
          //$upper  = preg_match('/[A-Z]/', $value);
          $number = preg_match('/[0-9]/', $value);
          return $empty || ($lower && $special && $number);
        }, ' deve conter letras, números e caracteres especiais');
      self::addRule('cpf', function($field, $value, $params) {
          return \HwapX\Core\Library\Validator::validateCPF($value);
        }, 'é inválido');
      self::addRule('cnpj', function($field, $value, $params) {
          return \HwapX\Core\Library\Validator::validateCNPJ($value);
        }, 'é inválido');
      self::addRule('cnpj_cpf', function($field, $value, $params) {
          return \HwapX\Core\Library\Validator::validateCNPJ($value) || \HwapX\Core\Library\Validator::validateCPF($value);
        }, 'é inválido');
      self::addRule('arrayMin', function($field, $value, $params) {
            return count($value) >= $params[0];
        }, 'É necessário selecionar ao menos um item');
      self::addRule('file', function($field, $value, $params) {
            $required = isset($params[0]) ? $params[0] : false;
            return isset($value['error']) && (!$value['error'] || ($value['error'] == UPLOAD_ERR_NO_FILE && !$required));
        }, 'Ocorreu um erro durante o envio do arquivo');

        $refClass = new \ReflectionClass('HwapX\Core\Library\Validator');
        self::$instance = $refClass->newInstanceArgs(func_get_args());
    }

    return self::$instance;
  }

  protected static function setupGroup($validator, $fields, $arr_group = null) {
    foreach ($fields as $field => $v) {
      if(isset($v['rules']) && count($v['rules'])) {
        //if($arr_group != null)
        //  $field = $arr_group.'.*.'.$field;

        foreach ($v['rules'] as $rule => $params) {
          if($arr_group) {
            foreach ((array)$validator->_fields[$arr_group] as $key => $value) {
              if(is_int($rule))
                $validator->rule($params, $arr_group.'.'.$key.'.'.$field);
              else
                call_user_func_array(
                  array($validator, 'rule'), 
                  array_merge(
                    array($rule, $arr_group.'.'.$key.'.'.$field),
                    (array)$params
                  )
                );
                //$validator->rule($rule, $arr_group.'.'.$key.'.'.$field, $params);

              $validator->label($v['label']);
            }
          } else {
            if(is_int($rule))
              $validator->rule($params, $field);
            else
              call_user_func_array(
                array($validator, 'rule'), 
                array_merge(
                  array($rule, $field),
                  (array)$params
                )
              );
              //$validator->rule($rule, $field, $params);
          }
        }

        $validator->label($v['label']);
      };

      if(isset($v['options']) && $v['type'] == 'dropdown' && (!isset($v['rules']) || !in_array('in', $v['rules']))) {
            if($arr_group) {
                foreach ((array)$validator->_fields[$arr_group] as $key => $value) 
                    $validator->rule('in', $arr_group.'.'.$key.'.'.$field, array_keys($v['options']));
            } else {
                $validator->rule('in', $field, array_keys($v['options']));
            }
      }
    }
  }

  public function from($form, $data = null) {
    $data = is_null($data) ? F3::get('POST') : $data;

    $this->data($data);


    $e = end($form);

    if(!isset($e['legend']))
      self::setupGroup($this, $form);
    else
      foreach ($form as $groupname => $group)
        if(isset($group['fields']))
          self::setupGroup($this, $group['fields']);
        elseif(isset($group['table']))
          self::setupGroup($this, $group['table'], $groupname);

    //print_r($this->_validations);die();
    //    $this->validate();
    //print_r($this->errors());die();
    return $this;
  }

  public static function createFrom($form, $data = null) {
    $validator = new self();
    $validator->from($form, $data);

    return $validator;
  }

  public function fromRules($fields, $data = null) {
    $data = is_null($data) ? F3::get('POST') : $data;

    $this->data($data);

    foreach ($fields as $field => $rules)
      foreach($rules as $rule => $params)
        if(is_int($rule))
          $this->rule($params, $field);
        else
          $this->rule($rule, $field, $params);
          
    return $this;
  }

  public static function createFromRules($fields, $data = null) {
    $validator = new self();
    $validator->fromRules($fields, $data);

    return $validator;
  }

  public function validate($data = null) {
    if($data != null)
      $this->_fields = $data;

    return parent::validate();
  }

  public function success() {
    return count($this->errors()) > 0;
  }

  public function data($data = null) {
    $result = $this->_fields;
    if(!is_null($data))
      $this->_fields = $data;
    return $result;
  }

  public function clearRules() {
    $this->_validations = array();
    return $this;
  }

  /**
   * Get array of error messages
   *
   * @param  null|string $field
   * @return array|bool
   */
  public function errors($field = null)
  {
      if ($field !== null) {
        $errors = isset($this->_errors[$field]) ? $this->_errors[$field] : false;
      } else {
        $errors = $this->_errors;
      }

      if($errors) {
        $errors2 = array();
        foreach ($errors as $key => $value) {
          $node = &$errors2;
          foreach (explode('.', $key) as $idx)
            $node = &$node[$idx];

          $node = $value;
        }

        return $errors2;
      } else {
        return $errors;
      }
  }

  protected function validateDate($field, $value)
  {
    $isDate = false;
    if ($value instanceof \DateTime) {
        $isDate = true;
    } else {
        $isDate = \DateTime::createFromFormat($this->f3->get('app.date.format'), $value)
               || \DateTime::createFromFormat($this->f3->get('app.datetime.format'), $value)
               || \DateTime::createFromFormat($this->f3->get('app.time.format'), $value)
               || \DateTime::createFromFormat($this->f3->get('app.date.format4'), $value)
               || \DateTime::createFromFormat($this->f3->get('app.datetime.format4'), $value)
               || \DateTime::createFromFormat($this->f3->get('app.time.format4'), $value);
    }

    return $isDate;
  }

  protected function validateDatetime($field, $value)
  {
    $isDate = false;
    if ($value instanceof \DateTime) {
        $isDate = true;
    } else {
        $isDate = \DateTime::createFromFormat($this->f3->get('app.datetime.format'), $value)
               || \DateTime::createFromFormat($this->f3->get('app.datetime.format4'), $value);
    }

    return $isDate;
  }

  public function errorMessage()
  {
    $msg = '';
    foreach ($this->errors() as $error) {
      $msg .= implode(PHP_EOL, $error).PHP_EOL;
    }

    return trim($msg);
  }

  public function getRuleCallback($rulename)
  {
    $rules = $this->getRules();

    if(isset($rules[$rulename]))
      return $rules[$rulename];
    else
      return array($this, 'validate'.ucfirst($rulename));
  }
}