<?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 TraktorpoolOption {
    public $id;
    public $name;
    public $unit;
    public $href;
    public $type;
    public $value;
    public $show;
}

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

    public function  __construct(object $contact) {
        $this->country_code = $contact->country_code;
        $this->area_code = $contact->area_code;
        $this->call_number = $contact->call_number;
    }
}

class TP_List {
    static $username = 'pixel-kraft';
    static $password = 'VG9gZ9au';

    static $cache = [];
    static $jCache;

    public $value;

    public function __construct(object $opt, string $lang = 'de')
    {
        ['id' => $id, 'href' => $href, 'show' => $value] = (array) $opt;

        if (!isset(static::$cache[$id])) {
            static::$cache[$id] = static::getItems($href, $lang);
        }

        $this->value = static::$cache[$id][$value];
    }

    public function __toString()
    {
        return $this->value;
    }

    private static function cacheInstance(): JCache
    {
        return static::$jCache ?? static::$jCache = new JCache([
                'lifetime' => 24 * 60 * 60,
                'caching' => true,
                'defaultgroup' => 'traktorpool_data',
            ]);
    }

    protected static function getItems(string $href, string $lang): array
    {
        $data = json_decode(static::cacheInstance()->get($href.$lang));

        if (empty($data)) {
            $data = static::cURL($href, $lang);
            static::cacheInstance()->store(json_encode($data), $href.$lang);
        }

        return array_reduce((array) $data->select_items, function($result, $item) {
            $result[$item->id] = $item->value;
            return $result;
        }, []);
    }

    protected static function cURL($url, $lang): object
    {
        $parameters = [
            'language' => substr($lang, 0, 2),
        ];

        $query = http_build_query($parameters);

        $options = [
            CURLOPT_URL => "$url?$query",
            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_USERPWD        => static::$username . ":" . static::$password,
            CURLOPT_HTTPAUTH       => CURLAUTH_DIGEST,
            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 json_decode($body);
    }
}

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


    public function  __construct(object $contact) {
        $this->id = $contact->id;
        $this->description = "$contact->first_name $contact->last_name";
        $this->email = $contact->email;


        if ($contact->phone_number) {
            $phone_types = array_column($contact->phone_number, 'type');
            if ($phone = $contact->phone_number[array_search('telephone', $phone_types)]) {
                $this->phone = new PhoneNumber($phone);
            }
            if ($mobile = $contact->phone_number[array_search('mobile', $phone_types)]) {
                $this->mobile = new PhoneNumber($mobile);
            }
            if ($fax = $contact->phone_number[array_search('fax', $phone_types)]) {
                $this->fax = new PhoneNumber($fax);
            }
        }

        if ($contact->image) {
            $this->image = "https://www.traktorpool.de{$contact->image->url}?width=250&quality=85";
        }
    }
}

class Vat {
    public $value;
    public $separable;

    public function  __construct($vat) {
        $this->value = (float) $vat->value / 100;
        $this->separable = $vat->separate;
    }
}

class Pixelcms_articlesModelTracktorpool_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 $linkParams;
    protected $allParams;
    protected $menuId;
    protected $limit;
    protected $filters = [];

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

    protected $visible = [
        'print_btn',
        'price_style',
        'disable_title_image',
    ];

    public function __construct(array $config = array())
    {
        $this->input = $jinput = JFactory::getApplication()->input;

        $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');
        $menu = JFactory::getApplication()->getMenu()->getItem($this->menuId);

        $this->linkParams = [];
        $this->allParams = [];
        foreach ($menu->query as $key => $item) {
            if (in_array($key, $this->visible)) {
                $this->linkParams[$key] = $item;
            }
            $this->allParams[$key] = $item;
        }

        $this->api_key = $params->get('tracktorpool_apikey');
        $this->customer_id = $params->get('tracktorpool_customer_id');
        $this->username = $params->get('tracktorpool_username');
        $this->password = $params->get('tracktorpool_password');

        $this->limit = $params->getInt('limit', null);

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

        $this->language = $langCode;
	    $this->ads = self::prepareAds();
    }

    private function prepareAds()
    {
        $ads = self::checkCache()->ads;

        // $ads = array_values(array_filter($ads, function($ad) {
        //     return $ad->status === 'active';
        // }));

        foreach ($ads as $key => &$ad) {
            $ad->power = null;
            $ad->workhours = null;
            $ad->computer = null;
            $ad->equipment = null;
            $ad->cutting_width = null;
            $ad->front_wheels = (object) [];
            $ad->back_wheels = (object) [];
            $ad->free_text = null;
            $ad->vat = new Vat($ad->vat);

            $lookup = array_flip(array_column($ad->options, 'id'));

            /** @var TraktorpoolOption $option */
            foreach ($ad->options as $key => $option) {
                if ($option->type === 'list') {
                    $option->show = new TP_List($option);
                }
                if ($option->id === 424) {
                    $ad->power = (object) [
                        'kW' => $option->value,
                        'PS' => round($option->value * 1.36),
                    ];
                } else if ($option->id === 314) {
                    $ad->workhours = $option;
                } else if ($option->id === 52) {
                    $ad->computer = (bool) $option->value;
                    $ad->equipment[] = $option->name;
                } else if ($option->id === 111) {
                    $ad->climate = (bool) $option->value;
                    $ad->equipment[] = $option->name;
                } else if ($option->id === 372) {
                    $ad->cutting_width = $option->value;
                } else if ($option->id === 243) {
                    $ad->front_wheels = (object) [
                        'name' => $option->name,
                        'type' => $option->value,
                        'quality' => (isset($lookup[365])) ? $ad->options[$lookup[365]]->value : null,
                    ];
                } else if ($option->id === 244) {
                    $ad->back_wheels = (object) [
                        'name' => $option->name,
                        'type' => $option->value,
                        'quality' => (isset($lookup[364])) ? $ad->options[$lookup[364]]->value : null,
                    ];
                } else if ($option->id === 386) {
                    $ad->cylinders = $option;
                } else if ($option->id === 284) {
                    $ad->transmission = $option;
                } else if ($option->id === 733) {
                    $ad->_3_point_hitch = $option;
                } else if ($option->id === 353) {
                    $ad->max_speed = $option;
                } else if ($option->id === 455) {
                    $ad->rim_diameter = $option;
                } else if ($option->id === 289) {
                    $ad->tyre_type = $option->show;
                } else if ($option->id === 487) {
                    $ad->available = (object) [
                        'value' => $option->value,
                        'name' => $option->name,
                    ];
                } else if ($option->id === 426) {
                    $ad->free_text = $option->value;

                    if (!$ad->power) {
                        // Try to get Power from Desc
                        preg_match_all('/Leistung:(\d+)/', $option->value, $power);
                        if (isset($power[1][0])) $ad->power = (object) [
                            'value' => $power[1][0],
                            'show' => $power[1][0] . ' kW',
                        ];
                    }

                    if (!$ad->workhours) {
                        // Try to get Hours from Desc
                        preg_match_all('/Betriebsstunden:(\d+)/', $option->value, $workhours);
                        if (isset($workhours[1][0])) $ad->workhours = (object) [
                            'value' => $workhours[1][0],
                            'show' => $workhours[1][0] . ' h',
                        ];
                    }

                    if (!$ad->front_wheels) {
                        // Try to get Wheels from Desc
                        preg_match_all('/Bereifung \(v\):(.*?),/', $option->value, $front_wheels);
                        if (isset($front_wheels[1][0])) $ad->front_wheels = (object) ['type' => $front_wheels[1][0]];
                    }
                    if (!$ad->front_wheels) {
                        preg_match_all('/Bereifung \(h\):(.*?),/', $option->value, $back_wheels);
                        if (isset($back_wheels[1][0])) $ad->back_wheels = (object) ['type' => $back_wheels[1][0]];
                    }

                    // Try to get Wheels condition from Desc
                    if ($ad->front_wheels && (!isset($ad->front_wheels->quality) || !$ad->front_wheels->quality)) {
                        preg_match_all('/Zustand-Bereifung \(v\):(\d+)/', $option->value, $front_wheels);
                        if (isset($front_wheels[1][0])) $ad->front_wheels->quality = $front_wheels[1][0];
                    }
                    if ($ad->back_wheels && (!isset($ad->front_wheels->quality) || !$ad->front_wheels->quality )) {
                        preg_match_all('/Zustand-Bereifung \(h\):(\d+)/', $option->value, $back_wheels);
                        if (isset($back_wheels[1][0])) $ad->back_wheels->quality = $back_wheels[1][0];
                    }
                } else if ( $option->type == 'bool' ) {
                    $ad->equipment[] = $option->name;
                }
            }
            if ($ad->price && $ad->price->value > 0) {
                $ad->price->values = (object) [
                    'netto' => $ad->price_decimal->value,
                    'brutto' => $ad->price_decimal->value * (($ad->vat->value) ? ($ad->vat->value + 1) : 1.19),
                ];
                if ($ad->price->specialprice === 'true') {
                    $ad->offer = true;
                    $ad->price->values->old = $ad->oldprice_decimal->value;
                } else {
                    $ad->offer = false;
                }
            }
            if ($ad->equipment) {
                asort($ad->equipment);
            }

            $query = http_build_query(array_merge($this->linkParams, [
                'articleId' => $ad->id,
                'Itemid' => $this->menuId,
            ]));
            $ad->link = JRoute::_("index.php?$query");

            if ($ad->manufacturer->id === 0) {
                $ad->manufacturer->value = JText::_( 'COM_PIXELCMS_OTHER' );
            }
        }

        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 function getDefault($var, $default)
	{
		return (isset($var)) ? $var : $default;
    }

    private static function fisherYatesShuffle(&$items, $seed)
    {
        @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;
    }

    private function prepareFilters()
    {
        $this->filters = [
            'status' => ['all', 'active'][$this->params->getInt('traktorpool_status', 0)],
            'sort' => $this->params->get('sort', 'desc'),
            'sortby' => $this->params->get('sortby', 'date'),
        ];
        if (($condition = $this->params->get('traktorpool_condition', null)) && $condition !== 'false') {
            $this->filters['condition'] = $condition;
        }

        return implode('.', array_merge(...array_map(function($value) {
            return (is_array($value)) ? $value : [$value];
        }, array_values($this->filters))));
    }

	/**
	 *
	 * @return mixed
	 *
	 * @since version
	 * @throws Exception
	 */
	private function getAds()
    {
        $parameters = $this->filters;

        if ($this->limit) {
            $parameters['per_page'] = $this->limit;
        }

        if ($this->params->get('traktorpool_status', 1)) {
            $parameters['status'] = 'active';
        }

    	$ads = $this->cURL("https://connect.traktorpool.com/offerer/$this->customer_id/detail_ads", $parameters);
    	return $this->fetchAdditionalData($ads);
    }

	private function fetchAdditionalData($ads)
	{
		foreach ($ads->ads as &$ad) {
			$ad->contact_persons = $this->getContactPersons($ad);
		}
		return $ads;
	}

	private function getContactPersons($ad)
	{
	    if (count($ad->contact_persons) === 0) {
	        return [$this->getContactPerson()];
        }
		return array_map(function($person) {
			return $this->getContactPerson($person->id);
		}, $ad->contact_persons);
	}

	/**
	 * @param $id
	 *
	 * @return mixed
	 *
	 * @since version
	 * @throws Exception
	 */
	private function getContactPerson($id = null)
	{
		if (isset($this->contact_persons[$id])) {
			return $this->contact_persons[$id];
		}

		if ($contacts = $this->cache->get("contact:$id", $this->module)) {
            return $this->contact_persons[$id] = unserialize($contacts);
        }

		if ($id === null) {
		    $contacts = $this->cURL("https://connect.traktorpool.com/offerer/$this->customer_id/contact_persons");
            $this->contact_persons[$id] = isset($contacts[0]) ? $contacts[0] : null;
        } else {
            $this->contact_persons[$id] = $this->cURL("https://connect.traktorpool.com/contact_persons/$id");
        }

		if ($this->contact_persons[$id]) {
            $this->contact_persons[$id] = new Contact($this->contact_persons[$id]);
        }

        $this->cache->store( serialize($this->contact_persons[$id]), "contact:$id", $this->module);

		return $this->contact_persons[$id];
	}

    private function checkCache($force = false)
    {
        $cacheKey = $this->language . $this->prepareFilters() . "limit:$this->limit";
	    $ads = $this->cache->get($cacheKey, $this->module);

	    if (empty($ads) || $force) {
		    $ads = $this->getAds();
		    $this->cache->store( serialize($ads), $cacheKey, $this->module);
	    } else {
		    $ads = unserialize($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 = [])
	{
		$parameters = array_merge([
			'api_key'=> $this->api_key,
			'language' => substr($this->language, 0, 2),
		], $parameters);

		$query = http_build_query($parameters);

		$options = [
			CURLOPT_URL => "$url?$query",
			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_USERPWD        => $this->username . ":" . $this->password,
			CURLOPT_HTTPAUTH       => CURLAUTH_DIGEST,
			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 json_decode($body);
	}

    public function getData()
    {
        if ($this->articleId === 'rand') {
            return $this->ads[array_rand($this->ads)];
        }
        if ($this->articleId) {
            return array_reduce($this->ads, function ($carry, $article) {
                if ($article->id === (int) $this->articleId) {
                    $carry = $article;
                }
                return $carry;
            });
        }
        return $this->ads;
    }
}
