<?php
namespace shop;
use shop\Shop;
use S;
class Product implements \ArrayAccess
{
  public function __construct( int $product_id, $lang_id = null, $permutation_hash = null )
  {
    global $mdb;

    if ( !$lang_id )
      $lang_id = \front\factory\Languages::default_language();

    $result = $mdb -> get( 'pp_shop_products', '*', [ 'id' => $product_id ] );
    if ( \S::is_array_fix( $result ) ) foreach ( $result as $key => $val )
      $this -> $key = $val;

    // kombinacja produktu
    if ( $this -> parent_id )
    {
      // pobranie wartości z produktu głównego
      if ( !$this -> price_netto or !$this -> price_brutto )
      {
        $result = $mdb -> get( 'pp_shop_products', [ 'price_netto', 'price_brutto', 'price_netto_promo', 'price_brutto_promo', 'vat', 'wp' ], [ 'id' => $this -> parent_id ] );
        if ( \S::is_array_fix( $result ) ) foreach ( $result as $key => $val )
          $this -> $key = $val;
      }

      $results = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $this -> parent_id, 'lang_id' => $lang_id ] ] );
      if ( \S::is_array_fix( $results ) ) foreach ( $results as $row )
      {
        if ( $row[ 'copy_from' ] )
        {
          $results2 = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $this -> parent_id, 'lang_id' => $row[ 'copy_from' ] ] ] );
          if ( is_array( $results2 ) )
            foreach ( $results2 as $row2 )
              $this -> language = $row2;
        }
        else
          $this -> language = $row;
      }

      $this -> images = $mdb -> select( 'pp_shop_products_images', '*', [ 'product_id' => $this -> parent_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] );
      $this -> files = $mdb -> select( 'pp_shop_products_files', '*', [ 'product_id' => $this -> parent_id] );
      $this -> categories = $mdb -> select( 'pp_shop_products_categories', 'category_id', [ 'product_id' => $this -> parent_id ] );

      $this -> products_related = $mdb -> select( 'pp_shop_products_related', 'product_related_id', [ 'product_id' => $this -> parent_id ] );
      $this -> products_sets = $mdb -> select( 'pp_shop_product_sets_products', 'product_id', [ 'AND' => [ 'set_id' => $this -> set_id, 'product_id[!]' => $this -> parent_id ] ] );
    }
    else
    {
      $results = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lang_id ] ] );
      if ( \S::is_array_fix( $results ) ) foreach ( $results as $row )
      {
        if ( $row[ 'copy_from' ] )
        {
          $results2 = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $row[ 'copy_from' ] ] ] );
          if ( is_array( $results2 ) )
            foreach ( $results2 as $row2 )
              $this -> language = $row2;
        }
        else
          $this -> language = $row;
      }

      $this -> images = $mdb -> select( 'pp_shop_products_images', '*', [ 'product_id' => $product_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] );
      $this -> files = $mdb -> select( 'pp_shop_products_files', '*', [ 'product_id' => $product_id ] );
      $this -> categories = $mdb -> select( 'pp_shop_products_categories', 'category_id', [ 'product_id' => $product_id ] );

      $this -> products_related = $mdb -> select( 'pp_shop_products_related', 'product_related_id', [ 'product_id' => $product_id ] );
      $this -> products_sets = $mdb -> select( 'pp_shop_product_sets_products', 'product_id', [ 'AND' => [ 'set_id' => $this -> set_id, 'product_id[!]' => $product_id ] ] );

      $results = $mdb -> select( 'pp_shop_products', 'id', [ 'parent_id' => $product_id ] );
      if ( \S::is_array_fix(  $results ) )
      {
        foreach ( $results as $row )
          $product_combinations[] = \shop\Product::getFromCache( $row, $lang_id );

        $this -> product_combinations = $product_combinations;
      }
    }

    $this -> producer = $mdb -> get( 'pp_shop_producer', '*', [ 'id' => (int) $this -> producer_id ] );

    if ( $permutation_hash )
    {
      $permutation_price = $mdb -> get( 'pp_shop_products', [ 'price_netto', 'price_brutto', 'price_netto_promo', 'price_brutto_promo' ], [ 'AND' => [ 'permutation_hash' => $permutation_hash, 'parent_id' => $product_id ] ] );
      if ( is_array( $permutation_price ) )
      {
        if ( $permutation_price[ 'price_netto' ] != null )
          $this -> price_netto = $permutation_price[ 'price_netto' ];

        if ( $permutation_price[ 'price_brutto' ] != null )
          $this -> price_brutto = $permutation_price[ 'price_brutto' ];

        if ( $permutation_price[ 'price_netto_promo' ] != null )
          $this -> price_netto_promo = $permutation_price[ 'price_netto_promo' ];

        if ( $permutation_price[ 'price_brutto_promo' ] != null )
          $this -> price_brutto_promo = $permutation_price[ 'price_brutto_promo' ];
      }
    }

    $this -> custom_fields = $mdb -> select( 'pp_shop_products_custom_fields', '*', [ 'id_product' => $product_id ] );
  }

  /**
   * Retrieves a product object from cache or creates a new instance if not found.
   *
   * @param int $product_id The ID of the product.
   * @param int $lang_id The ID of the language.
   * @param string $permutation_hash The permutation hash of the product.
   * @return \shop\Product The product object.
   */
  public static function getFromCache( $product_id, $lang_id, $permutation_hash = null )
  {
    // Check if Redis extension is loaded
    if ( class_exists( 'Redis' ) )
    {
      try
      {
        // Get the Redis connection instance
        $redis = \RedisConnection::getInstance() -> getConnection();

        // Try to retrieve the serialized product object from cache
        $objectData = $redis -> get( "shop\product:$product_id:$lang_id:$permutation_hash" );

        if ( !$objectData )
        {
          // Product not found in cache, create a new instance and store it in cache
          $object = new self( $product_id, $lang_id, $permutation_hash );
          $redis->setex( "shop\product:$product_id:$lang_id:$permutation_hash", 60 * 60 * 24, serialize( $object ) );
        }
        else
        {
          // Product found in cache, unserialize it
          $object = unserialize( $objectData );
        }
      }
      catch ( \Exception $e )
      {
        // Log the exception if needed
        $object = new self( $product_id, $lang_id, $permutation_hash );
      }
    }
    else
    {
      // Redis extension not loaded, create a new instance
      $object = new self( $product_id, $lang_id, $permutation_hash );
    }

    return $object;
  }

  public function getDefaultCombinationPrices()
  {
    $permutation_hash = '';

    $attributes = \shop\Product::get_product_attributes( $this -> product_combinations );
    foreach ( $attributes as $attribute )
    {
      foreach ( $attribute['values'] as $value )
      {
        if ( $value['is_default'] )
        {
          if ( $permutation_hash )
            $permutation_hash .= '|';
          $permutation_hash .= $attribute['id'] . '-' . $value['id'];
        }
      }
    }

    foreach ( $this -> product_combinations as $product_combination )
    {
      if ( $product_combination -> permutation_hash == $permutation_hash )
      {
        $prices['price_netto'] = $product_combination -> price_netto;
        $prices['price_brutto'] = $product_combination -> price_brutto;
        $prices['price_netto_promo'] = $product_combination -> price_netto_promo;
        $prices['price_brutto_promo'] = $product_combination -> price_brutto_promo;
      }
    }

    return $prices;
  }

  public function generateSubtitleFromAttributes( $permutation_hash )
  {
    global $lang_id;

    $subtitle = '';

    $attributes = explode( '|', $permutation_hash );
    foreach ( $attributes as $attribute )
    {
      $attribute = explode( '-', $attribute );

      if ( $subtitle )
        $subtitle .= ', ';

      $subtitle .= \shop\ProductAttribute::getAttributeName( $attribute[0], $lang_id ) . ': ' . \shop\ProductAttribute::get_value_name( $attribute[1], $lang_id );
    }

    return $subtitle;
  }

  public function getProductDataBySelectedAttributes( $selected_attribute )
  {
    global $settings;

    foreach ( $this -> product_combinations as $product_combination )
    {
      if ( $product_combination -> permutation_hash == $selected_attribute )
      {
        if ( $product_combination -> quantity !== null or $product_combination -> stock_0_buy )
        {
          $result['quantity'] = $product_combination -> quantity;
          $result['stock_0_buy'] = $product_combination -> stock_0_buy;
          $result['price_netto'] = Shop::shortPrice( $product_combination -> price_netto );
          $result['price_brutto'] = Shop::shortPrice( $product_combination -> price_brutto );
          $result['price_netto_promo'] = $product_combination -> price_netto_promo ? Shop::shortPrice( $product_combination -> price_netto_promo ) : null;
          $result['price_brutto_promo'] = $product_combination -> price_brutto_promo ? Shop::shortPrice( $product_combination -> price_brutto_promo ) : null;
        }
        else
        {
          $result['quantity'] = $this -> quantity;
          $result['stock_0_buy'] = $this -> stock_0_buy;
          $result['price_netto'] = Shop::shortPrice( $this -> price_netto );
          $result['price_brutto'] = Shop::shortPrice( $this -> price_brutto );
          $result['price_netto_promo'] = $this -> price_netto_promo ? Shop::shortPrice( $this -> price_netto_promo ) : null;
          $result['price_brutto_promo'] = $this -> price_brutto_promo ? Shop::shortPrice( $this -> price_brutto_promo ) : null;
        }
      }
    }
    $result['messages']['warehouse_message_zero'] = $this -> language['warehouse_message_zero'] ? $this -> language['warehouse_message_zero'] : $settings['warehouse_message_zero_pl'];
    $result['messages']['warehouse_message_nonzero'] = $this -> language['warehouse_message_nonzero'] ? $this -> language['warehouse_message_nonzero'] : $settings['warehouse_message_nonzero_pl'];
    $result['permutation_hash'] = $selected_attribute;
    return $result;
  }

  // sprawdź czy produkt jest na promocji
  static public function is_product_on_promotion( int $product_id )
  {
    global $mdb;

    if ( $mdb -> get( 'pp_shop_products', 'price_netto_promo', [ 'id' => $product_id ] ) != null )
      return true;

    return false;
  }

  // pobierz id produktu z Baselinker
  static public function get_baselinker_product_id( int $product_id )
  {
    global $mdb;

    $result = $mdb -> get( 'pp_shop_products', [ 'parent_id', 'baselinker_product_id' ], [ 'id' => $product_id ] );
    if ( $result['baselinker_product_id'] )
      return $result['baselinker_product_id'];
    else
      return $mdb -> get( 'pp_shop_products', 'baselinker_product_id', [ 'id' => $result['parent_id'] ] );
  }

  // pobierz kod SKU
  static public function get_product_sku( int $product_id )
  {
    global $mdb;

    $result = $mdb -> get( 'pp_shop_products', [ 'parent_id', 'sku' ], [ 'id' => $product_id ] );
    if ( $result['sku'] )
      return $result['sku'];
    else
      return $mdb -> get( 'pp_shop_products', 'sku', [ 'id' => $result['parent_id'] ] );
  }

  // pobierz cenę produktu
  static public function get_product_price( int $product_id )
  {
    global $mdb;

    $prices = $mdb -> get( 'pp_shop_products', [ 'price_brutto', 'price_brutto_promo' ], [ 'id' => $product_id ] );

    if ( $prices['price_brutto_promo'] != '' and $prices['price_brutto_promo'] < $prices['price_brutto'] )
      return $prices['price_brutto_promo'];
    else
      return $prices['price_brutto'];
  }

  // pobierz nazwę produktu
  static public function get_product_name( int $product_id, string $lang_id = null )
  {
    global $mdb;

    if ( !$lang_id )
      $lang_id = \front\factory\Languages::default_language();

    return $mdb -> get( 'pp_shop_products_langs', 'name', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lang_id ] ] );
  }

  // pobierz i wyświetl produktu do zestawu po dodaniu do koszyka
  static public function product_sets_when_add_to_basket( int $product_id )
  {
    global $mdb;

    $cacheHandler = new \CacheHandler();
    $cacheKey = "\shop\Product::product_sets_when_add_to_basket:$product_id";

    $objectData = $cacheHandler -> get( $cacheKey );

    if ( !$objectData )
    {
      if ( $product_id_tmp = $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $product_id ] ) )
        $set_id = $mdb -> get( 'pp_shop_products', 'set_id', [ 'id' => $product_id_tmp ] );
      else
        $set_id = $mdb -> get( 'pp_shop_products', 'set_id', [ 'id' => $product_id ] );

      $products = $mdb -> select( 'pp_shop_product_sets_products', 'product_id', [ 'set_id' => $set_id ] );
      if ( !$products )
      {
        $products_intersection = $mdb -> select( 'pp_shop_orders_products_intersection', [ 'product_1_id', 'product_2_id' ], [ 'OR' => [ 'product_1_id' => $product_id, 'product_2_id' => $product_id ], 'ORDER' => [ 'count' => 'DESC' ], 'LIMIT' => 5 ] );
        if ( !count( $products_intersection ) )
        {
          $product_id = $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $product_id ] );
          $products_intersection = $mdb -> select( 'pp_shop_orders_products_intersection', [ 'product_1_id', 'product_2_id' ], [ 'OR' => [ 'product_1_id' => $product_id, 'product_2_id' => $product_id ], 'ORDER' => [ 'count' => 'DESC' ], 'LIMIT' => 5 ] );
        }

        foreach ( $products_intersection as $product_intersection )
        {
          if ( $product_intersection['product_1_id'] != $product_id )
            $products[] = $product_intersection['product_1_id'];
          else
            $products[] = $product_intersection['product_2_id'];
        }
        array_unique( $products );
      }

      $cacheHandler -> set( $cacheKey, $products );
    }
    else
    {
      $products = unserialize( $objectData );
    }

    foreach ( $products as $product_id )
    {
      if ( !\front\factory\ShopProduct::is_product_active( $product_id ) )
        $products = array_diff( $products, [ $product_id ] );
    }

    return \Tpl::view( 'shop-basket/alert-product-sets', [
      'products' => $products
      ] );
  }

  // dodaje 1 do licznika odwiedziń produktu
  static public function add_visit( int $product_id )
  {
    global $mdb;
    return $mdb -> update( 'pp_shop_products', [ 'visits[+]' => 1 ], [ 'id' => $product_id ] );
  }

  //FIX:ME - do poprawy nazwa
  // pobierz zdjęcie główne
  static public function getProductImg( int $product_id )
  {
    global $mdb;

    $results = $mdb -> select( 'pp_shop_products_images', 'src', [ 'product_id' => $product_id, 'ORDER' => [ 'o' => 'ASC' ] ] );
    if ( \S::is_array_fix( $results ) ) foreach ( $results as $row )
      return $row;
  }

  //FIX:ME - do poprawy nazwa
  // pobierz bezpośredni url produktu
  static public function getProductUrl( int $product_id )
  {
    global $mdb;

    $lang_id = \front\factory\Languages::default_language();

    $results = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lang_id ] ] );
    if ( \S::is_array_fix( $results ) ) foreach ( $results as $row )
    {
      if ( $row[ 'copy_from' ] )
      {
        $results2 = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $row[ 'copy_from' ] ] ] );
        if ( is_array( $results2 ) )
          foreach ( $results2 as $row2 )
            $language = $row2;
      }
      else
        $language = $row;
    }

    $language['seo_link'] ? $url = '/' . $language['seo_link'] : $url = '/p-' . $product_id . '-' . \S::seo( $language['name'] );

    return $url;
  }

  // pobierz ilość wyników na podstawie wyszukiwanej nazwy
  static public function searchProductsByNameCount( $query, $lang_id )
  {
    global $mdb;

    $results = $mdb -> query( 'SELECT COUNT(0) AS c FROM ( '
                                . 'SELECT psp.id, '
                                  . '( CASE '
                                    . 'WHEN copy_from IS NULL THEN name '
                                    . 'WHEN copy_from IS NOT NULL THEN ( '
                                      . 'SELECT '
                                        . 'name '
                                      . 'FROM '
                                        . 'pp_shop_products_langs '
                                      . 'WHERE '
                                        . 'lang_id = pspl.copy_from AND product_id = psp.id '
                                      . ') '
                                    . 'END ) AS name '
                                  . 'FROM '
                                    . 'pp_shop_products AS psp '
                                    . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
                                  . 'WHERE '
                                    . 'status = 1 AND name LIKE :query AND lang_id = :lang_id '
                                  . ') AS q1', [
                                  ':query' => '%' . $query . '%',
                                  ':lang_id' => $lang_id
                                ] ) -> fetchAll( \PDO::FETCH_ASSOC );
    return $results[0]['c'];
  }

  // pobierz id produktów na podstawie wyszukiwanej nazwy
  static public function getProductsIdByName( string $query, string $lang_id, int $products_limit, int $from )
  {
    global $mdb;

    $results = $mdb -> query( 'SELECT '
                                . 'psp.id, '
                                . '( CASE '
                                  . 'WHEN copy_from IS NULL THEN name '
                                  . 'WHEN copy_from IS NOT NULL THEN ( '
                                    . 'SELECT '
                                      . 'name '
                                    . 'FROM '
                                      . 'pp_shop_products_langs '
                                    . 'WHERE '
                                      . 'lang_id = pspl.copy_from AND product_id = psp.id '
                                    . ') '
                                  . 'END ) AS name '
                                . 'FROM '
                                  . 'pp_shop_products AS psp '
                                  . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
                                . 'WHERE '
                                  . 'status = 1 AND name LIKE :query AND lang_id = :lang_id '
                                . 'ORDER BY '
                                  . 'name ASC '
                                . 'LIMIT '
                                  . $from . ',' . $products_limit, [
                                    ':query' => '%' . $query . '%',
                                    ':lang_id' => $lang_id
                                  ] ) -> fetchAll( \PDO::FETCH_ASSOC );
    if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
      $output[] = $row['id'];

    return $output;
  }

  static public function searchProductsByName( string $query, string $lang_id, int $bs = 0 )
  {
    $count = \shop\Product::searchProductsByNameCount( $query, $lang_id );
    $ls = ceil( $count / 12 );

    if ( $bs < 1 )
      $bs = 1;
    else if ( $bs > $ls )
      $bs = $ls;

    $from = 12 * ( $bs - 1 );

    if ( $from < 0 )
      $from = 0;

    $results['products'] = \shop\Product::getProductsIdByName( $query, $lang_id, 12, $from );
    $results['count'] = $count;
    $results['ls'] = $ls;

    return $results;

  }

  static public function searchProductByNameAjax( string $query, string $lang_id )
  {
    global $mdb;
    return $mdb -> query(
                    'SELECT
                        product_id
                      FROM
                        pp_shop_products_langs AS pspl
                        INNER JOIN pp_shop_products AS psp ON psp.id = pspl.product_id
                      WHERE
                        status = 1 AND lang_id = :lang_id AND LOWER(name) LIKE :query
                      ORDER BY visits DESC
                      LIMIT 12', [
                    ':query' => '%' . $query . '%',
                    ':lang_id' => $lang_id
                  ] ) -> fetchAll( \PDO::FETCH_ASSOC );
  }

  public function permutations()
  {
    if ( is_array( $this -> attributes ) ) foreach ( $this -> attributes as $attribute )
    {
      $attribute_obj = new \shop\ProductAttribute( $attribute['attribute_id'] );

      if ( $attribute_obj['stock_influences'] )
        $attributes_tmp[ $attribute[ 'attribute_id' ] ][] = $attribute[ 'value_id' ];
    }

    if ( is_array( $attributes_tmp ) )
      return $this -> permutations = self::array_cartesian( $attributes_tmp );
  }

  /// sprawdź czy produkt można zamawiać przy stanie magazynowym zero
  static public function is_stock_0_buy( int $product_id )
  {
    global $mdb;

    // jeżeli jest to kombinacja produktu
    if ( $parent_id = $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $product_id ] ) )
      return $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $parent_id ] );
    else
      return $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $product_id ] );
  }

  // pobierz stan magazynowy i komunikaty dla kombinacji produktu
  static public function get_product_permutation_quantity_options( int $product_id, $permutation )
  {
    global $mdb, $settings;

    $cacheHandler = new \CacheHandler();
    $cacheKey = "\shop\Product::get_product_permutation_quantity_options:$product_id:$permutation";

    $objectData = $cacheHandler -> get( $cacheKey );

    if ( !$objectData )
    {
      if ( $mdb -> count( 'pp_shop_products', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] ) )
      {
        $result['quantity'] = $mdb -> get( 'pp_shop_products', 'quantity', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] );
        if ( $result['quantity'] == null )
        {
          $result['quantity'] = $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] );
          $result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $product_id] );
          $result['messages'] = $mdb -> get( 'pp_shop_products_langs', [ 'warehouse_message_zero', 'warehouse_message_nonzero' ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => 'pl' ] ] );

          if ( !$result['messages']['warehouse_message_zero'] )
            $result['messages']['warehouse_message_zero'] = $settings['warehouse_message_zero_pl'];

          if ( !$result['messages']['warehouse_message_nonzero'] )
            $result['messages']['warehouse_message_nonzero'] = $settings['warehouse_message_nonzero_pl'];
        }
        else
        {
          $result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] );
          $result['messages'] = $mdb -> get( 'pp_shop_products_langs', [ 'warehouse_message_zero', 'warehouse_message_nonzero' ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => 'pl' ] ] );

          if ( !$result['messages']['warehouse_message_zero'] )
            $result['messages']['warehouse_message_zero'] = $settings['warehouse_message_zero_pl'];

          if ( !$result['messages']['warehouse_message_nonzero'] )
            $result['messages']['warehouse_message_nonzero'] = $settings['warehouse_message_nonzero_pl'];
        }
      }
      else
      {
        $result['quantity'] = $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] );
        $result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $product_id] );
        $result['messages'] = $mdb -> get( 'pp_shop_products_langs', [ 'warehouse_message_zero', 'warehouse_message_nonzero' ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => 'pl' ] ] );

        if ( !$result['messages']['warehouse_message_zero'] )
          $result['messages']['warehouse_message_zero'] = $settings['warehouse_message_zero_pl'];

        if ( !$result['messages']['warehouse_message_nonzero'] )
          $result['messages']['warehouse_message_nonzero'] = $settings['warehouse_message_nonzero_pl'];
      }

      $cacheHandler -> set( $cacheKey, $result );
    }
    else
    {
      return unserialize( $objectData );
    }

    return $result;
  }

  // pobierz stan magazynowy produktu
  static public function get_product_quantity( int $product_id )
  {
    global $mdb;
    return $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] );
  }

  public static function product_categories( int $product_id )
  {
    global $mdb;
    return $mdb -> select( 'pp_shop_products_categories', 'category_id', [ 'product_id' => $product_id ] );
  }

  public static function calculate_basket_product_price( float $price_brutto_promo, float $price_brutto, $coupon, $basket_position )
  {
    // ? produkty przecenione
    if ( $price_brutto_promo )
    {
      $price['price'] = $price_brutto;
      $price['price_new'] = $price_brutto_promo;

      // ? zastosuje kod rabatowy
      if ( $coupon -> type && $coupon -> include_discounted_product )
      {
        // ? ograniczony do wybranych kategorii
        if ( $coupon -> categories != null )
        {
          $coupon_categories = json_decode( $coupon -> categories );
          $product_categories = \shop\Product::product_categories( (int)$basket_position['parent_id'] ? (int)$basket_position['parent_id'] : (int)$basket_position['product-id'] );
          if ( is_array( $coupon_categories ) ) foreach ( $coupon_categories as $category_tmp )
          {
            if ( in_array( $category_tmp, $product_categories ) )
            {
              $price['price_new'] = \S::normalize_decimal( $price['price_new'] - $price['price_new'] * $coupon -> amount / 100 );
              break;
            }
          }
        }
        // ? nieograniczony kategoriami
        else
          $price['price_new'] = \S::normalize_decimal( $price['price_new'] - $price['price_new'] * $coupon -> amount / 100 );

        // ? uwzględnij promocję jeżeli może łączyć się z rabatami i produktami przecenionymi
        if ( $basket_position['discount_amount'] && $basket_position['discount_include_coupon'] && $basket_position['include_product_promo'] )
        {
          // ? najtańszy produkt w koszyku (z wybranych kategorii) za X zł
          if ( $basket_position['discount_type'] == 3 )
            $price['price_new'] = \S::normalize_decimal( $basket_position['discount_amount'] );

          // ? rabat procentowy
          if ( $basket_position['discount_type'] == 1 )
            $price['price_new'] = \S::normalize_decimal($price['price_new'] - $price['price_new'] * $basket_position['discount_amount'] / 100 );
        }
      }
      // ? brak kodu rabatowego
      else
      {
        if ( $basket_position['discount_amount'] && $basket_position['include_product_promo'] )
        {
          // ? Najtańszy produkt w koszyku (z wybranych kategorii) za X zł
          if ( $basket_position['discount_type'] == 3 )
            $price['price_new'] = \S::normalize_decimal( $basket_position['discount_amount'] );

          // ? rabat procentowy
          if ( $basket_position['discount_type'] == 1 )
            $price['price_new'] = \S::normalize_decimal($price['price_new'] - $price['price_new'] * $basket_position['discount_amount'] / 100 );
        }
      }
    }
    // ? produkt nieprzeceniony
    else
    {
      $price['price'] = $price_brutto;
      $price['price_new'] = $price_brutto;

      // ? zastosuj kod rabatowy
      if ( $coupon -> type )
      {
        // ? ograniczony do wybranych kategorii
        if ( $coupon -> categories != null )
        {
          $coupon_categories = json_decode( $coupon -> categories );
          $product_categories = \shop\Product::product_categories( $basket_position['parent_id'] ? $basket_position['parent_id'] : $basket_position['product-id'] );
          if ( is_array( $coupon_categories ) ) foreach ( $coupon_categories as $category_tmp )
          {
            if ( in_array( $category_tmp, $product_categories ) )
            {
              $price['price_new'] = \S::normalize_decimal( $price['price_new'] - $price['price_new'] * $coupon -> amount / 100 );
              break;
            }
          }
        }
        // ? nieograniczony
        else
          $price['price_new'] = \S::normalize_decimal($price['price'] - $price['price'] * $coupon -> amount / 100 );

        // ? uwzględnij promocję jeżeli może łączyć się z rabatami i produktami przecenionymi
        if ( $basket_position['discount_amount'] && $basket_position['discount_include_coupon'] && $basket_position['include_product_promo'] )
        {
          // ? najtańszy produkt w koszyku (z wybranych kategorii) za X zł
          if ( $basket_position['discount_type'] == 3 )
            $price['price_new'] = \S::normalize_decimal( $basket_position['discount_amount'] );

          // ? rabat procentowy
          if ( $basket_position['discount_type'] == 1 )
            $price['price_new'] = \S::normalize_decimal($price['price'] - $price['price'] * $basket_position['discount_amount'] / 100 );
        }
      }
      // ? bez kodu rabatowego
      else
      {
        if ( $basket_position['discount_amount'] )
        {
          // ? Najtańszy produkt w koszyku (z wybranych kategorii) za X zł
          if ( $basket_position['discount_type'] == 3 )
            $price['price_new'] = \S::normalize_decimal( $basket_position['discount_amount'] );

          // ? rabat procentowy
          if ( $basket_position['discount_type'] == 1 )
            $price['price_new'] = \S::normalize_decimal($price['price'] - $price['price'] * $basket_position['discount_amount'] / 100 );
        }
      }
    }

    return $price;
  }

  // pobierz wiadomość zależną od stanu magazynowego
  // id_product - id produktu
  // attributes - wybrane atrybuty przez użytkownika
  public static function getWarehouseMessage( int $id_product, $attributes, string $lang_id )
  {
    global $settings;

    $permutation = self::getPermutation( $attributes );

    $quantity = self::getPermutationQuantity( $id_product, $permutation );

    if ( $quantity )
    {
      if ( $msg = \front\factory\ShopProduct::warehouse_message_nonzero( $id_product, $lang_id ) )
        $result = [ 'msg' => $msg, 'quantity' => $quantity ];
      else if ( $settings[ 'warehouse_message_nonzero_' . $lang_id ] )
        $result = [ 'msg' => $settings[ 'warehouse_message_nonzero_' . $lang_id ], 'quantity' => $quantity ];
    }
    else
    {
      if ( $msg = \front\factory\ShopProduct::warehouse_message_zero( $id_product, $lang_id ) )
        $result = [ 'msg' => $msg, 'quantity' => $quantity ];
      else if ( $settings[ 'warehouse_message_zero_' . $lang_id ] )
        $result = [ 'msg' => $settings[ 'warehouse_message_zero_' . $lang_id ], 'quantity' => $quantity ];
    }
    return $result;
  }

  // pobranie id produktu wg wybranych parametrów
  static public function get_product_id_by_attributes( int $parent_id, array $attributes )
  {
    global $mdb;

    return $mdb -> get( 'pp_shop_products', 'id', [ 'AND' => [ 'parent_id' => $parent_id, 'permutation_hash' => implode( '|', $attributes ) ] ] );
  }

  // pobranie listy atrybutów z wybranymi wartościami
  static public function get_product_attributes( $products )
  {
    if ( !is_array( $products ) or !count( $products ) )
      return false;

    $attributes = array();

    foreach ( $products as $product )
    {
      $permutations = explode( '|', $product['permutation_hash'] );
      foreach ( $permutations as $permutation )
      {
        $attribute = explode( '-', $permutation );

        $value['id'] =  $attribute[1];
        $value['is_default'] = \shop\ProductAttribute::is_value_default( $attribute[1] );

        if ( array_search( $attribute[1], array_column( $attributes, 'id' ) ) === false )
          $attributes[ $attribute[0] ][] = $value;
      }
    }

    $attributes = S::removeDuplicates( $attributes, 'id' );

    foreach ( $attributes as $key => $val )
    {
      $row['id'] = $key;
      $row['values'] = $val;

      $attributes_sort[ \shop\ProductAttribute::get_attribute_order( $key ) ] = $row;
    }

    return $attributes_sort;
  }

  public static function array_cartesian( $input )
  {
    $result = array();

    while ( list($key, $values) = each( $input ) )
    {
      if ( empty( $values ) )
        continue;

      if ( empty( $result ) )
      {
        foreach ( $values as $value )
        {
          $result[] = array( $key => $value );
        }
      }
      else
      {
        $append = array();

        foreach ( $result as &$product )
        {
          $product[$key] = array_shift( $values );
          $copy = $product;

          foreach ( $values as $item )
          {
            $copy[$key] = $item;
            $append[]   = $copy;
          }

          array_unshift( $values, $product[$key] );
        }

        $result = array_merge( $result, $append );
      }
    }

    return $result;
  }

  public function __get( $variable )
  {
    if ( array_key_exists( $variable, $this -> data ) )
      return $this -> $variable;
  }

  public function __set( $variable, $value )
  {
    $this -> $variable = $value;
  }

  public function offsetExists( $offset )
  {
    return isset( $this -> $offset );
  }

  public function offsetGet( $offset )
  {
    return $this -> $offset;
  }

  public function offsetSet( $offset, $value )
  {
    $this -> $offset = $value;
  }

  public function offsetUnset( $offset )
  {
    unset( $this -> $offset );
  }

  // generate_sku_code
  public static function generate_sku_code( int $product_id )
  {
    global $mdb;
    // find product with sku like 'PP-000000'
    $skus = $mdb -> select( 'pp_shop_products', 'sku', [ 'sku[~]' => 'PP-' ] );
    if ( is_array( $skus ) )
    {
      foreach ( $skus as $sku )
        $sku_codes[] = (int)substr( $sku, 3 );
    }

    // find max sku
    if ( is_array( $sku_codes ) )
      $sku = 'PP-' . str_pad( max( $sku_codes ) + 1, 6, '0', STR_PAD_LEFT );
    else
      $sku = 'PP-000001';

    return $sku;
  }

  // product_xml_name_save
  public static function product_xml_name_save( int $product_id, string $xml_name, string $lang_id )
  {
    global $mdb;
    return $mdb -> update( 'pp_shop_products_langs', [ 'xml_name' => $xml_name ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lang_id ] ] );
  }

  // product_custom_label_suggestions
  public static function product_custom_label_suggestions( string $custom_label, string $label_type )
  {
    global $mdb;

    $results = $mdb -> query( 'SELECT DISTINCT
                                ' . $label_type . ' AS label
                              FROM
                                pp_shop_products
                              WHERE
                                ' . $label_type . ' LIKE :custom_label
                              LIMIT 10', [ ':custom_label' => '%' . $custom_label . '%' ] ) -> fetchAll( \PDO::FETCH_ASSOC );
    if ( is_array( $results ) )
    {
      foreach ( $results as $row )
        $output[] = $row;
    }

    return $output;
  }

  // product_custom_label_save
  public static function product_custom_label_save( int $product_id, string $custom_label, string $label_type )
  {
    global $mdb;
    return $mdb -> update( 'pp_shop_products', [ $label_type => $custom_label ], [ 'id' => $product_id ] );
  }
  public static function product_meta (int $product_id)
  {
    global $mdb;
    return $mdb->select(
      "pp_shop_products_categories (ppc)",
      [
          "[>]pp_shop_categories_langs (pcl)" => ["ppc.category_id" => "category_id"]
      ],
      [
          "pcl.title",
          "pcl.seo_link"
      ],
      [
          "ppc.product_id" => $product_id
      ]
    );
  }
}
