<?php
/**
 * @package    pixelcms_articles
 *
 * @author     christophf <your@email.com>
 * @copyright  A copyright
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @link       http://your.url.com
 */

use Joomla\Registry\Registry;

defined('_JEXEC') or die;

require_once 'BaseModel.php';

/**
 * Pixelcms_articles model.
 *
 * @package  pixelcms_articles
 * @since    1.0
 */
class Property {
    /** @var SimpleXMLElement $xml */
    protected static $xml;
    protected static $props;
    protected $propName;
    protected $unit;

    public function __construct()
    {
        $this->unit = (string) $this->getPropDefiniton()['displayunit'][0];
    }

    private function getPropDefiniton()
    {
        return static::$xml->xpath(static::$props . "[@name='$this->propName']")[0];
    }

    public static function setProps(SimpleXMLElement $xml, string $propertydefinitions = '/mascusexport/catalogs/catalog/categories/category/propertydefinitions/property')
    {
        static::$props = $propertydefinitions;
        static::$xml = $xml;
    }
}

class Category {
    public $id;
    public $name;

    public function  __construct(SimpleXMLElement $category) {
        $this->id = hexdec(substr(md5($category->name), 1, 3));
        $this->name = (string) $category->displayname;
    }
}

class PhoneNumber {
    public $area_code;
    public $country_code;
    public $call_number;

    public function  __construct(string $number) {
        preg_match('/^(\+\d+)\s(\d+)\s(\d+)/', $number, $matches);
        [, $country_code, $area_code, $call_number] = $matches;

        $this->country_code = $country_code;
        $this->area_code = $area_code;
        $this->call_number = $call_number;
    }
}

class Contact {
    public $id;
    public $description;
    public $email;
    public $phone;
    public $mobile;
    public $fax;
    public $image = null;


    public function  __construct(?SimpleXMLElement $contact) {
        $this->id = (string) $contact->xpath('id')[0];
        $this->description = (string) $contact->xpath('name')[0];
        $this->email = (string) $contact->xpath('email')[0];

        $phone = trim((string) $contact->xpath('phone')[0]);
        $mobile = trim((string) $contact->xpath('mobile')[0]);
        $fax = trim((string) $contact->xpath('fax')[0]);

        $this->phone = (!empty($phone)) ? new PhoneNumber($phone) : null;
        $this->mobile = (!empty($mobile)) ? new PhoneNumber($mobile) : null;
        $this->fax = (!empty($fax)) ? new PhoneNumber($fax) : null;
    }
}

class Manufacturer extends Property {
    protected $propName = "brand";
    public $id;
    public $value;

    public function  __construct(SimpleXMLElement $product) {
        parent::__construct();
        $manufacturer = $product->xpath('./property[@name="brand"]')[0];
        $this->id = hexdec(substr(md5($manufacturer), 1, 3));
        $this->value = (string) $manufacturer;
    }
}

class Workhours extends Property {
    protected $propName = "meterreadouthours";
    public $value;
    public $unit;
    public $show;

    public function  __construct(SimpleXMLElement $product) {
        parent::__construct();
        $this->value = (float) $product->xpath('./property[@name="meterreadouthours"]')[0];
        $this->show = "$this->value $this->unit";
    }
}

class Vat extends Property {
    protected $propName = "vatrate";
    public $value;
    public $separable = true; // TODO: Is there any field for this at mascus?

    public function  __construct(SimpleXMLElement $product) {
        parent::__construct();
        $this->value = (float) floatval($product->xpath('./property[@name="vatrate"]')[0]) / 100;
    }
}

class Power {
    public $PS;
    public $kW;

    public function  __construct(SimpleXMLElement $product) {
        $this->PS = (float) $product->xpath('./property[@name="engineoutput"]')[0];
        $this->kW = (float) round($this->PS / 1.36);
    }
}

class Price extends Property {
    protected $propName = "priceconverted";
    public $currency = 'EUR';
    public $specialprice = false;
    public $values;

    public function __construct(SimpleXMLElement $product)
    {
        parent::__construct();
        $original = $product->xpath('./property[@name="priceoriginal"]')[0];
        $this->currency = $this->unit;
        $oldprice = (float) $original;

        $netto = (float) $product->xpath('./property[@name="priceconverted"]')[0];
        $vat = (float) $product->xpath('./property[@name="vatrate"]')[0];

        // Only products with prices get calculated
        if ($netto > 0) {
            $this->values = (object) [
                'netto' => $netto,
                'brutto' => $netto * ((100 + $vat) / 100),
            ];

            if ($oldprice !== $netto) {
                $this->values['old'] = $oldprice;
                $this->specialprice = true;
            }
        }
    }
}

class Product {
    private $product;
    public $id;
    public $status;
    public $category;
    public $price;
    public $vat;
    public $properties;
    public $manufacturer;
    public $model;
    public $year;
    public $power;
    public $workhours;
    public $link;
    public $offer;
    public $images;
    public $free_text;
    public $contact_persons;
    public $dealer_internal_id;

    public function  __construct(SimpleXMLElement $product, Category $category) {
        $this->product = $product;
        $this->category = $category;

        $this->status = $this->getInt('./property[@name="visibilitybyte"]');

        $this->id = hexdec($this->getString('./property[@name="mascusid"]'));
        $this->dealer_internal_id = $this->getString('./property[@name="mascusid"]');

        $this->images = $this->getImages();
        $this->contact_persons = $this->getContactPersons();

        $this->model = $this->getString('./property[@name="model"]');
        $this->year = $this->getInt('./property[@name="yearofmanufacture"]');

        $this->manufacturer = new Manufacturer($product);
        $this->price = new Price($product);
        $this->vat = new Vat($product);
        if($this->hasProperty('meterreadouthours')) {
            $this->workhours = new Workhours($product);
        }

        if($this->hasProperty('engineoutput')) {
            $this->power = new Power($product);
        }

        $this->offer = $this->price->specialprice;

        $this->free_text = $this->getString('./property[@name="otherinformation"]');
    }

    private static function isValidNode($node): bool
    {
        return isset($node) && $node instanceof SimpleXMLElement && !empty($node);
    }

    private function hasProperty($prop): bool
    {
        $propertyNode = $this->product->xpath("./property[@name='$prop']");
        if (!isset($propertyNode[0])) return false;
        return static::isValidNode($propertyNode[0]);
    }

    private function getArray(string $path): array
    {
        return array_map(function($value) {
            return (string) $value;
        }, $this->product->xpath($path));
    }

    private function getString(string $path): string
    {
        return (string) $this->product->xpath($path)[0];
    }

    private function getInt(string $path): int
    {
        return (int) $this->product->xpath($path)[0];
    }

    private function getBool(string $path): bool
    {
        return (bool) $this->product->xpath($path)[0];
    }

    private function getContactPersons(): Array
    {
        return array_map(function ($contact) {
            return new Contact($contact);
        }, $this->product->xpath('./sellers/seller'));
    }

    private function getImages(): array
    {
        return array_map(function($image) {
            return (object) ['href' => ((strpos($image, 'st.mascus.com') !== false) ? "" : "https://st.mascus.com") . $image];
        }, $this->getArray('./attachments/attachment[@type="image"]'));
    }
}

class Pixelcms_articlesModelMascus_products  extends BaseModel
{
    protected $module = 'traktorpool';
    protected $api_key;
    protected $articleId;
    protected $customer_id;
    protected $contact_persons;
    protected $password;
    protected $username;
    protected $language;
    protected $url;
    protected $ads;
    protected $menuId;
    protected $filters = [];

    protected $defaults = [
        'print_btn' => 1,
        'price_style' => 0,
        'speed', 750,
        'disable_title_image' => 0,
    ];

    protected $hidden = [
        'tracktorpool_apikey',
        'tracktorpool_customer_id',
        'tracktorpool_username',
        'tracktorpool_password',
        'traktorpool_condition',
        'traktorpool_status',
    ];

    public function __construct(array $config = array())
    {
        $lang = JFactory::getLanguage();
        $langCode = $lang->getTag();
        $langCodeShort = substr($langCode, 0, 2);

        parent::__construct($config);

        /** @var ExtendedRegistry $params - Settings for current Menu Item */
        $params = $this->getState('params');

        $this->menuId = $params->getInt('Itemid');

        $this->customer_id = $params->get('tracktorpool_customer_id');

        //$this->print_btn = $jinput->getInt('print_btn');
        $this->articleId = $params->getInt('articleId', null);

        $this->language = $langCode;

        $this->ads = self::getAds();
    }

    private function getAds()
    {
        $ads = self::checkCache();

        foreach ($ads as $ad) {
            $query = http_build_query(array_merge($this->params->toArray(), [
                'articleId' => $ad->id,
                'Itemid' => $this->menuId,
            ]));
            $ad->link = JRoute::_("index.php?$query");
        }

        usort($ads, function($b, $a) {
            if ($a->id == $b->id) {
                return 0;
            }
            return ($a->id < $b->id) ? -1 : 1;
        });

        return $ads;
    }

    public function getRelatedAds()
    {
        $ad = $this->getData();
        $relatedAds =  array_values(array_filter($this->ads, function ($relatedAd) use ($ad) {
            return !!($ad->category->id == $relatedAd->category->id && $ad->id != $relatedAd->id);
        }));

        self::fisherYatesShuffle($relatedAds, $ad->id);

        return $relatedAds;
    }

    private static function fisherYatesShuffle(&$items, $seed)
    {
        if (is_string($seed)) {
            mt_srand(crc32($seed));
        } else {
            mt_srand($seed);
        }
        $items = array_values($items);
        for ($i = count($items) - 1; $i > 0; $i--)
        {
            $j = mt_rand(0, $i);
            $tmp = $items[$i];
            $items[$i] = $items[$j];
            $items[$j] = $tmp;
        }
    }

    public function getParams()
    {
        return $this->params;
    }

    /**
     *
     * @return mixed
     *
     * @since version
     * @throws Exception
     */
    private function fetchAds()
    {
        // $xmlBody = $this->cURL("http://services.mascus.com/api/getexport.aspx?exportid=$this->customer_id");
        $xml = simplexml_load_file("https://services.mascus.com/api/getexport.aspx?exportid=$this->customer_id", 'SimpleXMLElement');

        Property::setProps($xml);

        $categories = $xml->xpath('/mascusexport/catalogs/catalog/categories/category');
        $products = [];
        foreach($categories as $category) {
            $category_products = array_map(function($product) use ($category) {
                return new Product($product, new Category($category));
            }, $category->xpath('./products/product'));

            if ($this->params->get('traktorpool_status', 1)) {
                $products[] = array_filter($category_products, function (Product $product) {
                    return $product->status;
                });
            } else {
                $products[] = $category_products;
            }
        }

        return array_reduce($products, 'array_merge', []);
    }

    private function checkCache($force = false)
    {
        $cacheKey = "mascus";
        $ads = $this->cache->get($cacheKey, $this->module);

        if (empty($ads) || $force) {
            $ads = $this->fetchAds();
            $this->cache->store( json_encode($ads), $cacheKey, $this->module);
        } else {
            $ads = json_decode($ads);
        }

        return $ads ?? [];
    }

    public function revalidateCache()
    {
        $this->ads = $this->checkCache(true);
    }

    /**
     * @param       $url
     * @param array $parameters
     *
     * @return mixed
     *
     * @since version
     * @throws Exception
     */
    protected function cURL($url, $parameters = [])
    {
        $options = [
            CURLOPT_URL => "$url",
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER         => true,
            CURLOPT_ENCODING => "",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 20,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "GET",
            CURLOPT_POSTFIELDS => "",
            CURLOPT_HTTPHEADER => [
                "Accept: application/json",
                'Cache-Control: no-cache',
            ],
        ];

        $ch = curl_init();
        curl_setopt_array( $ch, $options );

        try {
            $raw_response  = curl_exec( $ch );

            // validate CURL status
            if(curl_errno($ch))
                throw new Exception(curl_error($ch), 500);

            $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($raw_response, 0, $header_size);
            $body = substr($raw_response, $header_size);

            $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if ($status_code != 200) {
                throw new Exception("Response with Status Code [" . $status_code . "].", 500);
            }
        } catch(Exception $ex) {
            if ($ch != null) curl_close($ch);
            throw new Exception($ex);
        }

        if ($ch != null) curl_close($ch);

        return $body;
    }

    public function getData()
    {
        $data = $this->ads;

        if ($this->articleId) {
            $data = array_reduce($data, function ($carry, $article) {
                if ($article->id === $this->articleId) {
                    $carry = $article;
                }
                return $carry;
            });

            if (!$data || $data->status === 0) {
                throw new \Exception("Maschine nicht gefunden", 404);
            }
        }

        return $data;
    }
}
