<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace Zend\Console\Adapter;

use 
Zend\Console\Charset;
use 
Zend\Console\Exception;
use 
Zend\Stdlib\StringUtils;

/**
 * Common console adapter codebase
 */
abstract class AbstractAdapter implements AdapterInterface
{
    
/**
     * Whether or not mbstring is enabled
     *
     * @var null|bool
     */
    
protected static $hasMBString;

    
/**
     * @var Charset\CharsetInterface
     */
    
protected $charset;

    
/**
     * Current cursor X position
     *
     * @var int
     */
    
protected $posX;

    
/**
     * Current cursor Y position
     *
     * @var int
     */
    
protected $posY;

    
/**
     * Write a chunk of text to console.
     *
     * @param string   $text
     * @param null|int $color
     * @param null|int $bgColor
     */
    
public function write($text, $color = null, $bgColor = null)
    {
        if (
$color !== null || $bgColor !== null) {
            echo 
$this->colorize($text, $color, $bgColor);
        } else {
            echo 
$text;
        }
    }

    
/**
     * Alias for write()
     *
     * @param string   $text
     * @param null|int $color
     * @param null|int $bgColor
     */
    
public function writeText($text, $color = null, $bgColor = null)
    {
        return 
$this->write($text, $color, $bgColor);
    }

    
/**
     * Write a single line of text to console and advance cursor to the next line.
     *
     * @param string   $text
     * @param null|int $color
     * @param null|int $bgColor
     */
    
public function writeLine($text = "", $color = null, $bgColor = null)
    {
        
$this->write($text . PHP_EOL, $color, $bgColor);
    }

    
/**
     * Write a piece of text at the coordinates of $x and $y
     *
     *
     * @param string   $text    Text to write
     * @param int      $x       Console X coordinate (column)
     * @param int      $y       Console Y coordinate (row)
     * @param null|int $color
     * @param null|int $bgColor
     */
    
public function writeAt($text, $x, $y, $color = null, $bgColor = null)
    {
        
$this->setPos( $x, $y );
        
$this->write( $text, $color, $bgColor );
    }

    
/**
     * Write a box at the specified coordinates.
     * If X or Y coordinate value is negative, it will be calculated as the distance from far right or bottom edge
     * of the console (respectively).
     *
     * @param int      $x1           Top-left corner X coordinate (column)
     * @param int      $y1           Top-left corner Y coordinate (row)
     * @param int      $x2           Bottom-right corner X coordinate (column)
     * @param int      $y2           Bottom-right corner Y coordinate (row)
     * @param int      $lineStyle    (optional) Box border style.
     * @param int      $fillStyle    (optional) Box fill style or a single character to fill it with.
     * @param int      $color        (optional) Foreground color
     * @param int      $bgColor      (optional) Background color
     * @param null|int $fillColor    (optional) Foreground color of box fill
     * @param null|int $fillBgColor  (optional) Background color of box fill
     * @throws Exception\BadMethodCallException if coordinates are invalid
     */
    
public function writeBox(
        
$x1,
        
$y1,
        
$x2,
        
$y2,
        
$lineStyle = self::LINE_SINGLE,
        
$fillStyle = self::FILL_NONE,
        
$color = null,
        
$bgColor = null,
        
$fillColor = null,
        
$fillBgColor = null
    
) {
        
// Sanitize coordinates
        
$x1 = (int) $x1;
        
$y1 = (int) $y1;
        
$x2 = (int) $x2;
        
$y2 = (int) $y2;

        
// Translate negative coordinates
        
if ($x2 < 0) {
            
$x2 = $this->getWidth() - $x2;
        }

        if (
$y2 < 0) {
            
$y2 = $this->getHeight() - $y2;
        }

        
// Validate coordinates
        
if ($x1 < 0
            
|| $y1 < 0
            
|| $x2 < $x1
            
|| $y2 < $y1
       
) {
            throw new 
Exception\BadMethodCallException('Supplied X,Y coordinates are invalid.');
        }

        
// Determine charset and dimensions
        
$charset = $this->getCharset();
        
$width   = $x2 - $x1 + 1;
        
$height  = $y2 - $y1 + 1;

        if (
$width <= 2) {
            
$lineStyle = static::LINE_NONE;
        }

        
// Activate line drawing
        
$this->write($charset::ACTIVATE);

        
// Draw horizontal lines
        
if ($lineStyle !== static::LINE_NONE) {
            switch (
$lineStyle) {
                case static::
LINE_SINGLE:
                    
$lineChar = $charset::LINE_SINGLE_EW;
                    break;

                case static::
LINE_DOUBLE:
                    
$lineChar = $charset::LINE_DOUBLE_EW;
                    break;

                case static::
LINE_BLOCK:
                default:
                    
$lineChar = $charset::LINE_BLOCK_EW;
                    break;
            }

            
$this->setPos($x1 + 1, $y1);
            
$this->write(str_repeat($lineChar, $width - 2), $color, $bgColor);
            
$this->setPos($x1 + 1, $y2);
            
$this->write(str_repeat($lineChar, $width - 2), $color, $bgColor);
        }

        
// Draw vertical lines and fill
        
if (is_numeric($fillStyle)
            && 
$fillStyle !== static::FILL_NONE) {

            switch (
$fillStyle) {
                case static::
FILL_SHADE_LIGHT:
                    
$fillChar = $charset::SHADE_LIGHT;
                    break;
                case static::
FILL_SHADE_MEDIUM:
                    
$fillChar = $charset::SHADE_MEDIUM;
                    break;
                case static::
FILL_SHADE_DARK:
                    
$fillChar = $charset::SHADE_DARK;
                    break;
                case static::
FILL_BLOCK:
                default:
                    
$fillChar = $charset::BLOCK;
                    break;
            }

        } elseif (
$fillStyle) {
            
$fillChar = StringUtils::getWrapper()->substr($fillStyle, 0, 1);
        } else {
            
$fillChar = ' ';
        }

        if (
$lineStyle === static::LINE_NONE) {
            for (
$y = $y1; $y <= $y2; $y++) {
                
$this->setPos($x1, $y);
                
$this->write(str_repeat($fillChar, $width), $fillColor, $fillBgColor);
            }
        } else {
            switch (
$lineStyle) {
                case static::
LINE_DOUBLE:
                    
$lineChar = $charset::LINE_DOUBLE_NS;
                    break;
                case static::
LINE_BLOCK:
                    
$lineChar = $charset::LINE_BLOCK_NS;
                    break;
                case static::
LINE_SINGLE:
                default:
                    
$lineChar = $charset::LINE_SINGLE_NS;
                    break;
            }

            for (
$y = $y1 + 1; $y < $y2; $y++) {
                
$this->setPos($x1, $y);
                
$this->write($lineChar, $color, $bgColor);
                
$this->write(str_repeat($fillChar, $width - 2), $fillColor, $fillBgColor);
                
$this->write($lineChar, $color, $bgColor);
            }
        }


        
// Draw corners
        
if ($lineStyle !== static::LINE_NONE) {
            if (
$color !== null) {
                
$this->setColor($color);
            }
            if (
$bgColor !== null) {
                
$this->setBgColor($bgColor);
            }
            if (
$lineStyle === static::LINE_SINGLE) {
                
$this->writeAt($charset::LINE_SINGLE_NW, $x1, $y1);
                
$this->writeAt($charset::LINE_SINGLE_NE, $x2, $y1);
                
$this->writeAt($charset::LINE_SINGLE_SE, $x2, $y2);
                
$this->writeAt($charset::LINE_SINGLE_SW, $x1, $y2);
            } elseif (
$lineStyle === static::LINE_DOUBLE) {
                
$this->writeAt($charset::LINE_DOUBLE_NW, $x1, $y1);
                
$this->writeAt($charset::LINE_DOUBLE_NE, $x2, $y1);
                
$this->writeAt($charset::LINE_DOUBLE_SE, $x2, $y2);
                
$this->writeAt($charset::LINE_DOUBLE_SW, $x1, $y2);
            } elseif (
$lineStyle === static::LINE_BLOCK) {
                
$this->writeAt($charset::LINE_BLOCK_NW, $x1, $y1);
                
$this->writeAt($charset::LINE_BLOCK_NE, $x2, $y1);
                
$this->writeAt($charset::LINE_BLOCK_SE, $x2, $y2);
                
$this->writeAt($charset::LINE_BLOCK_SW, $x1, $y2);
            }
        }

        
// Deactivate line drawing and reset colors
        
$this->write($charset::DEACTIVATE);
        
$this->resetColor();
    }

    
/**
     * Write a block of text at the given coordinates, matching the supplied width and height.
     * In case a line of text does not fit desired width, it will be wrapped to the next line.
     * In case the whole text does not fit in desired height, it will be truncated.
     *
     * @param string   $text    Text to write
     * @param int      $width   Maximum block width. Negative value means distance from right edge.
     * @param int|null $height  Maximum block height. Negative value means distance from bottom edge.
     * @param int      $x       Block X coordinate (column)
     * @param int      $y       Block Y coordinate (row)
     * @param null|int $color   (optional) Text color
     * @param null|int $bgColor (optional) Text background color
     */
    
public function writeTextBlock(
        
$text,
        
$width,
        
$height = null,
        
$x = 0,
        
$y = 0,
        
$color = null,
        
$bgColor = null
    
) {
    }

    
/**
     * Determine and return current console width.
     *
     * @return int
     */
    
public function getWidth()
    {
        return 
80;
    }

    
/**
     * Determine and return current console height.
     *
     * @return int
     */
    
public function getHeight()
    {
        return 
25;
    }

    
/**
     * Determine and return current console width and height.
     *
     * @return array array($width, $height)
     */
    
public function getSize()
    {
        return array(
            
$this->getWidth(),
            
$this->getHeight(),
        );
    }

    
/**
     * Check if console is UTF-8 compatible
     *
     * @return bool
     */
    
public function isUtf8()
    {
        return 
true;
    }

    
/**
     * Set cursor position
     *
     * @param int $x
     * @param int $y
     */
    
public function setPos($x, $y)
    {
    }

    
/**
     * Show console cursor
     */
    
public function showCursor()
    {
    }

    
/**
     * Hide console cursor
     */
    
public function hideCursor()
    {
    }

    
/**
     * Return current console window title.
     *
     * @return string
     */
    
public function getTitle()
    {
        return 
'';
    }

    
/**
     * Prepare a string that will be rendered in color.
     *
     * @param  string   $string
     * @param  int      $color
     * @param  null|int $bgColor
     * @return string
     */
    
public function colorize($string, $color = null, $bgColor = null)
    {
        return 
$string;
    }

    
/**
     * Change current drawing color.
     *
     * @param int $color
     */
    
public function setColor($color)
    {
    }

    
/**
     * Change current drawing background color
     *
     * @param int $color
     */
    
public function setBgColor($color)
    {
    }

    
/**
     * Reset color to console default.
     */
    
public function resetColor()
    {
    }

    
/**
     * Set Console charset to use.
     *
     * @param Charset\CharsetInterface $charset
     */
    
public function setCharset(Charset\CharsetInterface $charset)
    {
        
$this->charset = $charset;
    }

    
/**
     * Get charset currently in use by this adapter.
     *
     * @return Charset\CharsetInterface $charset
     */
    
public function getCharset()
    {
        if (
$this->charset === null) {
            
$this->charset = $this->getDefaultCharset();
        }

        return 
$this->charset;
    }

    
/**
     * @return Charset\Utf8
     */
    
public function getDefaultCharset()
    {
        return new 
Charset\Utf8;
    }

    
/**
     * Clear console screen
     */
    
public function clear()
    {
        echo 
"\f";
    }

    
/**
     * Clear line at cursor position
     */
    
public function clearLine()
    {
        echo 
"\r" . str_repeat( " ", $this->getWidth() ) . "\r";
    }

    
/**
     * Clear console screen
     */
    
public function clearScreen()
    {
        return 
$this->clear();
    }

    
/**
     * Read a single line from the console input
     *
     * @param int $maxLength        Maximum response length
     * @return string
     */
    
public function readLine($maxLength = 2048)
    {
        
$f    = fopen('php://stdin','r');
        
$line = stream_get_line($f, $maxLength, PHP_EOL);
        
fclose($f);
        return 
rtrim($line,"\n\r");
    }

    
/**
     * Read a single character from the console input
     *
     * @param string|null   $mask   A list of allowed chars
     * @return string
     */
    
public function readChar($mask = null)
    {
        
$f = fopen('php://stdin','r');
        do {
            
$char = fread($f,1);
        } while (
"" === $char || ($mask !== null && false === strstr($mask, $char)));
        
fclose($f);
        return 
$char;
    }
}