<?php

namespace Mtc\Core;

use Illuminate\Support\Facades\DB;

/**
 * UK Postcode class.
 * What it can do:
 * - Normalize postcodes - bring 'dd13ja' to 'DD1 3JA'
 * - Parse potcodes into parts - inward code, outward, area etc.
 * - Validate the postcode agains the RegEx
 *
 * Example postcode used in below examples: DD1 3JA
 *
 * Description of the UK Postcode format
 * https://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom#Formatting
 *
 * @author  Aleksey Lavrinenko
 * @version 25.02.2016.
 */
class UkPostcode implements Postcode
{

    /**
     * Normalizes postcode format to uppercase with space.
     * Brings dd13ja to DD1 3JA
     *
     * @param $postcode_string
     *
     * @return string
     */
    public function normalize($postcode_string)
    {
        $postcode_string = strtoupper($postcode_string);

        // adjust postcode with no spaces
        if (strpos($postcode_string, ' ') === false) {
            $postcode_string = preg_replace('#(.{3})$#', ' $1', $postcode_string);
        }

        return $postcode_string;
    }

    /**
     * Checks if a string validates as a UK postcode. The Regex was taken from the Royal Mail website with two
     * adjustments:
     * - space in the middle of postcode has been made unmandatory
     * - added parentheses around the outward core part of regex, otherwise the 'start of string' char didn't work
     * (due to it being treated as optional by the or | bit)
     *
     * @param string $postcode
     *
     * @return int
     */
    public function validates($postcode = '')
    {
        /*
         * This was added because some functions from other modules attempt to
         * use this function statically (As it used to be static) and so it
         * would need to declare $this for it to work in that case
         */
        if (empty($postcode) && (!(isset($this) && get_class($this) == __CLASS__)) || (isset($this))) {
            $postcode = $this->__toString();
        }

        return preg_match(
            '/^(([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))) ?[0-9][A-Za-z]{2})$/',
            $postcode
        );

    }

    /**
     * Connect to PAF and retrieve address returned as a concatenated string.
     * Known default usages in member registration & checkout basket if PAF_ENABLED.
     *
     * @param string $postcode Postcode to find
     *
     * @return array list of addresses found
     * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
     */
    public static function retrievePAFAddresses(string $postcode): array
    {
        return DB::connection('paf')
            ->table('Address_Complete')
            ->select([
                'Organisation Name',
                'Building Number',
                'Building Name',
                'Sub Building Name',
                'Thoroughfare Name',
                'Thoroughfare Descriptor',
                'Dependent Locality',
                'Post Town',
                'Department Name',
                'PO Box Number',
                'Dependent Thoroughfare Name',
                'Dependent Thoroughfare Descriptor',
            ])
            ->where('Postcode', $postcode)
            ->orderBy('PO Box Number', 'ASC')
            ->orderBy('Department Name', 'ASC')
            ->orderBy('Organisation Name', 'ASC')
            ->orderBy('Building Number', 'ASC')
            ->orderBy('Building Name', 'ASC')
            ->orderBy('Sub Building Name', 'ASC')
            ->orderBy('Address Key', 'ASC')
            ->get()
            ->map(function ($pafAddress) {
                $data = (array)$pafAddress;

                $organisation_name                 = $data['Organisation Name'];
                $department_name                   = $data['Department Name'];
                $po_box                            = $data['PO Box Number'];
                $building_number                   = $data['Building Number'];
                $building_name                     = $data['Building Name'];
                $sub_building_name                 = $data['Sub Building Name'];
                $thoroughfare_name                 = $data['Thoroughfare Name'];
                $thoroughfare_descriptor           = $data['Thoroughfare Descriptor'];
                $dependent_locality                = $data['Dependent Locality'];
                $dependent_thoroughfare_name       = $data['Dependent Thoroughfare Name'];
                $dependent_thoroughfare_descriptor = $data['Dependent Thoroughfare Descriptor'];
                $post_town                         = $data['Post Town'];

                $address = [];
                $address['display_name'] = '';

                if (!empty($department_name)) {
                    $address['display_name'] .= $department_name . " ";
                    $address['department_name'] = $department_name;
                }
                if (!empty($organisation_name)) {
                    $address['display_name'] .= $organisation_name . " ";

                    $address['organisation_name'] = $organisation_name;
                }
                if (!empty($department_name) || !empty($po_box)) {
                    $address['display_name'] .= 'POBox ' . $po_box . " ";

                    $address['po_box'] = $po_box;
                }
                if (!empty($organisation_name) || !empty($po_box)) {
                    $address['display_name'] = rtrim($address['display_name']) . '| ';
                }
                if (!empty($sub_building_name)) {
                    $address['display_name'] .= $sub_building_name . " ";

                    $address['sub_building_name'] = $sub_building_name;
                }
                if (!empty($building_name)) {
                    $address['display_name'] .= $building_name . " ";

                    $address['building_name'] = $building_name;
                }

                /*
                 * If at least one of the params for first section (address1) of the address is set
                 * replace the ending whitespace with comma and whitespace
                 */
                if (!empty($department_name)
                    || !empty($organisation_name)
                    || !empty($po_box)
                    || !empty($sub_building_name)
                    || !empty($building_name)
                ) {
                    $address['display_name'] = rtrim($address['display_name']) . ", ";
                }

                if ($building_number != '' && $building_number != 0) {
                    $address['display_name'] .= $building_number . " ";

                    $address['building_number'] = $building_number;
                }
                if (!empty($thoroughfare_name)) {
                    $address['display_name'] .= $thoroughfare_name . " ";

                    $address['thoroughfare_name'] = $thoroughfare_name;
                }
                if (!empty($thoroughfare_descriptor)) {
                    $address['display_name'] .= $thoroughfare_descriptor . "| ";

                    $address['thoroughfare_descriptor'] = $thoroughfare_descriptor;
                }
                if (!empty($dependent_thoroughfare_name)) {
                    $address['display_name'] .= $dependent_thoroughfare_name . " ";

                    $address['dependent_thoroughfare_name'] = $dependent_thoroughfare_name;
                }
                if (!empty($dependent_thoroughfare_descriptor)) {
                    $address['display_name'] .= $dependent_thoroughfare_descriptor . " ";

                    $address['dependent_thoroughfare_descriptor'] = $dependent_thoroughfare_descriptor;
                }
                if (!empty($thoroughfare_descriptor)
                    && empty($dependent_thoroughfare_name)
                    && empty($dependent_thoroughfare_descriptor)
                ) {
                    $address['display_name'] = rtrim($address['display_name']);
                }

                /*
                 * If at least one of the params for second section (address2) of the address is set
                 * replace the ending whitespace with comma and whitespace
                 */
                if (!empty($thoroughfare_name)
                    || !empty($thoroughfare_descriptor)
                    || !empty($dependent_thoroughfare_name)
                    || !empty($dependent_thoroughfare_descriptor)
                ) {
                    $address['display_name'] = substr($address['display_name'], 0, -1) . ", ";
                }

                if (!empty($dependent_locality)) {
                    $address['display_name'] .= $dependent_locality . " ";

                    $address['dependent_locality'] = $dependent_locality;
                }
                if (!empty($dependent_locality)) {
                    $address['display_name'] = rtrim($address['display_name']) . ", ";
                }

                if (!empty($post_town)) {
                    $address['display_name'] .= $post_town;

                    $address['post_town'] = $post_town;
                }

                foreach($address as $key => $name) {
                    $address[$key] = ucwords(strtolower($name));
                }
                return $address;
            })
            ->toArray();
    }

    /**
     * The 'DD1' part of 'DD1 3JA' postcode
     *
     * @var string
     */
    protected $outward_code;

    /**
     * The '3JA' part of 'DD1 3JA' postcode
     *
     * @var string
     */
    protected $inward_code;

    /**
     * The 'DD' part of 'DD1 3JA' postcode
     *
     * @var string
     */
    protected $area;

    /**
     * The postcode district is the outward code. In our case this is just the number - '1' of 'DD1'
     * of 'DD1 3JA' postcode
     *
     * @var string
     */
    protected $district;

    /**
     * The '3' part
     *
     * @var string
     */
    protected $sector;

    /**
     * The 'JA' part of 'DD1 3JA' postcode
     *
     * @var string
     */
    protected $unit;

    /**
     * @return string
     */
    public function getOutwardCode()
    {
        return $this->outward_code;
    }

    /**
     * @return string
     */
    public function getInwardCode()
    {
        return $this->inward_code;
    }

    /**
     * @return string
     */
    public function getArea()
    {
        return $this->area;
    }

    /**
     * @param bool $full If true, will return the full district including the area code.
     *
     * @return string
     */
    public function getDistrict($full = false)
    {
        return $full ? ($this->area . $this->district) : $this->district;
    }

    /**
     * @param bool $full If true, will return the full district including the area code and district.
     *
     * @return string
     */
    public function getSector($full = false)
    {
        return $full ? ($this->getDistrict(true) . ' ' . $this->sector) : $this->sector;
    }

    /**
     * @return string
     */
    public function getUnit()
    {
        return $this->unit;
    }

    /**
     * UkPostcode constructor.
     *
     * @param string $postcode_string Postcode as string
     */
    public function __construct($postcode_string)
    {
        $postcode_string = self::normalize($postcode_string);

        list($this->outward_code, $this->inward_code) = explode(' ', $postcode_string);

        preg_match('#^([A-Z]+)(\d+[A-Z]?) (\d)([A-Z]+)$#', $postcode_string, $m);

        list(, $this->area, $this->district, $this->sector, $this->unit) = $m;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->outward_code . ' ' . $this->inward_code;
    }

    /**
     * Finds postcode coordinates by postcode
     *
     * @param $postcode
     * @return array|object
     * @throws \Exception
     */
    public static function retrievePostcodeCoordinates($postcode)
    {
        $location = DB::connection('paf')
            ->table('postcode_geo')
            ->where('postcode', $postcode)
            ->first();
        if (empty($location)) {
            return [];
        }
        return (array)$location;
    }
}
