<?php
/**
 * @copyright (c) Kufer Software GmbH, Loisachstrasse 4, D-84453 Mühldorf
 */

use Kufer\Web\Classes\Identifier\Identifier;
use Kufer\Web\Classes\Identifier\IIdentifiable;


class Datamodul implements IIdentifiable
{
  
  public $id;

  public function __construct()
  {
    $this->init();
  }

  protected function init(): void
  {
  }

  public function getVarCount()
  {
    return count(get_object_vars($this));
  }

  public function getAttributeNames()
  {
    return array_keys(get_object_vars($this));
  }

  
  public function VarExist($name)
  {
    if (property_exists($this, $name)) {
      return true;
    }

    $ary = get_object_vars($this);
    foreach ($ary as $key => $val) {
      if (StringOperations::strtoupper($key) == StringOperations::strtoupper($name)) {
        return true;
      }
    }

    return false;
  }

  public function getVarValue($name)
  {
    
    $ary = get_object_vars($this);
    $lowerKeyAry = array_change_key_case($ary);
    $lowerName = StringOperations::strtolower($name);
    if (isset($lowerKeyAry[$lowerName])) {
      return $lowerKeyAry[$lowerName];
    } else {
      return '';
    }
    
  }

  
  public function getDmzVarName($pName)
  {
    $dmzVarnameMapping = $this->getDMZVarnameMappingForSendingData();
    if (isset($dmzVarnameMapping[$pName])) {
      return $dmzVarnameMapping[$pName];
    } else {
      return $pName;
    }
  }

  
  public function getDMZVarnameMappingForSendingData()
  {
    return array();
  }

  
  public function getDmzVarnameMappingForReceivingData(): array
  {
    return array();
  }

  
  public function getVarValueEx($name, $datamodulname)
  {
    $datamodul = $this->getDatamodulByName($datamodulname);

    return $datamodul->getVarValue($name);
  }

  public function getDatamodulByName($datamodulname)
  {
    $lcdatamodulname = \StringOperations::strtolower($datamodulname);
    if ($this->getDatamodulName() != $lcdatamodulname) {
      $ary = get_object_vars($this);
      for ($i = 0;$i < count($ary);$i++) {
        $tmpvarname = $this->getVarName($i);
        $obj = $ary[$tmpvarname];
        if (is_object($obj)) {
          if ($obj instanceof \Datamodul) {
            if ($obj->getDatamodulName() === $lcdatamodulname) {
              return $obj;
            }
          } elseif (StringOperations::strtolower(get_class($obj)) == $lcdatamodulname) {
            return $obj;
          }
        }
      }
    }

    return $this;
  }

  
  public function getVarName($id)
  {
    $ary = array_keys(get_object_vars($this));

    return $ary[$id];
  }

  
  public function setValue2Var($var, $val)
  {
    switch ($var) {
      case 'id': $this->id = $val;
        break;
      default:
        if (array_key_exists($var, get_object_vars($this))) {
          $this->{$var} = $val;
        }
    }
  }

  
  public function getDatamodulName(): string
  {
    return \StringOperations::strtolower(static::class);
  }

  public function setValue2VarEx($var, $val, $datamodulname)
  {
    $lcdatamodulname = \StringOperations::strtolower($datamodulname);
    if ($this->getDatamodulName() !== $lcdatamodulname) {
      $ary = get_object_vars($this);
      for ($i = 0;$i < count($ary);$i++) {
        $tmpvarname = $this->getVarName($i);
        $obj = $ary[$tmpvarname];
        if (is_object($obj)) {
          if ($obj instanceof \Datamodul) {
            if ($obj->getDatamodulName() === $lcdatamodulname) {
              $obj->setValue2Var($var, $val);
              $this->setValue2Var($tmpvarname, $obj);

              return;
            }
          } elseif (StringOperations::strtolower(get_class($obj)) === $lcdatamodulname) {
            $obj->setValue2Var($var, $val);
            $this->setValue2Var($tmpvarname, $obj);

            return;
          }
        }
      }
    } else {
      $this->setValue2Var($var, $val);
    }
  }

  
  public function isEmpty()
  {
    foreach (get_object_vars($this) as $value) {
      if ($value instanceof \Kufer\Web\Classes\Addfunctions\Guid) {
        if (!$value->isEmpty()) {
          return false;
        }
      } elseif (is_object($value) && is_a($value, 'Datamodul')) {
        if (!$value->isEmpty()) {
          return false;
        }
      } else {
        if (!$this->isValueEmptyOrDefault($value)) {
          if ($value != -1) {
            return false;
          }
        }
      }
    }

    return true;
  }

  protected function isValueEmptyOrDefault($value): bool
  {
    if (empty($value)) {
      return true;
    }
    if (\DateTimeOperations::isDateTimeDefault($value)) {
      return true;
    }
    if (\DateTimeOperations::isTimeDefault($value)) {
      return true;
    }
    if (is_array($value) && count($value) === 0) {
      return true;
    }

    return false;
  }

  
  public function copy()
  {
    return clone $this;
  }

  
  public function getTyp($pFieldName)
  {
    switch ($pFieldName) {
      default:
        return \DatamodulVarType::TYPE_STRING;
    }
  }

  
  public function getDMZValue($fieldname)
  {
    
    if ($fieldname == 'nr') {
      return $this->id;
    }

    $datenTyp = $this->getTyp($fieldname);
    $value = $this->getVarValue($fieldname);

    if (is_object($value)) {
      if (!method_exists($value, '__toString')) {
        return '';
      } else {
        $value = $value->__toString();
      }
    }

    switch ($datenTyp) {
      case \DatamodulVarType::TYPE_STRING:
        if (is_string($value) === false) { 
          return $value;
        }

        return stripslashes($value);

      case \DatamodulVarType::TYPE_INT:
        return StringOperations::getfilledStr(
          $value,
          '0' 
        );

      case \DatamodulVarType::TYPE_FLOAT:
        return MathOperations::stringToFloat(
          $value
        );

      case \DatamodulVarType::TYPE_BOOL_JN:
        if ($value == true) {
          return 'J';
        } else {
          return 'N';
        }
        
      case \DatamodulVarType::TYPE_BOOL_WF:
        if ($value == true) {
          return 'W';
        } else {
          return 'F';
        }
        
      case \DatamodulVarType::TYPE_BOOL_VN:
        if ($value == true) {
          return 'V';
        } else {
          return 'N';
        }
        
      case \DatamodulVarType::TYPE_BOOL_HALF_DEFAULT_TRUE:
        if ($value == true) {
          return '1';
        } else {
          return '0.5';
        }
        
      case \DatamodulVarType::TYPE_WF_EMPTY: 
                            
                            
                            
        if ($value === true) {
          return 'W';
        } elseif ($value === false) {
          return 'F';
        } else {
          return '';
        }
        
      case \DatamodulVarType::TYPE_DATE:
        return DateTimeOperations::getValidDateString($value, true);
      case \DatamodulVarType::TYPE_DATETIME:
        return DateTimeOperations::getValidDateTimeString($value, true);
      case \DatamodulVarType::TYPE_BINARY_NO_BASE64:
        return base64_encode($value);
      case \DatamodulVarType::TYPE_BINARY:
      case \DatamodulVarType::TYPE_TIME:
      default:
        return $value;
    }
  }

  public function getVarValues($pArray)
  {
    $result = array();
    foreach ($pArray as $item) {
      $result[$item] = $this->getVarValue($item);
    }

    return $result;
  }

  
  public static function toDatamodul($pClassName, $pObject, $pSubClassNames = array(), $pMapping = array(), $valuefunction = null)
  {
    $result = new $pClassName();
    $objectvarsAlsValues = array_keys(get_object_vars($result));
    $objectvarsAlsKeysUndValues = array_combine($objectvarsAlsValues, $objectvarsAlsValues);
    $ary = array_merge($objectvarsAlsKeysUndValues, $pMapping);
    if (isset($pObject->id)) {
      $result->id = $pObject->id;
    }
    foreach ($ary as $varVorher => $varNachher) {
      $objectvar = self::getObjectVar($pObject, $varVorher);
      if (isset($pObject->{$objectvar})) {
        if (is_array($pObject->{$objectvar})) {
          
          
          $result->{$varNachher} = array();
          foreach ($pObject->{$objectvar} as $item) {
            if ($item instanceof stdClass) {
              if (!isset($pSubClassNames[$varVorher])) {
                throw new Exception('Datamodul::toDatamodul: KlassenName für ' . $varVorher . '/' . $varNachher . ' nicht in $pSubClassNames angegeben');
              }
              array_push($result->{$varNachher}, Datamodul::toDatamodul(
                $pSubClassNames[$varVorher],
                $item,
                $pSubClassNames
              ));
            } else {
              $result->{$varNachher}[] = $item;
            }
          }
        } elseif (is_object($pObject->{$objectvar}) && $pObject->{$objectvar} instanceof stdClass) {
          if (!isset($pSubClassNames[$varVorher])) {
            throw new Exception('Datamodul::toDatamodul: KlassenName für ' . $varVorher . '/' . $varNachher . ' nicht in $pSubClassNames angegeben');
          }
          $result->{$varNachher} = Datamodul::toDatamodul($pSubClassNames[$varVorher], $pObject->{$objectvar}, $pSubClassNames);
        } else {
          if (is_callable($valuefunction)) {
            $value = call_user_func($valuefunction, $pObject->{$objectvar});
          } else {
            $value = $pObject->{$objectvar};
          }
          $result->setValue2Var($varNachher, $value);
        }
      }
    }

    return $result;
  }

  
  private static function getObjectvar($pObject, $var)
  {
    if (isset($pObject->{$var})) {
      $objectvar = $var;
    } elseif (isset($pObject->{strtolower($var)})) {
      $objectvar = strtolower($var);
    } elseif (isset($pObject->{ucfirst($var)})) {
      $objectvar = ucfirst($var);
    } else {
      $objectvar = '';
    }

    return $objectvar;
  }

  
  public static function cast($destination, $sourceObject)
  {
    if (is_string($destination)) {
      $destination = new $destination();
    }
    $sourceReflection = new ReflectionObject($sourceObject);
    $destinationReflection = new ReflectionObject($destination);
    $sourceProperties = $sourceReflection->getProperties();
    foreach ($sourceProperties as $sourceProperty) {
      $sourceProperty->setAccessible(true);
      $name = $sourceProperty->getName();
      $value = $sourceProperty->getValue($sourceObject);
      $value = \StringOperations::utf8_decode($value);
      if ($destinationReflection->hasProperty($name)) {
        $propDest = $destinationReflection->getProperty($name);
        $propDest->setAccessible(true);
        $propDest->setValue($destination, $value);
      } else {
        $destination->{$name} = $value;
      }
    }

    return $destination;
  }

  
  public static function mergeWith($oldDataModul, $newDataModul)
  {
    $mergedDataModul = $oldDataModul->copy();
    $varCount = count(get_object_vars($newDataModul));
    $varNameAry = array_keys(get_object_vars($newDataModul));
    for ($i = 0; $i < $varCount; $i++) {
      $varname = $varNameAry[$i];
      
      $value = $newDataModul->{$varname};
      if (!is_null($value)) {
        if (is_object($value)) {
          $mergedDataModul->{$varname} = Datamodul::mergeWith($oldDataModul->{$varname}, $value);
        } else {
          $mergedDataModul->{$varname} = $value;
        }
      }
    }

    return $mergedDataModul;
  }

  
  public function ToXml($pExceptions = array(), $pForDMZ = false, $pForceVars = array(), $pNodeTag = false)
  {
    if (!$pNodeTag) {
      $nodeTag = get_class($this);
    } else {
      $nodeTag = $pNodeTag;
    }
    $pDatamodul = $this;

    return XMLNode::getXMLNode($pDatamodul, $nodeTag, $pExceptions, $pForDMZ, $pForceVars);
  }

  
  public function getDiffFields($pDatamodul, $pDmz = false)
  {
    $varCount = $this->getVarCount();
    $result = array();
    for ($i = 0; $i < $varCount; $i++) {
      $varname = $this->getVarname($i);

      if ($pDmz) {
        $value1 = $this->getDMZValue($varname);
        $value2 = $pDatamodul->getDMZValue($varname);
      } else {
        $value1 = $this->getVarValue($varname);
        $value2 = $pDatamodul->getVarValue($varname);
      }

      if ($value1 != $value2) {
        $result[] = $varname;
      }
    }

    return $result;
  }

  
  public function __clone()
  {
    $this->init();

    $referenzVariablen = $this->_referenzVariablen();
    foreach ($referenzVariablen as $varname) {
      if (isset($this->{$varname}) && $this->{$varname} === (object)$this->{$varname}) {
        $this->{$varname} = clone $this->{$varname};
      }
    }
  }

  
  protected function _referenzVariablen()
  {
    $objs = get_object_vars($this);
    $objs = \array_filter($objs, function($obj) {
      return !($obj instanceof \Kufer\Utils\Lazy);
    });

    return array_keys(
      $objs
    );
  }

  
  public function getDbTyp()
  {
    return '';
  }

  public function checkValidierung($pValidierung = null)
  {
    $result = array();
    if ($pValidierung == null) {
      $pValidierung = ValidierungWB::getInstance();
    }

    for ($i = 0; $i < $this->getVarCount(); $i++) {
      $name = $this->getVarName($i);
      $value = $this->getVarValue($name);
      if (is_object($value)) {
        if ($value instanceof Datamodul) {
          $value = $this->getVarValueEx($name, get_class($value));
        } else {
          $value = '';
        }
      }

      if ($value !== '') {
        $zusatzInfo = array();
        if ($name === 'konto') {
          $zusatzInfo['blz'] = $this->getVarValue('blz');
        } elseif ($name === 'iban') {
          $zusatzInfo['bic'] = $this->getVarValue('bic');
        } elseif ($name === 'nurstrasse') {
          $zusatzInfo['plz'] = $this->getVarValue('nurplz');
        }

        $tmp = $pValidierung->isValidVar($name, $value, $zusatzInfo);

        if ($tmp !== true) {
          $result[] = $tmp;
        }
      }
    }

    return $result;
  }

  public function __wakeup(): void
  {
    $this->init();
  }

  public function __sleep(): array
  {
    $result = \get_object_vars($this);
    $result = \array_filter($result, function($obj) {
      return !($obj instanceof \Kufer\Utils\Lazy);
    });

    return \array_keys($result);
  }

  
  public function isValueDifferent(string $varname, $value)
  {
    $typ = $this->getTyp($varname);
    switch ($typ) {
      case \DatamodulVarType::TYPE_DATETIME:
      case \DatamodulVarType::TYPE_DATE:
        $thisVal = $this->getVarValue($varname);

        return !DateTimeOperations::equals($thisVal, $value);
      default:
        return $value != $this->getVarValue($varname);
    }
  }

  
  public function updateByDmzValues(array $dmzVarnameMapping, array $dmzValues): bool
  {
    $valueChanged = false;
    foreach ($dmzVarnameMapping as $varnameDmz => $varnameWeb) {
      if (isset($dmzValues[$varnameDmz])) {
        $valDmz = \StringOperations::utf8_decode($dmzValues[$varnameDmz]);
        if ($this->isValueDifferent($varnameWeb, $valDmz)) {
          $value = self::getValueDmz($valDmz, $this->getTyp($varnameWeb));
          $this->setValue2Var($varnameWeb, $value);
          $valueChanged = true;
        }
      }
    }

    return $valueChanged;
  }

  public function getFotoTyp(): ?string
  {
    return null;
  }

  protected function makeIdentifier(string $idAttr, string $guidAttr)
  {
    if (!isset($this->{$guidAttr})) {
      return Identifier::fromId($this->{$idAttr});
    }

    return new Identifier($this->{$idAttr}, $this->{$guidAttr});
  }

  public function getIdentifier(): Identifier
  {
    return Identifier::fromId($this->id);
  }

  
  public static function fromDmzValues(\stdClass $dmzDatamodul)
  {
    $datamodul = new static();
    
    
    $mapping = array_change_key_case($datamodul->getDmzVarnameMappingForReceivingData(), CASE_LOWER);
    $vars = get_object_vars($dmzDatamodul);
    foreach ($vars as $key => $value) {
      $varname = StringOperations::strtolower($key);
      if (isset($mapping[$varname])) {
        $varname = $mapping[$varname];
      }
      $varVal = self::getValueDmz($value, $datamodul->getTyp($varname));
      $datamodul->setValue2Var($varname, $varVal);
    }

    return $datamodul;
  }

  
  private static function getValueDmz($value, $vartyp)
  {
    switch ($vartyp) {
      case \DatamodulVarType::TYPE_BOOL:
        return $value == '1';
      case \DatamodulVarType::TYPE_BOOL_JN:
        return $value == 'J';
      case \DatamodulVarType::TYPE_BOOL_WF:
        return $value == 'W';
      case \DatamodulVarType::TYPE_BOOL_VN:
        return $value == 'V';
      case \DatamodulVarTYPe::TYPE_BOOL_HALF_DEFAULT_TRUE:
        return $value !== '0.5';
      case \DatamodulVarType::TYPE_WF_EMPTY:
        if ($value === 'W') {
          return true;
        } elseif ($value === 'F') {
          return false;
        } else {
          return '';
        }
        
      case \DatamodulVarType::TYPE_TIME:
        $dt = \DateTimeObj::createFromString($value);

        return $dt->getTime(false, true);
      case \DatamodulVarType::TYPE_DATETIME:
        $dt = \DateTimeObj::createFromString($value);

        return $dt->getDateTime(false, true);
      case \DatamodulVarType::TYPE_DATE:
        $dt = \DateTimeObj::createFromString($value);

        return $dt->getDate();
      case \DatamodulVarType::TYPE_FLOAT:
        return MathOperations::floatToString($value);
      case \DatamodulVarType::TYPE_INT:
        return intval($value);
      case \DatamodulVarType::TYPE_FLOAT_DOT:
        return floatval($value);
      case \DatamodulVarType::TYPE_BINARY:
        return base64_encode($value);
      case \DatamodulVarType::TYPE_BINARY_NO_BASE64:
        return $value;
      case \DatamodulVarType::TYPE_GUID:
        return new \Kufer\Web\Classes\Addfunctions\Guid($value ?? '');
      case \DatamodulVarType::TYPE_STRING:
      default:
        return $value;
    }
  }
}