<?php
    /**
     * Class: AtomFeed
     * Generates an Atom feed piece by piece.
     *
     * See Also:
     *     https://tools.ietf.org/html/rfc4287
     */
    class AtomFeed implements FeedGenerator {
        # Boolean: $open
        # Has the feed been opened?
        protected $open = false;

        # Variable: $count
        # The number of entries generated.
        protected $count = 0;

        # Array: $xml
        # Holds the feed as an array.
        protected $xml = array();

        /**
         * Function: type
         * Returns the content type of the feed.
         */
        public static function type(): string {
            return "application/atom+xml";
        }

        /**
         * Function: open
         * Adds the opening feed element and top-level elements.
         *
         * Parameters:
         *     $title - Title for this feed.
         *     $subtitle - Subtitle (optional).
         *     $id - Feed ID (optional).
         *     $updated - Time of update (optional).
         */
        public function open(
            $title,
            $subtitle = "",
            $id = "",
            $updated = null
        ): bool {
            if ($this->open)
                return false;

            $this->open = true;

            $language = lang_base(Config::current()->locale);

            $feed = '<?xml version="1.0" encoding="UTF-8"?>'."\n";

            $feed.= '<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="'.
                    fix($language, true).'">'.
                    "\n";

            $feed.= '<title>'.fix($title).'</title>'."\n";

            if (!empty($subtitle))
                $feed.= '<subtitle>'.fix($subtitle).'</subtitle>'."\n";

            $feed.= '<id>'.fix(oneof($id, self_url())).'</id>'."\n";

            $feed.= '<updated>'.
                    when(DATE_ATOM, oneof($updated, time())).
                    '</updated>'.
                    "\n";

            $feed.= '<link href="'.
                    self_url().
                    '" rel="self" type="application/atom+xml" />'.
                    "\n";

            $feed.= '<generator uri="http://chyrplite.net/" version="'.
                    CHYRP_VERSION.
                    '">'.
                    CHYRP_IDENTITY.
                    '</generator>'.
                    "\n";

            $this->xml = array(
                "feed" => $feed,
                "items" => array()
            );

            return $this->open = true;
        }

        /**
         * Function: entry
         * Adds an individual feed entry.
         *
         * Parameters:
         *     $title - Title for this entry.
         *     $id - The unique ID.
         *     $content - Content for this entry.
         *     $link - The URL to the resource.
         *     $published - Time of creation.
         *     $updated - Time of update (optional).
         *     $name - Name of the author (optional).
         *     $uri - URI of the author (optional).
         *     $email - Email address of the author (optional).
         *
         * Notes:
         *     The entry remains open to allow triggered insertions.
         */
        public function entry(
            $title,
            $id,
            $content,
            $link,
            $published,
            $updated = null,
            $name = "",
            $uri = "",
            $email = ""
        ): bool {
            if (!$this->open)
                return false;

            $this->count++;

            $entry = '<title type="html">'.
                     fix($title, false, true).
                     '</title>'.
                     "\n";

            $entry.= '<id>'.fix($id).'</id>'."\n";

            $entry.= '<updated>'.
                     when(DATE_ATOM, oneof($updated, $published)).
                     '</updated>'.
                     "\n";

            $entry.= '<published>'.
                     when(DATE_ATOM, $published).
                     '</published>'.
                     "\n";

            $entry.= '<link rel="alternate" type="text/html" href="'.
                     fix($link, true).
                     '" />'.
                     "\n";

            $entry.= '<author>'."\n";

            $entry.= '<name>'.
                     fix(oneof($name, __("Guest"))).
                     '</name>'.
                     "\n";

            if (!empty($uri) and is_url($uri))
                $entry.= '<uri>'.fix($uri).'</uri>'."\n";

            if (!empty($email) and is_email($email))
                $entry.= '<email>'.fix($email).'</email>'."\n";

            $entry.= '</author>'."\n";

            $entry.= '<content type="html">'.
                     fix($content, false, true).
                     '</content>'.
                     "\n";

            $item = $this->count - 1;
            $this->xml["items"][$item] = $entry;
            return true;
        }

        /**
         * Function: category
         * Adds a category element for an entry or feed.
         *
         * Parameters:
         *     $term - String that identifies the category.
         *     $scheme - URI for the categorization scheme (optional).
         *     $label - Human-readable label for the category (optional).
         */
        public function category(
            $term,
            $scheme = "",
            $label = ""
        ): bool {
            if (!$this->open)
                return false;

            $category = '<category term="'.
                        fix($term, true).
                        '"';

            if (!empty($scheme))
                $category.= ' scheme="'.fix($scheme, true).'"';

            if (!empty($label))
                $category.= ' label="'.fix($label, true).'"';

            $category.= ' />'."\n";

            if (!$this->count) {
                $this->xml["feed"].= $category;
            } else {
                $item = $this->count - 1;
                $this->xml["items"][$item].= $category;
            }

            return true;
        }

        /**
         * Function: rights
         * Adds a rights element for an entry or feed.
         *
         * Parameters:
         *     $text - Human-readable licensing information.
         */
        public function rights(
            $text
        ): bool {
            if (!$this->open)
                return false;

            $rights = '<rights>'.
                      fix($text, false, true).
                      '</rights>'.
                      "\n";

            if (!$this->count) {
                $this->xml["feed"].= $rights;
            } else {
                $item = $this->count - 1;
                $this->xml["items"][$item].= $rights;
            }

            return true;
        }

        /**
         * Function: enclosure
         * Adds a link element for a resource that is potentially large in size.
         *
         * Parameters:
         *     $link - The URL to the resource.
         *     $length - Size in bytes of the resource (optional).
         *     $type - The media type of the resource (optional).
         *     $title - Title for the resource (optional).
         */
        public function enclosure(
            $link,
            $length = null,
            $type = "",
            $title = ""
        ): bool {
            if (!$this->open)
                return false;

            $enclosure = '<link rel="enclosure" href="'.
                         fix($link, true).
                         '"';

            if (!empty($length))
                $enclosure.= ' length="'.fix($length, true).'"';

            if (!empty($type))
                $enclosure.= ' type="'.fix($type, true).'"';

            if (!empty($title))
                $enclosure.= ' title="'.fix($title, true).'"';

            $enclosure.= ' />'."\n";

            if (!$this->count) {
                $this->xml["feed"].= $enclosure;
            } else {
                $item = $this->count - 1;
                $this->xml["items"][$item].= $enclosure;
            }

            return true;
        }

        /**
         * Function: related
         * Adds a link element for a resource related to an entry or feed.
         *
         * Parameters:
         *     $link - The URL to the resource.
         */
        public function related(
            $link
        ): bool {
            if (!$this->open)
                return false;

            if (empty($link) or !is_url($link))
                return false;

            $related = '<link rel="related" href="'.
                       fix($link, true).
                       '" />'.
                       "\n";

            if (!$this->count) {
                $this->xml["feed"].= $related;
            } else {
                $item = $this->count - 1;
                $this->xml["items"][$item].= $related;
            }

            return true;
        }

        /**
         * Function: feed
         * Returns the generated feed.
         */
        public function feed(): string {
            $feed = $this->xml["feed"];
            $items = $this->xml["items"];

            foreach ($items as $item) {
                $feed.= '<entry>'."\n".
                        $item.
                        '</entry>'."\n";
            }

            $feed.= '</feed>'."\n";
            return $feed;
        }

        /**
         * Function: output
         * Displays the generated feed.
         */
        public function display(): bool {
            if (headers_sent())
                return false;

            header("Content-Type: ".self::type()."; charset=UTF-8");
            echo $this->feed();
            return true;
        }
    }