<?php

require_once("pixel.php");

Class XML_Node {
    private $properties = array();
    private $children = array();
    private $name;
    private $close;
    // server with php 5.3.0 has no '...' token
    //public function __construct($name = "kisses", $close = true, ... $properties) {
    public function __construct($name = "kisses", $close = true, $properties = array()) {
        //$this->add(... $properties);
        $this->add($properties);
        $this->name = $name;
        $this->close = $close;
    }
    public function append(XML_Node $child) { $this->children[] = $child; }
    //public function add(... $properties) {
    public function add($properties) {
        foreach($properties as $arr) $this->properties[] = $arr;
    }
    /*  convert properties to a string, allow "late-binding", or when one wants
     *  to set a property after initially setting it to another value, a good
     *  example of this is a programmer "building" a wall and not knowing
     *  beforehand what the final size of the wall is, part of the program
     *  might calculate how tall something is based on how many bricks the
     *  programmer used.  This is sub-optimal, but at the moment I am not sure
     *  how many bricks are needed, so unless someone can say this glut is going
     *  to stay.
     *
     *  -- better code would just build this string once rather than every
     *     time the Class is coerced
     */
    private function propertyStr() {
        $str = array();
        foreach($this->properties as $arr) {
            // convert properties with values into HTML
            if(is_array($arr)) {
                // preserve native PHP datatype quotes, this way "1.0" is a string
                if("string" === gettype($arr[1]) || is_object($arr[1])) {
                    $arr[1] = '"' . htmlentities($arr[1]) . '"';
                } else if(is_object($arr[1])) {}
                $arr = "{$arr[0]}={$arr[1]}";
            } else { /* just leave property alone */ }
            $str[] = $arr;
        }
        return (0<count($str) ?' ' :'') . implode(' ', $str);
    }
    public function __tostring() {
        $arr = array("<{$this->name}{$this->propertyStr()}>");
        foreach($this->children as $v) $arr[] = "\t" . strval($v);
        // either close comes at the end or is just a single node with terminating '/>'
        if($this->close) $arr[] = "</{$this->name}>";
        else $arr[0] = substr($arr[0], 0, -1) . " />";
        return implode(PHP_EOL, $arr);
    }
    static public function stanza($version = "1.0") {
        $s = new XML_Node("?xml", false, array(
            array("version", $version),
            array("standalone", "no")
        ));
        // XML requires '?' instead of '/' in '/>' tag terminator
        return substr($s, 0, -2) . '?>';
    }
}

Class Printable {
    public $value;
    public function __construct($v = 0) { $this->value = $v; }
    public function __tostring() { return strval($this->value); }
}
Class Coordinate {
    public $x;
    public $y;
    public function __construct($x = 0, $y = 0) {
        $this->x = $x; 
        $this->y = $y; 
    }
}
Class Coord extends Coordinate { /* alias */ }

Class Paint {
    public $xml = array();
    private $svg;
    private $height;
    private $width;

    public function __construct() {
        $this->xml[] = XML_Node::stanza();
        // make references of the height and width properties for the SVG node
        $this->height = new Printable();
        $this->width = new Printable();
        $this->svg = new XML_Node("svg", true, array(
            // height and width as references
            array("width",  /* needs to be ref */ $this->width),
            array("height", /* needs to be ref */ $this->height),
            array("version", "1.1"),
            array("xmlns", "http://www.w3.org/2000/svg"),
        ));
        // store a reference to the SVG, reference updates are tracked
        $this->xml[] = $this->svg;
    }
    private function expandCanvas($h = 0, $w = 0) {
        $this->height->value = max($this->height->value, $h);
        $this->width->value  = max($this->width->value, $w);
    }
    public function addCircle(Coord $origin = NULL, Pixel $fill = NULL, $radius = 20) {
        if(NULL === $origin) $origin = new Coord();
        if(NULL === $fill)   $fill   = new Pixel(/*red*/ 255,0,0, 0.5);
        $y = $x = $radius;
        $circle = new XML_Node("circle", false, array(
            array("cx", strval($x + $origin->x)),
            array("cy", strval($y + $origin->y)),
            array("r", strval($radius)),
            array("fill", "#" . strval($fill))
        ));
        $this->svg->append($circle);
        // height needs to be radius * 2, assuming radius includes stroke
        $this->expandCanvas(
            /*height*/ $origin->y + ($y * 2),
            /*width*/  $origin->x + ($x * 2)
        );
    }
    public function __tostring() {
        $s = "";
        foreach($this->xml as $n) $s .= strval($n) . PHP_EOL;
        return $s;
    }
}
/*
header("Content-Type: application: xhtml+xml");
$test = new Paint();
$test->addCircle(new Coord(0,0));
$test->addCircle(new Coord(40,0), new Pixel(255,255,0));
echo $test;
 */
