<?php

namespace App\Helpers;

use DateTime;

class TimeLine {

    public $start = null;
    public $end = null;
    public $available = [];
    public $unavailable = [];

    public $removeStart = null;
    public $removeEnd = null;

    public function __construct(DateTime $_start, DateTime $_end)
    {
        $this->start = $_start->getTimestamp();
        $this->end = $_end->getTimestamp();
    }

    public function addAvailability(DateTime $_start, DateTime $_end) {

        $this->available[] = new TimeSlot($_start, $_end);

        // sort by start
        usort($this->available, function ($item1, $item2) {
            return $item1->start <=> $item2->start;
        });

        // compact (join adjacent overlapping slots)
        // $this->vdump($this->available);
        $this->normalize();
        $this->vdump($this->available);
    }

    public function removeAvailability(DateTime $_start, DateTime $_end) {

        $removeStart = $_start->getTimestamp();
        $removeEnd = $_end->getTimestamp();

        $cleaned = $this->removeEmbeddedInAvailSlot($removeStart, $removeEnd);
        if(!$cleaned) {
            for ($i=0; $i<count($this->available); $i++) $this->available[$i]->processed = false;
            $this->removeStart($removeStart, $removeEnd);
            for ($i=0; $i<count($this->available); $i++) $this->available[$i]->processed = false;
            $this->removeEnd($removeStart, $removeEnd);
            $this->removeEmbeddedInRemoveSlot($removeStart, $removeEnd);
        }

        // sort by start
        usort($this->available, function ($item1, $item2) {
            return $item1->start <=> $item2->start;
        });
        $this->normalize();
        $this->vdump($this->available);

        // add to unavailable
        $this->unavailable[] = new TimeSlot($_start, $_end);
        // $this->normalizeUnavailable();
    }

    private function removeEmbeddedInAvailSlot($removeStart, $removeEnd) {
        for ($i=0; $i<count($this->available); $i++) {

            // removeStart at or after slot start
            // removeEnd at or before slot end
            // split slot into 2
            if ($removeStart >= $this->available[$i]->start &&
                $removeEnd <= $this->available[$i]->end) {
                $newSlot = new TimeSlot(new DateTime(), new DateTime());
                $newSlot->start = $removeEnd;
                $newSlot->end = $this->available[$i]->end;

                $this->available[$i]->end = $removeStart;
                array_splice($this->available, $i + 1, 0, [$newSlot]);
                return true; // nothing more to do
            }
        }

        return false;
    }

    private function removeEmbeddedInRemoveSlot($removeStart, $removeEnd) {

        $allDone = true;

        for ($i=0; $i<count($this->available); $i++) {

            // removeStart at or before slot start
            // removeEnd at or after slot end
            // remove slot
            if ($removeStart <= $this->available[$i]->start &&
                $removeEnd >= $this->available[$i]->end) {
                array_splice($this->available, $i, 1);
                $allDone = false;
            }
        }

        if(!$allDone) {
            $this->removeEmbeddedInRemoveSlot($removeStart, $removeEnd); // recurse till clean
        }

        return false;
    }

    private function removeStart($removeStart, $removeEnd) {

        $allDone = true;

        for ($i=0; $i<count($this->available); $i++) {

            if($this->available[$i]->processed) continue;

            // removeStart at or after slot start
            // removeStart at or before slot end
            // update slot to end at removeStart
            if($removeStart >= $this->available[$i]->start &&
                $removeStart <= $this->available[$i]->end) {
                $this->available[$i]->end = $removeStart;
                $this->available[$i]->processed = true;
                $allDone = false;
                break;
            }

        }

        if(!$allDone) {
            $this->removeStart($removeStart, $removeEnd); // recurse till clean
        }

    }

    private function removeEnd($removeStart, $removeEnd) {

        $allDone = true;

        for ($i=0; $i<count($this->available); $i++) {

            if($this->available[$i]->processed) continue;

            // removeEnd at or after slot start
            // removeEnd at or before slot end
            // update slot to start at removeEnd
            if($removeEnd >= $this->available[$i]->start &&
                $removeEnd <= $this->available[$i]->end) {
                $this->available[$i]->start = $removeEnd;
                $this->available[$i]->processed = true;
                $allDone = false;
                break;
            }

        }

        if(!$allDone) {
            $this->removeEnd($removeStart, $removeEnd); // recurse till clean
        }

    }

    private function normalize() {

        $allDone = true;

        for ($i=0; $i<count($this->available)-1; $i++) {
            if($this->available[$i]->end >= $this->available[$i+1]->start && // ends in the middle of next slot
                $this->available[$i]->end <= $this->available[$i+1]->end)
            {
                // extend self & delete next
                $this->available[$i]->end = $this->available[$i+1]->end;
                array_splice($this->available, $i+1, 1);
                $allDone = false;
                break;
            }

            if($this->available[$i]->end >= $this->available[$i+1]->end) // ends after next slot end
            {
                // delete next
                array_splice($this->available, $i+1, 1);
                $allDone = false;
                break;
            }

            if($this->available[$i]->start >= $this->available[$i]->end) // starts and ends at the same time!
            {
                // delete self
                array_splice($this->available, $i, 1);
                $allDone = false;
                break;
            }
        }

        if(!$allDone) {
            $this->normalize(); // recurse till clean
        }

    }

    private function normalizeUnavailable() {

        $allDone = true;

        for ($i=0; $i<count($this->unavailable)-1; $i++) {
            if($this->unavailable[$i]->end >= $this->unavailable[$i+1]->start && // ends in the middle of next slot
                $this->unavailable[$i]->end <= $this->unavailable[$i+1]->end)
            {
                // extend self & delete next
                $this->unavailable[$i]->end = $this->unavailable[$i+1]->end;
                array_splice($this->unavailable, $i+1, 1);
                $allDone = false;
                break;
            }

            if($this->unavailable[$i]->end >= $this->unavailable[$i+1]->end) // ends after next slot end
            {
                // delete next
                array_splice($this->unavailable, $i+1, 1);
                $allDone = false;
                break;
            }

            if($this->unavailable[$i]->start >= $this->unavailable[$i]->end) // starts and ends at the same time!
            {
                // delete self
                array_splice($this->unavailable, $i, 1);
                $allDone = false;
                break;
            }
        }

        if(!$allDone) {
            $this->normalizeUnavailable(); // recurse till clean
        }

    }

    public function getAvailable() {
        return $this->available;
    }

    public function getUnavailable() {
        return $this->unavailable;
    }

    public function vdump($_x) {
//        echo "<pre>";
//        print_r($_x);
//        echo "</pre><hr>";
    }

}
