<?php

/**
 * Class USTID
 *
 * echo $this->check("DE263136143","SE556459933901","Wind River AB","Kista","Finlandsgatan 52","16493","ja");
 * echo $this->check("DE263136143","HU12925481","TGIF KFT.","Egerszaloki","3394","Szechenyiu","ja");
 * echo $this->check("DE263136143","SE556459933901","jkjk","Kista","Finlandsgatan 52","16493","ja");
 */
class USTID
{
  /**
   * online formular @https://evatr.bff-online.de/eVatR/index_html#Einfach_Ergebnis
   *
   * @var string API base
   * @var string API endpoint
   */
  const BASE = 'https://evatr.bff-online.de';
  const ENDPOINT = 'evatrRPC';

  /**
   * just for downward compatibility,
   * the classes 'Adresse' and the 'erpAPI'
   * use that public value to access the
   * response data
   *
   * @var array
   */
  var $answer = array(
    'UstId_1' => '',
    'UstId_2' => '',
    'ErrorCode' => '',
    'Druck' => '',
    'Erg_PLZ' => '',
    'Ort' => '',
    'Datum' => '',
    'PLZ' => '',
    'Erg_Ort' => '',
    'Uhrzeit' => '',
    'Erg_Name' => '',
    'Gueltig_ab' => '',
    'Gueltig_bis' => '',
    'Strasse' => '',
    'Firmenname' => '',
    'Erg_Str' => '',
    'ErrorMSG' => '',
    'OK' => '',
  );

  /**
   * the required GET-parameter,
   * leave blank if field is not necessary,
   * but don't remove it from the request.
   * @see: https://evatr.bff-online.de/eVatR/xmlrpc/schnittstelle
   *
   * @var array
   */
  private $_data = array(
    'UstId_1' => '',    // Ihre deutsche USt-IdNr.
    'UstId_2' => '',    // Anzufragende ausländische USt-IdNr.
    'Firmenname' => '', // Name der anzufragenden Firma einschl. Rechtsform
    'Ort' => '',        // Ort der anzufragenden Firma
    'PLZ' => '',        // Postleitzahl der anzufragenden Firma
    'Strasse' => '',    // Strasse und Hausnummer der anzufragenden Firma,
    'Druck' => 'nein'   // ja = mit amtlicher Bestätigungsmitteilung
  );

  /**
   * @see https://evatr.bff-online.de/eVatR/xmlrpc/codes
   * @var array $_errorCodes
   */
  private $_errorCodes = array(
    /*
     * my own
     * Kein gültiger Fehlercode vom Finanzamt-Server erhalten.
     */
    100 => 'Es wurde keine Antwort vom Finanzamt-Server erhalten. Nur in der Zeit zwischen 05:00 Uhr und 23:00 Uhr sind abfragen möglich',
    101 => 'Es wurde eine Fehlerhafte Antwort vom Finanzamt-Server erhalten',
    /*
     * API's status/error codes
     */
    200 => 'Die angefragte USt-IdNr. ist gültig.',
    201 => 'Die angefragte USt-IdNr. ist ungültig.',
    202 => 'Die angefragte USt-IdNr. ist ungültig. Sie ist nicht in der Unternehmerdatei des betreffenden EU-Mitgliedstaates registriert. (Hinweis: Ihr Geschäftspartner kann seine gültige USt-IdNr. bei der für ihn zuständigen Finanzbehörde in Erfahrung bringen. Möglicherweise muss er einen Antrag stellen, damit seine USt-IdNr. in die Datenbank aufgenommen wird.)',
    203 => 'Die angefragte USt-IdNr. ist ungültig. Sie ist erst ab dem {{Gueltig_ab}} gültig.',
    204 => 'Die angefragte USt-IdNr. ist ungültig. Sie war im Zeitraum von {{Gueltig_ab}} bis {{Gueltig_bis}} gültig.',
    205 => 'Ihre Anfrage kann derzeit durch den angefragten EU-Mitgliedstaat oder aus anderen Gründen nicht beantwortet werden.',
    206 => 'Ihre deutsche USt-IdNr. ist ungültig. Eine Bestätigungsanfrage ist daher nicht möglich.',
    207 => 'Ihnen wurde die deutsche USt-IdNr. ausschliesslich zu Zwecken der Besteuerung des innergemeinschaftlichen Erwerbs erteilt. Sie sind somit nicht berechtigt, Bestätigungsanfragen zu stellen.',
    208 => 'Für die von Ihnen angefragte USt-IdNr. läuft gerade eine Anfrage von einem anderen Nutzer. Eine Bearbeitung ist daher nicht möglich. Bitte versuchen Sie es später noch einmal.',
    209 => 'Die angefragte USt-IdNr. ist ungültig. Sie entspricht nicht dem Aufbau der für diesen EU-Mitgliedstaat gilt.',
    210 => 'Die angefragte USt-IdNr. ist ungültig. Sie entspricht nicht den Prüfziffernregeln die für diesen EU-Mitgliedstaat gelten.',
    211 => 'Die angefragte USt-IdNr. ist ungültig. Sie enthält unzulässige Zeichen (wie z.B. Leerzeichen oder Punkt oder Bindestrich usw.).',
    212 => 'Die angefragte USt-IdNr. ist ungültig. Sie enthält ein unzulässiges Länderkennzeichen.',
    213 => 'Die Abfrage einer deutschen USt-IdNr. ist nicht möglich.',
    214 => 'Ihre deutsche USt-IdNr. ist fehlerhaft. Sie beginnt mit \'DE\' gefolgt von 9 Ziffern.',
    215 => 'Ihre Anfrage enthält nicht alle notwendigen Angaben für eine einfache Bestätigungsanfrage (Ihre deutsche USt-IdNr. und die ausl. USt-IdNr.).',
    216 => 'Ihre Anfrage enthält nicht alle notwendigen Angaben für eine qualifizierte Bestätigungsanfrage. (Ihre deutsche USt-IdNr., die ausl. USt-IdNr., Firmenname einschl. Rechtsform und Ort).',
    217 => 'Bei der Verarbeitung der Daten aus dem angefragten EU-Mitgliedstaat ist ein Fehler aufgetreten.',
    218 => 'Eine qualifizierte Bestätigung ist zur Zeit nicht möglich.',
    219 => 'Bei der Durchführung der qualifizierten Bestätigungsanfrage ist ein Fehler aufgetreten. Die angefragte USt-IdNr. ist gültig.',
    220 => 'Bei der Anforderung der amtlichen Bestätigungsmitteilung ist ein Fehler aufgetreten. Sie werden kein Schreiben erhalten.',
    221 => 'Die Anfragedaten enthalten nicht alle notwendigen Parameter oder einen ungültigen Datentyp.',
    222 => 'Die angefragte USt-IdNr. ist gültig.',
    999 => 'Eine Bearbeitung Ihrer Anfrage ist zurzeit nicht möglich. Bitte versuchen Sie es später noch einmal.'
  );

  /**
   * the last error message
   * @var string
   */
  private $_error = '';

  /**
   * @param string $ust1 your UST ID
   * @param string $ust2 requested UST ID
   * @param string $firmenname
   * @param string $ort
   * @param string $strasse
   * @param string $plz
   * @param string $druck
   * @param $onlinefehler
   * @return int
   *
   * return values:
   * 1: ok,
   * -1: invalid/wrong response,
   * -2: curl error
   */
  function check($ust1, $ust2, $firmenname, $ort, $strasse, $plz, $druck = "nein", &$onlinefehler)
  {
    $this->set('UstId_1', $ust1);
    $this->set('UstId_2', $ust2);
    $this->set('Firmenname', $firmenname);
    $this->set('Ort', $ort);
    $this->set('Strasse', $strasse);
    $this->set('PLZ', $plz);
    $this->set('Druck', $druck);

    /*
     * we could use $response = $this->send();
     * send() returns only false or the response.
     * To detect the reason of an error, don't use it.
     */

    $url = $this->buildQueryURL();
    $response = $this->curl($url);

    if ($response == false) {
      /**
       * curl failed,
       *
       */
      $onlinefehler = $this->_error;
      return -2;
    }

    $response = $this->convertXML2Array($response);
    $this->answer = $response;
    $this->_error = $response['ErrorMSG'];
    $onlinefehler = $this->_error;

    return $response['OK'] == true ? 1 : -1;
  }

  /**
   * from erpAPI (class.erpapi.php) to simplify that class
   * from first version of USTID, still unused?
   */
  function checkAndSendMailIfWrong()
  {
    // Job: implement
    // this method was empty
  }

  /**
   * just for downward compatibility,
   * the class 'Adresse' use that method
   * to get the error code message
   *
   * @param $code
   * @return string
   */
  function errormessages($code)
  {
    return $this->generateErrorMsg($code);
  }

  /**
   * return the error code message
   * alias for errormessages
   *
   * @param $code
   * @return string
   */
  public function getErrorMsg($code)
  {
    return $this->generateErrorMsg($code);
  }

  /**
   * create the full url and send the request
   *
   * @param array $args
   * @return mixed
   */
  public function send($args = array())
  {
    if (is_array($args) && !empty($args)) {
      foreach ($args as $key => $value) {
        $this->set($key, $value);
      }
    }
    if (!empty($this->_error)) {
      /**
       * on error, don't send the request,
       * let the user change the settings
       */
      return false;
    }

    $url = $this->buildQueryURL();
    $response = $this->curl($url);
    if ($response == false) {
      /**
       * curl failed,
       *
       */
      return false;
    }
    $response = $this->convertXML2Array($response);
    $this->answer = $response;
    $this->_error = $response['ErrorMSG'];
    return true;
  }

  /**
   * just a little helper to reset the request
   */
  public function reset()
  {
    foreach ($this->_data as $key => $_) {
      $this->_data[$key] = '';
    }
  }

  /**
   * set an GET parameter,
   * only the keys from $_data array are allowed,
   * only string values are allowed
   *
   * @param string $key
   * @param string $value
   * @return bool
   */
  public function set($key, $value)
  {
    if (!is_string($value)) {
      $this->_error = 'Invalid value type given';
      return false;
    }
    if (!is_string($key)) {
      $this->_error = 'Invalid key type given';
      return false;
    }
    $keys = array_keys($this->_data);
    if (!in_array($key, $keys)) {
      $this->_error = "Key [{$key}] is not allowed";
      return false;
    }
    $this->_data[$key] = $value;
    return true;
  }

  /**
   * build the query url and reset the config
   *
   * @return string
   */
  private function buildQueryURL()
  {
    $args = $this->formatData($this->_data);
    $args = http_build_query($args);

    $url = self::BASE . '/' . self::ENDPOINT . '?' . $args;
    $this->reset();

    return $url;
  }

  /**
   * before curl, reformat the data.
   * validate the 'Druck' value,
   * remove blanks from the UstId
   *
   * @param array $data
   * @return array
   */
  private function formatData($data)
  {
    if (!$data['Druck'] == 'ja') {
      /*
       * the 'Druck' value was not set to 'ja',
       * so empty that value
       */
      $data['Druck'] = '';
    }
    foreach (array('UstId_1', 'UstId_2') as $key) {
      /*
       * unzulässige Zeichen (wie z.B. Leerzeichen oder Punkt oder Bindestrich usw.)
       */
      $value = $data[$key];
      $value = str_replace(" ", "", $value);
      $value = str_replace(".", "", $value);
      $value = str_replace("-", "", $value);
      $data[$key] = $value;
    }

    return $data;
  }

  /**
   * curl the given url
   * @param $url
   * @return mixed
   */
  private function curl($url)
  {
    $ch = curl_init($url);

    curl_setopt_array($ch, array(
      CURLOPT_SSL_VERIFYPEER => false,
      CURLOPT_SSLVERSION => 6,
      CURLOPT_POST => false,
      CURLOPT_RETURNTRANSFER => true,
    ));

    $result = curl_exec($ch);
    if (!$result) {
      $this->_error = 'curl error: ' . curl_error($ch);
      return false;
    }

    $info = curl_getinfo($ch);

    curl_close($ch);

    $httpCode = $info['http_code'];
    if ($httpCode != 200 && $httpCode != 222) {
      $this->_error = 'Konnte Finanzamt-Server nicht erreichen (HTTP status code: ' . $httpCode . ')';
      return false;
    }

    return $result;
  }

  /**
   * convert the xml to array
   *
   * @param $xml
   * @return array
   */
  private function convertXML2Array($xml)
  {
    if (!$xml) {
      return array(
        'ErrorMSG' => $this->_errorCodes[100],
        'ErrorCode' => 100
      );
    }

    $xml = simplexml_load_string($xml);
    if ($xml === false) {
      return array(
        'ErrorMSG' => $this->_errorCodes[101],
        'ErrorCode' => 101
      );
    }

    $response = array();
    if (isset($xml->param)) {
      foreach ($xml->param as $param) {
        if (!isset($param->value->array->data->value)) {
          continue;
        }
        $key = (string)$param->value->array->data->value[0]->string;
        $value = (string)$param->value->array->data->value[1]->string;
        $response[$key] = is_numeric($value) ? ((int)$value) : $value;
      }
    }
    $response = $this->extendResponse($response);
    return $response;
  }

  /**
   * extend the 'ErrorMSG' and the 'OK' field
   *
   * @param array $response
   * @return array
   */
  private function extendResponse($response)
  {
    $response = (array)$response;

    /**
     * let's add the error msg from the error codes 'table'
     */
    $response['ErrorMSG'] = 'Kein gültiger Fehlercode vom Finanzamt-Server erhalten.';

    if (array_key_exists('ErrorCode', $response)) {
      $error = (int)$response['ErrorCode'];
      $response['ErrorMSG'] = $this->generateErrorMsg($error, $response);
      $response['OK'] = $response['ErrorCode'] == 200;
    } else {
      $response['ErrorMSG'] = 'Kein gültiger Fehlercode vom Finanzamt-Server erhalten.';
      $response['OK'] = false;
    }
    return $response;
  }

  /**
   * @param $code
   * @param $env
   * @return string
   */
  private function generateErrorMsg($code, $env = array())
  {
    if (!array_key_exists($code, $this->_errorCodes)) {
      return 'Kein gültiger Fehlercode vom Finanzamt-Server erhalten.';
    }

    $msg = $this->_errorCodes[$code];

    if (empty($env) || !is_array($env)) {
      $rep = array('{{' => '(Siehe Feld: ', '}}' => ')');
      return strtr($msg, $rep);
    }

    /**
     * extracts the placeholders in the msg string.
     * E.g.: Sie war im Zeitraum von {{Gueltig_ab}} bis {{Gueltig_bis}} gültig.
     * converts to:
     * array(
     *  0 => array(
     *    0 => '{{Gueltig_ab}}'
     *    1 => '{{Gueltig_bis}}'
     *  )
     *  1 => array(
     *    0 => Gueltig_ab
     *    1 => Gueltig_bis
     *  )
     * )
     */
    preg_match_all("/\{\{([^}]+)\}\}/", $msg, $keys);
    /*
     * filter empty results:
     */
    $keys = array_filter($keys);

    /*
     * for each key in the extract, replace the string
     */
    $rep = array();
    if (is_array($keys) && (!empty($keys)?count($keys):0) == 2) {
      foreach ($keys[1] as $key) {
        if (!array_key_exists($key, $env)) {
          continue;
        }
        $rep['{{' . $key . '}}'] = $env[$key];
      }
      $msg = strtr($msg, $rep);
    }
    return $msg;
  }
}