<?php

/* * ********************************************************************
 * ProxmoxAddon product developed. (Aug 23, 2018)
 * *
 *
 *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
 *  CONTACT                        ->       contact@modulesgarden.com
 *
 *
 * This software is furnished under a license and may be used and copied
 * only  in  accordance  with  the  terms  of such  license and with the
 * inclusion of the above copyright notice.  This software  or any other
 * copies thereof may not be provided or otherwise made available to any
 * other person.  No title to and  ownership of the  software is  hereby
 * transferred.
 *
 *
 * ******************************************************************** */

namespace ModulesGarden\ProxmoxAddon\App\UI\IpManagement\Providers;

use ModulesGarden\ProxmoxAddon as main;
use ModulesGarden\ProxmoxAddon\App\Libs\BigInteger;
use ModulesGarden\ProxmoxAddon\Core\UI\Interfaces\AdminArea;
use ModulesGarden\ProxmoxAddon\Core\UI\ResponseTemplates\HtmlDataJsonResponse;
use ModulesGarden\ProxmoxAddon\Core\UI\Widget\Forms\DataProviders\BaseModelDataProvider;
use function ModulesGarden\ProxmoxAddon\App\Libs\inet_itop;
use function ModulesGarden\ProxmoxAddon\App\Libs\inet_ptoi;
use function ModulesGarden\ProxmoxAddon\App\Libs\ipv6_range;
use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;

/**
 *
 * Description of RangeVmProvider
 *
 * @author Pawel Kopec <pawelk@modulesgardne.com>
 */
class IpAddressProvider extends BaseModelDataProvider implements AdminArea
{

    public function __construct()
    {
        parent::__construct(main\App\Models\IpAddress::class);
    }

    public function read()
    {
        if (!$this->actionElementId)
        {
            return false;
        }
        $dbData = $this->model->where('id', $this->actionElementId)->first();
        if ($dbData === null)
        {
            return;
        }
        $data = $dbData->toArray();
        //Node
        $this->data['node']          = [];
        $this->data['node']['value'] = $data['node'];
        unset($data['node']);
        //sid
        $this->data['sid']          = [];
        $this->data['sid']['value'] = $data['sid'];
        unset($data['sid']);
        //visualization
        $this->data['visualization']          = [];
        $this->data['visualization']['value'] = $data['visualization'];
        unset($data['visualization']);
        //private
        $this->data['private'] = $data['private'] == "1" ? "on" : "off";
        unset($data['private']);
        $this->data = array_merge($this->data, $data);
        sl('lang')->addReplacementConstant('ip', $this->data['ip']);
    }

    public function create()
    {

        try
        {
            $form = $this->getFormDataValues();
            if (preg_match('/\:/', $this->formData['ipPool']))
            {
                $pool = $this->createIpv6Pool($this->formData['ipPool'], $this->formData['cidr']);
                $type = 'IPv6';
            }
            else
            {
                $pool = $this->createIpv4Pool($this->formData['ipPool'], $this->formData['cidr']);
                $type = 'IPv4';
            }
            foreach ($pool as $ip)
            {
                if (main\App\Models\IpAddress::where("ip", $ip)->count())
                {
                    continue;
                }
                $attributes              = $this->formData;
                $attributes['ip']        = $ip;
                $attributes['private']   = $this->formData['private'] == "on" ? "1" : "0";
                $attributes['trunks']    = (int)$this->formData['trunks'] > 0 ? $this->formData['trunks'] : null;
                $attributes['tag']       = (int)$this->formData['tag'] > 0 ? $this->formData['tag'] : null;
                $attributes['type']      = $type;
                $attributes['hostingId'] = 0;
                $ipAddress               = new main\App\Models\IpAddress();
                $ipAddress->fill($attributes)->save();
                unset($ipAddress);
            }
            main\Core\ServiceLocator::call('lang')->addReplacementConstant("ipPool", $this->formData['ipPool'])
                ->addReplacementConstant("cidr", $this->formData['cidr']);
            return (new HtmlDataJsonResponse())->setMessageAndTranslate('Pool address :ipPool:/:cidr: has been added');
        }
        catch (\Exception $ex)
        {
            return (new HtmlDataJsonResponse())->setStatusError()->setMessageAndTranslate($ex->getMessage());
        }
    }

    private function createIpv6Pool($ipPool, $mask)
    {
        $prefix = implode('/', [$ipPool, $mask]);
        $range  = ipv6_range($prefix);
        $start  = inet_ptoi($range[1]);

        $end  = inet_ptoi($range[2]);
        $a    = new BigInteger($start);
        $b    = new BigInteger($end);
        $diff = $b->subtract($a)->toString();

        $pool = [];
        for ($i = 0; $i < ($diff + 1); $i++)
        {
            $c       = new BigInteger($i);
            $iptoint = $a->add($c)->toString();
            $pool[]  = inet_itop($iptoint);
        }
        return $pool;
    }

    private function createIpv4Pool($ipPool, $mask)
    {
        $pool   = [];
        $ip_enc = ip2long($ipPool);
        //convert last (32-$mask) bits to zeroes
        $curr_ip         = $ip_enc | pow(2, (32 - $mask)) - pow(2, (32 - $mask));
        $ips             = [];
        $ip_nmask        = self::translateBitmaskToNetmask((int)$mask);
        $ip_address_long = $ip_enc;
        $ip_nmask_long   = ip2long($ip_nmask);
        //caculate network address
        $ip_net = $ip_address_long & $ip_nmask_long;
        //caculate first usable address
        $ip_host_first = ((~$ip_nmask_long) & $ip_address_long);
        $ip_first      = ($ip_address_long ^ $ip_host_first) + 1;
        //caculate last usable address
        $ip_broadcast_invert = ~$ip_nmask_long;
        $ip_last             = ($ip_address_long | $ip_broadcast_invert) - 1;
        //caculate broadcast address
        $ip_last       = ($ip_address_long | $ip_broadcast_invert) - 1;
        $ip_last_short = long2ip($ip_last);
        $totalHost     = (float)pow(2, (32 - $mask));
        if ($totalHost > 10000)
        {
            throw new \Exception(sprintf("Subnet %s/%s is too large. You are tring to add %s addresses.", $ipPool, $mask, $totalHost));
        }
        for ($pos = 0; $pos < pow(2, (32 - $mask)); ++$pos)
        {
            $ip     = long2ip((float)$curr_ip + $pos);
            $pool[] = $ip;
            if ($ip == $ip_last_short)
            {
                break;
            }
        }
        return $pool;
    }

    public static function translateBitmaskToNetmask($bitmask)
    {
        $maskMap = [
            0  => "0.0.0.0",
            1  => "128.0.0.0",
            2  => "192.0.0.0",
            3  => "224.0.0.0",
            4  => "240.0.0.0",
            5  => "248.0.0.0",
            6  => "252.0.0.0",
            7  => "254.0.0.0",
            8  => "255.0.0.0",
            9  => "255.128.0.0",
            10 => "255.192.0.0",
            11 => "255.224.0.0",
            12 => "255.240.0.0",
            13 => "255.248.0.0",
            14 => "255.252.0.0",
            15 => "255.254.0.0",
            16 => "255.255.0.0",
            17 => "255.255.128.0",
            18 => "255.255.192.0",
            19 => "255.255.224.0",
            20 => "255.255.240.0",
            21 => "255.255.248.0",
            22 => "255.255.252.0",
            23 => "255.255.254.0",
            24 => "255.255.255.0",
            25 => "255.255.255.128",
            26 => "255.255.255.192",
            27 => "255.255.255.224",
            28 => "255.255.255.240",
            29 => "255.255.255.248",
            30 => "255.255.255.252",
            31 => "255.255.255.254",
            32 => "255.255.255.255"
        ];

        return isset($maskMap[$bitmask]) ? $maskMap[$bitmask] : $bitmask;
    }

    public function update()
    {
        $form                      = $this->getFormDataValues();
        $this->formData['private'] = $form['private'] == "on" ? "1" : "0";
        $this->formData['trunks']  = (int)$form['trunks'] > 0 ? $form['trunks'] : null;
        $this->formData['tag']     = (int)$form['tag'] > 0 ? $form['tag'] : null;
        parent::update();
        main\Core\ServiceLocator::call('lang')->addReplacementConstant('ip', $this->formData['ip']);
        return (new HtmlDataJsonResponse())->setMessageAndTranslate('IP Address :ip: has been updated successfully');
    }

    public function delete()
    {
        parent::delete();
        main\Core\ServiceLocator::call('lang')->addReplacementConstant('ip', $this->formData['ip']);
        return (new HtmlDataJsonResponse())->setMessageAndTranslate('IP Address :ip: has been deleted successfully');
    }

    public function deleteMass()
    {
        if (!$this->getRequestValue('massActions'))
        {
            return;
        }
        $this->model->destroy((array)$this->getRequestValue('massActions'));
        return (new HtmlDataJsonResponse())->setMessageAndTranslate('The selected IP Addresses have been deleted successfully');
    }
}
