<?php

namespace XQueue\Maileon\API\Reports;

use XQueue\Maileon\API\XML\AbstractXMLWrapper;
use XQueue\Maileon\API\Reports\ReportContact;

/**
 * This class represents a unique bounce containing the timestamp, the contact, and the ID of the mailing.
 *
 * @author Marcus Ständer
 */
class UniqueBounce extends AbstractXMLWrapper
{
    /**
     * @var String
     */
    public $timestamp;

    /**
     * @var com_maileon_api_reports_ReportContact
     */
    public $contact;

    /**
     * @var long
     */
    public $mailingId;

    /**
     * @var String
     */
    public $lastType;

    /**
     * @var int
     */
    public $count;

    /**
     * @var int
     */
    public $countHard;

    /**
     * @var int
     */
    public $countSoft;

    /**
     * @return \em string
     *  containing a human-readable representation of this unique bounce
     */
    function toString()
    {
        return "UniqueBounce [timestamp=" . $this->timestamp .
        ", contact=" . $this->contact->toString() .
        ", mailingId=" . $this->mailingId .
        ", count=" . $this->count .
        ", countHard=" . $this->countHard .
        ", countSoft=" . $this->countSoft .
        ", lastType=" . $this->lastType . "]";
    }

    /**
     * @return \em csv string
     *  containing a csv pepresentation of this unique bounce
     */
    function toCsvString()
    {
        return $this->timestamp .
        ";" . $this->contact->toCsvString() .
        ";" . $this->mailingId .
        ";" . $this->count .
        ";" . $this->countHard .
        ";" . $this->countSoft .
        ";" . $this->lastType;
    }

    /**
     * Initializes this unique bounce from an XML representation.
     *
     * @param SimpleXMLElement $xmlElement
     *  the XML representation to use
     */
    function fromXML($xmlElement)
    {
        $this->contact = new ReportContact();
        $this->contact->fromXML($xmlElement->contact);

        if (isset($xmlElement->mailing_id)) $this->mailingId = $xmlElement->mailing_id;
        if (isset($xmlElement->timestamp)) $this->timestamp = $xmlElement->timestamp;
        if (isset($xmlElement->last_type)) $this->lastType = $xmlElement->last_type;
        if (isset($xmlElement->count)) $this->count = $xmlElement->count;
        if (isset($xmlElement->count_hard)) $this->countHard = $xmlElement->count_hard;
        if (isset($xmlElement->count_soft)) $this->countSoft = $xmlElement->count_soft;
    }

    /**
     * For future use, not implemented yet.
     *
     * @return \em SimpleXMLElement
     *  containing the XML serialization of this object
     */
    function toXML()
    {
        // Not implemented yet.
    }
}


<?php

namespace PhpOffice\PhpSpreadsheet\Shared;

// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4                                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license,      |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net>                              |
// | Based on OLE::Storage_Lite by Kawai, Takanori                        |
// +----------------------------------------------------------------------+
//

use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream;
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;

/*
 * Array for storing OLE instances that are accessed from
 * OLE_ChainedBlockStream::stream_open().
 *
 * @var array
 */
$GLOBALS['_OLE_INSTANCES'] = [];

/**
 * OLE package base class.
 *
 * @author   Xavier Noguer <xnoguer@php.net>
 * @author   Christian Schmidt <schmidt@php.net>
 *
 * @category   PhpSpreadsheet
 */
class OLE
{
    const OLE_PPS_TYPE_ROOT = 5;
    const OLE_PPS_TYPE_DIR = 1;
    const OLE_PPS_TYPE_FILE = 2;
    const OLE_DATA_SIZE_SMALL = 0x1000;
    const OLE_LONG_INT_SIZE = 4;
    const OLE_PPS_SIZE = 0x80;

    /**
     * The file handle for reading an OLE container.
     *
     * @var resource
     */
    public $_file_handle;

    /**
     * Array of PPS's found on the OLE container.
     *
     * @var array
     */
    public $_list = [];

    /**
     * Root directory of OLE container.
     *
     * @var Root
     */
    public $root;

    /**
     * Big Block Allocation Table.
     *
     * @var array (blockId => nextBlockId)
     */
    public $bbat;

    /**
     * Short Block Allocation Table.
     *
     * @var array (blockId => nextBlockId)
     */
    public $sbat;

    /**
     * Size of big blocks. This is usually 512.
     *
     * @var int number of octets per block
     */
    public $bigBlockSize;

    /**
     * Size of small blocks. This is usually 64.
     *
     * @var int number of octets per block
     */
    public $smallBlockSize;

    /**
     * Threshold for big blocks.
     *
     * @var int
     */
    public $bigBlockThreshold;

    /**
     * Reads an OLE container from the contents of the file given.
     *
     * @acces public
     *
     * @param string $file
     *
     * @throws ReaderException
     *
     * @return bool true on success, PEAR_Error on failure
     */
    public function read($file)
    {
        $fh = fopen($file, 'r');
        if (!$fh) {
            throw new ReaderException("Can't open file $file");
        }
        $this->_file_handle = $fh;

        $signature = fread($fh, 8);
        if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
            throw new ReaderException("File doesn't seem to be an OLE container.");
        }
        fseek($fh, 28);
        if (fread($fh, 2) != "\xFE\xFF") {
            // This shouldn't be a problem in practice
            throw new ReaderException('Only Little-Endian encoding is supported.');
        }
        // Size of blocks and short blocks in bytes
        $this->bigBlockSize = pow(2, self::_readInt2($fh));
        $this->smallBlockSize = pow(2, self::_readInt2($fh));

        // Skip UID, revision number and version number
        fseek($fh, 44);
        // Number of blocks in Big Block Allocation Table
        $bbatBlockCount = self::_readInt4($fh);

        // Root chain 1st block
        $directoryFirstBlockId = self::_readInt4($fh);

        // Skip unused bytes
        fseek($fh, 56);
        // Streams shorter than this are stored using small blocks
        $this->bigBlockThreshold = self::_readInt4($fh);
        // Block id of first sector in Short Block Allocation Table
        $sbatFirstBlockId = self::_readInt4($fh);
        // Number of blocks in Short Block Allocation Table
        $sbbatBlockCount = self::_readInt4($fh);
        // Block id of first sector in Master Block Allocation Table
        $mbatFirstBlockId = self::_readInt4($fh);
        // Number of blocks in Master Block Allocation Table
        $mbbatBlockCount = self::_readInt4($fh);
        $this->bbat = [];

        // Remaining 4 * 109 bytes of current block is beginning of Master
        // Block Allocation Table
        $mbatBlocks = [];
        for ($i = 0; $i < 109; ++$i) {
            $mbatBlocks[] = self::_readInt4($fh);
        }

        // Read rest of Master Block Allocation Table (if any is left)
        $pos = $this->_getBlockOffset($mbatFirstBlockId);
        for ($i = 0; $i < $mbbatBlockCount; ++$i) {
            fseek($fh, $pos);
            for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) {
                $mbatBlocks[] = self::_readInt4($fh);
            }
            // Last block id in each block points to next block
            $pos = $this->_getBlockOffset(self::_readInt4($fh));
        }

        // Read Big Block Allocation Table according to chain specified by $mbatBlocks
        for ($i = 0; $i < $bbatBlockCount; ++$i) {
            $pos = $this->_getBlockOffset($mbatBlocks[$i]);
            fseek($fh, $pos);
            for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) {
                $this->bbat[] = self::_readInt4($fh);
            }
        }

        // Read short block allocation table (SBAT)
        $this->sbat = [];
        $shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4;
        $sbatFh = $this->getStream($sbatFirstBlockId);
        for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) {
            $this->sbat[$blockId] = self::_readInt4($sbatFh);
        }
        fclose($sbatFh);

        $this->_readPpsWks($directoryFirstBlockId);

        return true;
    }

    /**
     * @param int $blockId byte offset from beginning of file
     *
     * @return int
     */
    public function _getBlockOffset($blockId)
    {
        return 512 + $blockId * $this->bigBlockSize;
    }

    /**
     * Returns a stream for use with fread() etc. External callers should
     * use \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File::getStream().
     *
     * @param int|OLE\PPS $blockIdOrPps block id or PPS
     *
     * @return resource read-only stream
     */
    public function getStream($blockIdOrPps)
    {
        static $isRegistered = false;
        if (!$isRegistered) {
            stream_wrapper_register('ole-chainedblockstream', ChainedBlockStream::class);
            $isRegistered = true;
        }

        // Store current instance in global array, so that it can be accessed
        // in OLE_ChainedBlockStream::stream_open().
        // Object is removed from self::$instances in OLE_Stream::close().
        $GLOBALS['_OLE_INSTANCES'][] = $this;
        $instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES']));

        $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
        if ($blockIdOrPps instanceof OLE\PPS) {
            $path .= '&blockId=' . $blockIdOrPps->startBlock;
            $path .= '&size=' . $blockIdOrPps->Size;
        } else {
            $path .= '&blockId=' . $blockIdOrPps;
        }

        return fopen($path, 'r');
    }

    /**
     * Reads a signed char.
     *
     * @param resource $fh file handle
     *
     * @return int
     */
    private static function _readInt1($fh)
    {
        list(, $tmp) = unpack('c', fread($fh, 1));

        return $tmp;
    }

    /**
     * Reads an unsigned short (2 octets).
     *
     * @param resource $fh file handle
     *
     * @return int
     */
    private static function _readInt2($fh)
    {
        list(, $tmp) = unpack('v', fread($fh, 2));

        return $tmp;
    }

    /**
     * Reads an unsigned long (4 octets).
     *
     * @param resource $fh file handle
     *
     * @return int
     */
    private static function _readInt4($fh)
    {
        list(, $tmp) = unpack('V', fread($fh, 4));

        return $tmp;
    }

    /**
     * Gets information about all PPS's on the OLE container from the PPS WK's
     * creates an OLE_PPS object for each one.
     *
     * @param int $blockId the block id of the first block
     *
     * @return bool true on success, PEAR_Error on failure
     */
    public function _readPpsWks($blockId)
    {
        $fh = $this->getStream($blockId);
        for ($pos = 0; true; $pos += 128) {
            fseek($fh, $pos, SEEK_SET);
            $nameUtf16 = fread($fh, 64);
            $nameLength = self::_readInt2($fh);
            $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2);
            // Simple conversion from UTF-16LE to ISO-8859-1
            $name = str_replace("\x00", '', $nameUtf16);
            $type = self::_readInt1($fh);
            switch ($type) {
                case self::OLE_PPS_TYPE_ROOT:
                    $pps = new OLE\PPS\Root(null, null, []);
                    $this->root = $pps;

                    break;
                case self::OLE_PPS_TYPE_DIR:
                    $pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []);

                    break;
                case self::OLE_PPS_TYPE_FILE:
                    $pps = new OLE\PPS\File($name);

                    break;
                default:
                    continue;
            }
            fseek($fh, 1, SEEK_CUR);
            $pps->Type = $type;
            $pps->Name = $name;
            $pps->PrevPps = self::_readInt4($fh);
            $pps->NextPps = self::_readInt4($fh);
            $pps->DirPps = self::_readInt4($fh);
            fseek($fh, 20, SEEK_CUR);
            $pps->Time1st = self::OLE2LocalDate(fread($fh, 8));
            $pps->Time2nd = self::OLE2LocalDate(fread($fh, 8));
            $pps->startBlock = self::_readInt4($fh);
            $pps->Size = self::_readInt4($fh);
            $pps->No = count($this->_list);
            $this->_list[] = $pps;

            // check if the PPS tree (starting from root) is complete
            if (isset($this->root) && $this->_ppsTreeComplete($this->root->No)) {
                break;
            }
        }
        fclose($fh);

        // Initialize $pps->children on directories
        foreach ($this->_list as $pps) {
            if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) {
                $nos = [$pps->DirPps];
                $pps->children = [];
                while ($nos) {
                    $no = array_pop($nos);
                    if ($no != -1) {
                        $childPps = $this->_list[$no];
                        $nos[] = $childPps->PrevPps;
                        $nos[] = $childPps->NextPps;
                        $pps->children[] = $childPps;
                    }
                }
            }
        }

        return true;
    }

    /**
     * It checks whether the PPS tree is complete (all PPS's read)
     * starting with the given PPS (not necessarily root).
     *
     * @param int $index The index of the PPS from which we are checking
     *
     * @return bool Whether the PPS tree for the given PPS is complete
     */
    public function _ppsTreeComplete($index)
    {
        return isset($this->_list[$index]) &&
            ($pps = $this->_list[$index]) &&
            ($pps->PrevPps == -1 ||
                $this->_ppsTreeComplete($pps->PrevPps)) &&
            ($pps->NextPps == -1 ||
                $this->_ppsTreeComplete($pps->NextPps)) &&
            ($pps->DirPps == -1 ||
                $this->_ppsTreeComplete($pps->DirPps));
    }

    /**
     * Checks whether a PPS is a File PPS or not.
     * If there is no PPS for the index given, it will return false.
     *
     * @param int $index The index for the PPS
     *
     * @return bool true if it's a File PPS, false otherwise
     */
    public function isFile($index)
    {
        if (isset($this->_list[$index])) {
            return $this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE;
        }

        return false;
    }

    /**
     * Checks whether a PPS is a Root PPS or not.
     * If there is no PPS for the index given, it will return false.
     *
     * @param int $index the index for the PPS
     *
     * @return bool true if it's a Root PPS, false otherwise
     */
    public function isRoot($index)
    {
        if (isset($this->_list[$index])) {
            return $this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT;
        }

        return false;
    }

    /**
     * Gives the total number of PPS's found in the OLE container.
     *
     * @return int The total number of PPS's found in the OLE container
     */
    public function ppsTotal()
    {
        return count($this->_list);
    }

    /**
     * Gets data from a PPS
     * If there is no PPS for the index given, it will return an empty string.
     *
     * @param int $index The index for the PPS
     * @param int $position The position from which to start reading
     *                          (relative to the PPS)
     * @param int $length The amount of bytes to read (at most)
     *
     * @return string The binary string containing the data requested
     *
     * @see OLE_PPS_File::getStream()
     */
    public function getData($index, $position, $length)
    {
        // if position is not valid return empty string
        if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) {
            return '';
        }
        $fh = $this->getStream($this->_list[$index]);
        $data = stream_get_contents($fh, $length, $position);
        fclose($fh);

        return $data;
    }

    /**
     * Gets the data length from a PPS
     * If there is no PPS for the index given, it will return 0.
     *
     * @param int $index The index for the PPS
     *
     * @return int The amount of bytes in data the PPS has
     */
    public function getDataLength($index)
    {
        if (isset($this->_list[$index])) {
            return $this->_list[$index]->Size;
        }

        return 0;
    }

    /**
     * Utility function to transform ASCII text to Unicode.
     *
     * @param string $ascii The ASCII string to transform
     *
     * @return string The string in Unicode
     */
    public static function ascToUcs($ascii)
    {
        $rawname = '';
        $iMax = strlen($ascii);
        for ($i = 0; $i < $iMax; ++$i) {
            $rawname .= $ascii[$i]
                . "\x00";
        }

        return $rawname;
    }

    /**
     * Utility function
     * Returns a string for the OLE container with the date given.
     *
     * @param int $date A timestamp
     *
     * @return string The string for the OLE container
     */
    public static function localDateToOLE($date)
    {
        if (!isset($date)) {
            return "\x00\x00\x00\x00\x00\x00\x00\x00";
        }

        // factor used for separating numbers into 4 bytes parts
        $factor = pow(2, 32);

        // days from 1-1-1601 until the beggining of UNIX era
        $days = 134774;
        // calculate seconds
        $big_date = $days * 24 * 3600 + gmmktime(date('H', $date), date('i', $date), date('s', $date), date('m', $date), date('d', $date), date('Y', $date));
        // multiply just to make MS happy
        $big_date *= 10000000;

        $high_part = floor($big_date / $factor);
        // lower 4 bytes
        $low_part = floor((($big_date / $factor) - $high_part) * $factor);

        // Make HEX string
        $res = '';

        for ($i = 0; $i < 4; ++$i) {
            $hex = $low_part % 0x100;
            $res .= pack('c', $hex);
            $low_part /= 0x100;
        }
        for ($i = 0; $i < 4; ++$i) {
            $hex = $high_part % 0x100;
            $res .= pack('c', $hex);
            $high_part /= 0x100;
        }

        return $res;
    }

    /**
     * Returns a timestamp from an OLE container's date.
     *
     * @param int $string A binary string with the encoded date
     *
     * @return string The timestamp corresponding to the string
     */
    public static function OLE2LocalDate($string)
    {
        if (strlen($string) != 8) {
            throw new ReaderException('Expecting 8 byte string');
        }

        // factor used for separating numbers into 4 bytes parts
        $factor = pow(2, 32);
        list(, $high_part) = unpack('V', substr($string, 4, 4));
        list(, $low_part) = unpack('V', substr($string, 0, 4));

        $big_date = ($high_part * $factor) + $low_part;
        // translate to seconds
        $big_date /= 10000000;

        // days from 1-1-1601 until the beggining of UNIX era
        $days = 134774;

        // translate to seconds from beggining of UNIX era
        $big_date -= $days * 24 * 3600;

        return floor($big_date);
    }
}


<?php

namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;

use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;

/**
 * Matrix class.
 *
 * @author Paul Meagher
 * @author Michael Bommarito
 * @author Lukasz Karapuda
 * @author Bartek Matosiuk
 *
 * @version 1.8
 *
 * @see http://math.nist.gov/javanumerics/jama/
 */
class Matrix
{
    const POLYMORPHIC_ARGUMENT_EXCEPTION = 'Invalid argument pattern for polymorphic function.';
    const ARGUMENT_TYPE_EXCEPTION = 'Invalid argument type.';
    const ARGUMENT_BOUNDS_EXCEPTION = 'Invalid argument range.';
    const MATRIX_DIMENSION_EXCEPTION = 'Matrix dimensions are not equal.';
    const ARRAY_LENGTH_EXCEPTION = 'Array length must be a multiple of m.';
    const MATRIX_SPD_EXCEPTION = 'Can only perform operation on symmetric positive definite matrix.';

    /**
     * Matrix storage.
     *
     * @var array
     */
    public $A = [];

    /**
     * Matrix row dimension.
     *
     * @var int
     */
    private $m;

    /**
     * Matrix column dimension.
     *
     * @var int
     */
    private $n;

    /**
     * Polymorphic constructor.
     *
     * As PHP has no support for polymorphic constructors, we use tricks to make our own sort of polymorphism using func_num_args, func_get_arg, and gettype. In essence, we're just implementing a simple RTTI filter and calling the appropriate constructor.
     */
    public function __construct(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                //Rectangular matrix - m x n initialized from 2D array
                case 'array':
                    $this->m = count($args[0]);
                    $this->n = count($args[0][0]);
                    $this->A = $args[0];

                    break;
                //Square matrix - n x n
                case 'integer':
                    $this->m = $args[0];
                    $this->n = $args[0];
                    $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));

                    break;
                //Rectangular matrix - m x n
                case 'integer,integer':
                    $this->m = $args[0];
                    $this->n = $args[1];
                    $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));

                    break;
                //Rectangular matrix - m x n initialized from packed array
                case 'array,integer':
                    $this->m = $args[1];
                    if ($this->m != 0) {
                        $this->n = count($args[0]) / $this->m;
                    } else {
                        $this->n = 0;
                    }
                    if (($this->m * $this->n) == count($args[0])) {
                        for ($i = 0; $i < $this->m; ++$i) {
                            for ($j = 0; $j < $this->n; ++$j) {
                                $this->A[$i][$j] = $args[0][$i + $j * $this->m];
                            }
                        }
                    } else {
                        throw new CalculationException(self::ARRAY_LENGTH_EXCEPTION);
                    }

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
        } else {
            throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
        }
    }

    /**
     * getArray.
     *
     * @return array Matrix array
     */
    public function getArray()
    {
        return $this->A;
    }

    /**
     * getRowDimension.
     *
     * @return int Row dimension
     */
    public function getRowDimension()
    {
        return $this->m;
    }

    /**
     * getColumnDimension.
     *
     * @return int Column dimension
     */
    public function getColumnDimension()
    {
        return $this->n;
    }

    /**
     * get.
     *
     * Get the i,j-th element of the matrix.
     *
     * @param int $i Row position
     * @param int $j Column position
     *
     * @return mixed Element (int/float/double)
     */
    public function get($i = null, $j = null)
    {
        return $this->A[$i][$j];
    }

    /**
     * getMatrix.
     *
     *    Get a submatrix
     *
     * @param int $i0 Initial row index
     * @param int $iF Final row index
     * @param int $j0 Initial column index
     * @param int $jF Final column index
     *
     * @return Matrix Submatrix
     */
    public function getMatrix(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                //A($i0...; $j0...)
                case 'integer,integer':
                    list($i0, $j0) = $args;
                    if ($i0 >= 0) {
                        $m = $this->m - $i0;
                    } else {
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
                    }
                    if ($j0 >= 0) {
                        $n = $this->n - $j0;
                    } else {
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
                    }
                    $R = new self($m, $n);
                    for ($i = $i0; $i < $this->m; ++$i) {
                        for ($j = $j0; $j < $this->n; ++$j) {
                            $R->set($i, $j, $this->A[$i][$j]);
                        }
                    }

                    return $R;

                    break;
                //A($i0...$iF; $j0...$jF)
                case 'integer,integer,integer,integer':
                    list($i0, $iF, $j0, $jF) = $args;
                    if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
                        $m = $iF - $i0;
                    } else {
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
                    }
                    if (($jF > $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
                        $n = $jF - $j0;
                    } else {
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
                    }
                    $R = new self($m + 1, $n + 1);
                    for ($i = $i0; $i <= $iF; ++$i) {
                        for ($j = $j0; $j <= $jF; ++$j) {
                            $R->set($i - $i0, $j - $j0, $this->A[$i][$j]);
                        }
                    }

                    return $R;

                    break;
                //$R = array of row indices; $C = array of column indices
                case 'array,array':
                    list($RL, $CL) = $args;
                    if (count($RL) > 0) {
                        $m = count($RL);
                    } else {
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
                    }
                    if (count($CL) > 0) {
                        $n = count($CL);
                    } else {
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
                    }
                    $R = new self($m, $n);
                    for ($i = 0; $i < $m; ++$i) {
                        for ($j = 0; $j < $n; ++$j) {
                            $R->set($i - $i0, $j - $j0, $this->A[$RL[$i]][$CL[$j]]);
                        }
                    }

                    return $R;

                    break;
                //A($i0...$iF); $CL = array of column indices
                case 'integer,integer,array':
                    list($i0, $iF, $CL) = $args;
                    if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
                        $m = $iF - $i0;
                    } else {
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
                    }
                    if (count($CL) > 0) {
                        $n = count($CL);
                    } else {
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
                    }
                    $R = new self($m, $n);
                    for ($i = $i0; $i < $iF; ++$i) {
                        for ($j = 0; $j < $n; ++$j) {
                            $R->set($i - $i0, $j, $this->A[$RL[$i]][$j]);
                        }
                    }

                    return $R;

                    break;
                //$RL = array of row indices
                case 'array,integer,integer':
                    list($RL, $j0, $jF) = $args;
                    if (count($RL) > 0) {
                        $m = count($RL);
                    } else {
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
                    }
                    if (($jF >= $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
                        $n = $jF - $j0;
                    } else {
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
                    }
                    $R = new self($m, $n + 1);
                    for ($i = 0; $i < $m; ++$i) {
                        for ($j = $j0; $j <= $jF; ++$j) {
                            $R->set($i, $j - $j0, $this->A[$RL[$i]][$j]);
                        }
                    }

                    return $R;

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
        } else {
            throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
        }
    }

    /**
     * checkMatrixDimensions.
     *
     *    Is matrix B the same size?
     *
     * @param Matrix $B Matrix B
     *
     * @return bool
     */
    public function checkMatrixDimensions($B = null)
    {
        if ($B instanceof self) {
            if (($this->m == $B->getRowDimension()) && ($this->n == $B->getColumnDimension())) {
                return true;
            }

            throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
        }

        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
    }

    //    function checkMatrixDimensions()

    /**
     * set.
     *
     * Set the i,j-th element of the matrix.
     *
     * @param int $i Row position
     * @param int $j Column position
     * @param mixed $c Int/float/double value
     *
     * @return mixed Element (int/float/double)
     */
    public function set($i = null, $j = null, $c = null)
    {
        // Optimized set version just has this
        $this->A[$i][$j] = $c;
    }

    //    function set()

    /**
     * identity.
     *
     * Generate an identity matrix.
     *
     * @param int $m Row dimension
     * @param int $n Column dimension
     *
     * @return Matrix Identity matrix
     */
    public function identity($m = null, $n = null)
    {
        return $this->diagonal($m, $n, 1);
    }

    /**
     * diagonal.
     *
     *    Generate a diagonal matrix
     *
     * @param int $m Row dimension
     * @param int $n Column dimension
     * @param mixed $c Diagonal value
     *
     * @return Matrix Diagonal matrix
     */
    public function diagonal($m = null, $n = null, $c = 1)
    {
        $R = new self($m, $n);
        for ($i = 0; $i < $m; ++$i) {
            $R->set($i, $i, $c);
        }

        return $R;
    }

    /**
     * getMatrixByRow.
     *
     *    Get a submatrix by row index/range
     *
     * @param int $i0 Initial row index
     * @param int $iF Final row index
     *
     * @return Matrix Submatrix
     */
    public function getMatrixByRow($i0 = null, $iF = null)
    {
        if (is_int($i0)) {
            if (is_int($iF)) {
                return $this->getMatrix($i0, 0, $iF + 1, $this->n);
            }

            return $this->getMatrix($i0, 0, $i0 + 1, $this->n);
        }

        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
    }

    /**
     * getMatrixByCol.
     *
     *    Get a submatrix by column index/range
     *
     * @param int $j0 Initial column index
     * @param int $jF Final column index
     *
     * @return Matrix Submatrix
     */
    public function getMatrixByCol($j0 = null, $jF = null)
    {
        if (is_int($j0)) {
            if (is_int($jF)) {
                return $this->getMatrix(0, $j0, $this->m, $jF + 1);
            }

            return $this->getMatrix(0, $j0, $this->m, $j0 + 1);
        }

        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
    }

    /**
     * transpose.
     *
     *    Tranpose matrix
     *
     * @return Matrix Transposed matrix
     */
    public function transpose()
    {
        $R = new self($this->n, $this->m);
        for ($i = 0; $i < $this->m; ++$i) {
            for ($j = 0; $j < $this->n; ++$j) {
                $R->set($j, $i, $this->A[$i][$j]);
            }
        }

        return $R;
    }

    //    function transpose()

    /**
     * trace.
     *
     *    Sum of diagonal elements
     *
     * @return float Sum of diagonal elements
     */
    public function trace()
    {
        $s = 0;
        $n = min($this->m, $this->n);
        for ($i = 0; $i < $n; ++$i) {
            $s += $this->A[$i][$i];
        }

        return $s;
    }

    /**
     * uminus.
     *
     *    Unary minus matrix -A
     *
     * @return Matrix Unary minus matrix
     */
    public function uminus()
    {
    }

    /**
     * plus.
     *
     *    A + B
     *
     * @param mixed $B Matrix/Array
     *
     * @return Matrix Sum
     */
    public function plus(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $M->set($i, $j, $M->get($i, $j) + $this->A[$i][$j]);
                }
            }

            return $M;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * plusEquals.
     *
     *    A = A + B
     *
     * @param mixed $B Matrix/Array
     *
     * @return Matrix Sum
     */
    public function plusEquals(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $validValues = true;
                    $value = $M->get($i, $j);
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
                    }
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
                        $value = trim($value, '"');
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
                    }
                    if ($validValues) {
                        $this->A[$i][$j] += $value;
                    } else {
                        $this->A[$i][$j] = Functions::NAN();
                    }
                }
            }

            return $this;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * minus.
     *
     *    A - B
     *
     * @param mixed $B Matrix/Array
     *
     * @return Matrix Sum
     */
    public function minus(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $M->set($i, $j, $M->get($i, $j) - $this->A[$i][$j]);
                }
            }

            return $M;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * minusEquals.
     *
     *    A = A - B
     *
     * @param mixed $B Matrix/Array
     *
     * @return Matrix Sum
     */
    public function minusEquals(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $validValues = true;
                    $value = $M->get($i, $j);
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
                    }
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
                        $value = trim($value, '"');
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
                    }
                    if ($validValues) {
                        $this->A[$i][$j] -= $value;
                    } else {
                        $this->A[$i][$j] = Functions::NAN();
                    }
                }
            }

            return $this;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * arrayTimes.
     *
     *    Element-by-element multiplication
     *    Cij = Aij * Bij
     *
     * @param mixed $B Matrix/Array
     *
     * @return Matrix Matrix Cij
     */
    public function arrayTimes(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $M->set($i, $j, $M->get($i, $j) * $this->A[$i][$j]);
                }
            }

            return $M;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * arrayTimesEquals.
     *
     *    Element-by-element multiplication
     *    Aij = Aij * Bij
     *
     * @param mixed $B Matrix/Array
     *
     * @return Matrix Matrix Aij
     */
    public function arrayTimesEquals(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $validValues = true;
                    $value = $M->get($i, $j);
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
                    }
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
                        $value = trim($value, '"');
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
                    }
                    if ($validValues) {
                        $this->A[$i][$j] *= $value;
                    } else {
                        $this->A[$i][$j] = Functions::NAN();
                    }
                }
            }

            return $this;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * arrayRightDivide.
     *
     *    Element-by-element right division
     *    A / B
     *
     * @param Matrix $B Matrix B
     *
     * @return Matrix Division result
     */
    public function arrayRightDivide(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $validValues = true;
                    $value = $M->get($i, $j);
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
                    }
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
                        $value = trim($value, '"');
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
                    }
                    if ($validValues) {
                        if ($value == 0) {
                            //    Trap for Divide by Zero error
                            $M->set($i, $j, '#DIV/0!');
                        } else {
                            $M->set($i, $j, $this->A[$i][$j] / $value);
                        }
                    } else {
                        $M->set($i, $j, Functions::NAN());
                    }
                }
            }

            return $M;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * arrayRightDivideEquals.
     *
     *    Element-by-element right division
     *    Aij = Aij / Bij
     *
     * @param mixed $B Matrix/Array
     *
     * @return Matrix Matrix Aij
     */
    public function arrayRightDivideEquals(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $this->A[$i][$j] = $this->A[$i][$j] / $M->get($i, $j);
                }
            }

            return $M;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * arrayLeftDivide.
     *
     *    Element-by-element Left division
     *    A / B
     *
     * @param Matrix $B Matrix B
     *
     * @return Matrix Division result
     */
    public function arrayLeftDivide(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $M->set($i, $j, $M->get($i, $j) / $this->A[$i][$j]);
                }
            }

            return $M;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * arrayLeftDivideEquals.
     *
     *    Element-by-element Left division
     *    Aij = Aij / Bij
     *
     * @param mixed $B Matrix/Array
     *
     * @return Matrix Matrix Aij
     */
    public function arrayLeftDivideEquals(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $this->A[$i][$j] = $M->get($i, $j) / $this->A[$i][$j];
                }
            }

            return $M;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * times.
     *
     *    Matrix multiplication
     *
     * @param mixed $n Matrix/Array/Scalar
     *
     * @return Matrix Product
     */
    public function times(...$args)
    {
        if (count() > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $B = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }
                    if ($this->n == $B->m) {
                        $C = new self($this->m, $B->n);
                        for ($j = 0; $j < $B->n; ++$j) {
                            for ($k = 0; $k < $this->n; ++$k) {
                                $Bcolj[$k] = $B->A[$k][$j];
                            }
                            for ($i = 0; $i < $this->m; ++$i) {
                                $Arowi = $this->A[$i];
                                $s = 0;
                                for ($k = 0; $k < $this->n; ++$k) {
                                    $s += $Arowi[$k] * $Bcolj[$k];
                                }
                                $C->A[$i][$j] = $s;
                            }
                        }

                        return $C;
                    }

                    throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
                case 'array':
                    $B = new self($args[0]);
                    if ($this->n == $B->m) {
                        $C = new self($this->m, $B->n);
                        for ($i = 0; $i < $C->m; ++$i) {
                            for ($j = 0; $j < $C->n; ++$j) {
                                $s = '0';
                                for ($k = 0; $k < $C->n; ++$k) {
                                    $s += $this->A[$i][$k] * $B->A[$k][$j];
                                }
                                $C->A[$i][$j] = $s;
                            }
                        }

                        return $C;
                    }

                    throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
                case 'integer':
                    $C = new self($this->A);
                    for ($i = 0; $i < $C->m; ++$i) {
                        for ($j = 0; $j < $C->n; ++$j) {
                            $C->A[$i][$j] *= $args[0];
                        }
                    }

                    return $C;
                case 'double':
                    $C = new self($this->m, $this->n);
                    for ($i = 0; $i < $C->m; ++$i) {
                        for ($j = 0; $j < $C->n; ++$j) {
                            $C->A[$i][$j] = $args[0] * $this->A[$i][$j];
                        }
                    }

                    return $C;
                case 'float':
                    $C = new self($this->A);
                    for ($i = 0; $i < $C->m; ++$i) {
                        for ($j = 0; $j < $C->n; ++$j) {
                            $C->A[$i][$j] *= $args[0];
                        }
                    }

                    return $C;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
            }
        } else {
            throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
        }
    }

    /**
     * power.
     *
     *    A = A ^ B
     *
     * @param mixed $B Matrix/Array
     *
     * @return Matrix Sum
     */
    public function power(...$args)
    {
        if (count() > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $validValues = true;
                    $value = $M->get($i, $j);
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
                    }
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
                        $value = trim($value, '"');
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
                    }
                    if ($validValues) {
                        $this->A[$i][$j] = pow($this->A[$i][$j], $value);
                    } else {
                        $this->A[$i][$j] = Functions::NAN();
                    }
                }
            }

            return $this;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * concat.
     *
     *    A = A & B
     *
     * @param mixed $B Matrix/Array
     *
     * @return Matrix Sum
     */
    public function concat(...$args)
    {
        if (count($args) > 0) {
            $match = implode(',', array_map('gettype', $args));

            switch ($match) {
                case 'object':
                    if ($args[0] instanceof self) {
                        $M = $args[0];
                    } else {
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
                    }

                    break;
                case 'array':
                    $M = new self($args[0]);

                    break;
                default:
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);

                    break;
            }
            $this->checkMatrixDimensions($M);
            for ($i = 0; $i < $this->m; ++$i) {
                for ($j = 0; $j < $this->n; ++$j) {
                    $this->A[$i][$j] = trim($this->A[$i][$j], '"') . trim($M->get($i, $j), '"');
                }
            }

            return $this;
        }

        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
    }

    /**
     * Solve A*X = B.
     *
     * @param Matrix $B Right hand side
     *
     * @return Matrix ... Solution if A is square, least squares solution otherwise
     */
    public function solve($B)
    {
        if ($this->m == $this->n) {
            $LU = new LUDecomposition($this);

            return $LU->solve($B);
        }
        $QR = new QRDecomposition($this);

        return $QR->solve($B);
    }

    /**
     * Matrix inverse or pseudoinverse.
     *
     * @return Matrix ... Inverse(A) if A is square, pseudoinverse otherwise.
     */
    public function inverse()
    {
        return $this->solve($this->identity($this->m, $this->m));
    }

    /**
     * det.
     *
     *    Calculate determinant
     *
     * @return float Determinant
     */
    public function det()
    {
        $L = new LUDecomposition($this);

        return $L->det();
    }
}


<?php

namespace joshmoody\Mock;

use Illuminate\Database\Capsule\Manager as DB;

use joshmoody\Mock\Models\Database;
use joshmoody\Mock\Models\LastName;
use joshmoody\Mock\Models\FirstName;
use joshmoody\Mock\Models\Street;
use joshmoody\Mock\Models\Zipcode;
use joshmoody\Mock\Entities as Entities;

use Exception;
use StdClass;
use DateTime;

class Generator
{
	/**
	 * @param array $opts
	 * @codeCoverageIgnore
	 */
	public function __construct($opts = [])
	{
		if (is_array($opts) && array_key_exists('dsn', $opts)) {
			Database::init($opts['dsn']);
		} else {
			Database::init();
		}
	}

	/**
	 * Generate a float number between $min and $max, with precision $precision
	 *
	 * @param int $min
	 * @param int $max
	 * @param int $precision
	 * @return float
	 */
	public function getFloat($min = 0, $max = 10000, $precision = 2)
	{
		$num = rand($min, $max) . '.' . $this->getString('number', $precision);

		if ($num > $max) {
			// In case adding the additional decimal points makes us exceed the max.
			$num = $max;
		}

		return round($num, $precision);
	}

	/**
	 * Generate a random number between $min and $max
	 *
	 * @param int $min
	 * @param int $max
	 * @return int
	 */
	public function getInteger($min = 0, $max = 10000)
	{
		return rand($min, $max);
	}

	/**
	 * Generate a unique sha1 hash.
	 *
	 * @return string
	 */
	public function getUniqueHash()
	{
		return sha1(uniqid(uniqid(), true));
	}

	/**
	 *	Generate random string.
	 *
	 * @access public
	 * @param string $type Options: letter, number, or mix.	 default: letter
	 * @param mixed $desired_length Will be random length if not specified.
	 * @return string
	 */
	public function getString($type = 'letter', $desired_length = null)
	{
		if (empty($desired_length)) {
			$desired_length = $this->getInteger(1, 50);
		}

		$result = '';

		while (strlen($result) < $desired_length) {
			if ($type == 'letter') {
				$result .= $this->getLetter();
			} elseif ($type == 'number') {
				$result .= $this->getInteger(1, 10);
			} else {
				// Mix letters/numbers.
				$result .= $this->getUniqueHash();
			}
		}

		return substr($result, 0, $desired_length);
	}

	/**
	 * Generate a GUID.
	 *
	 * @return string
	 */
	public function getGuid()
	{
		return sprintf(
						'%04x%04x-%04x-%03x4-%04x-%04x%04x%04x',
						mt_rand(0, 65535),
						mt_rand(0, 65535),	// 32 bits for "time_low"
						mt_rand(0, 65535),	// 16 bits for "time_mid"
						mt_rand(0, 4095),	// 12 bits before the 0100 of (version) 4 for "time_hi_and_version"
						bindec(substr_replace(sprintf('%016b', mt_rand(0, 65535)), '01', 6, 2)),
						// 8 bits, the last two of which (positions 6 and 7) are 01, for "clk_seq_hi_res"
						// (hence, the 2nd hex digit after the 3rd hyphen can only be 1, 5, 9 or d)
						// 8 bits for "clk_seq_low"
						mt_rand(0, 65535),
						mt_rand(0, 65535),
						mt_rand(0, 65535) // 48 bits for "node"
		);
	}

	/**
	 * Return a random value from an array
	 *
	 * @param array $array
	 * @return mixed
	 */
	public function fromArray($array = [])
	{
		if (is_array($array) && count($array) > 0) {
			return $array[rand(0, count($array)-1)];
		} else {
			return null;
		}
	}

	/**
	 * Generate a boolean. Use the parameters to custom the ressponse to be true/false, 1/0, Yes/No, etc.
	 *
	 * @access public
	 * @param mixed $true Value that should be returned if true (default: true)
	 * @param mixed $false Value that should be returned if false (default: false)
	 * @param mixed $likely How likely is it (1-10) the result will be true?  1 = Always, 10 = Almost never.
	 * @return mixed the result.
	 *
	 * @codeCoverageIgnore
	 */
	public function getBool($true = true, $false = false, $likely = 2)
	{
		$i = $this->getInteger(1, 100);

		if ($i % $likely == 0) {
			return $true;
		} else {
			return $false;
		}
	}

	/**
	 * Generate a random date.
	 * @param array $params Associative array with following keys: minYear, maxYear, minMonth, maxMonth
	 * @param string $format date() format for return value.  Default: Y-m-d
	 * @return string formatted date string.
	 */
	public function getDate($params = [], $format = 'Y-m-d')
	{
		foreach ($params as $k => $v) {
			$$k = $v;
		}

		if (!isset($min_year)) {
			$min_year = date('Y') - 2;
		}

		if (!isset($max_year)) {
			$max_year = date('Y');
		}

		if (!isset($min_month)) {
			$min_month = 1;
		}

		if (!isset($max_month)) {
			$max_month = 12;
		}

		// Pick a random year and month within the valid ranges.
		$rand_year	= rand($min_year, $max_year);
		$rand_month	= rand($min_month, $max_month);

		// Create a date object using the first day of this random month/year.
		$date = DateTime::createFromFormat('Y-m-d', join('-', [$rand_year, $rand_month, '01']));

		// How many days in this random month?
		$days_in_month = $date->format('t');

		// Pick a day of the month.
		$rand_day = rand(1, $days_in_month);

		return DateTime::createFromFormat('Y-m-d', join('-', [$rand_year, $rand_month, $rand_day]))->format($format);
	}

	/**
	 * Generate a reasonable birth date.	 Default Age: 20-80 years.
	 *
	 * @param array $params Associative array with following keys: minYear, maxYear, minMonth, maxMonth
	 * @param string $format date() format for return value.  Default: Y-m-d
	 * @return string formatted date string.
	 */
	public function getBirthDate($params = [], $format = 'Y-m-d')
	{
		$params['min_year'] = array_key_exists('min_year', $params) ? $params['min_year'] : date('Y') - 80;
		$params['max_year'] = array_key_exists('max_year', $params) ? $params['max_year'] : date('Y') - 20;
		return $this->getDate($params, $format);
	}

	/**
	 * Get a future date. Suitable for DLN / CC Expiration
	 *
	 * @param string $format
	 * @return string formatted date string.
	 */
	public function getExpiration($format = 'm/Y')
	{
		$date_params = ['min_year' => date('Y'), 'max_year' => date('Y') + 3];
		return $this->getDate($date_params, $format);
	}

	/**
	 * Returns a DLN object that contains a driver license number, state, and expiration
	 *
	 * @param null $state_code
	 * @param int $min
	 * @param int $max
	 * @return \joshmoody\Mock\Entities\DriverLicense
	 */
	public function getDln($state_code = null, $min = 900000001, $max = 999999999)
	{
		$dln = new Entities\DriverLicense();

		$dln->number		= rand($min, $max);
		$dln->state			= !empty($state_code) ? $state_code : $this->getState();
		$dln->expiration	= $this->getExpiration();

		return $dln;
	}

	/**
	 * Generate a 9 digit number that could be an SSN for a given state.
	 * SSN Prefixes from http://socialsecuritynumerology.com/prefixes.php for
	 *
	 * @param null $state_code
	 * @return string
	 */
	public function getSsn($state_code = null)
	{
		if (empty($state_code)) {
			$state_code = $this->getState()->code;
		}

		/**
		 Prefixes 580-xx-xxxx and up are allocated to US Territories and other states.
		 The below array structure does not support multiple prefix ranges, but this will do for now.
		 We are looking for FAKE data, not COMPLETE data.
		 */
		$ranges = [];
		$ranges['NH'] = ['min_prefix' => 1, 'max_prefix' => 3];
		$ranges['ME'] = ['min_prefix' => 4, 'max_prefix' => 7];
		$ranges['VT'] = ['min_prefix' => 8, 'max_prefix' => 9];
		$ranges['MA'] = ['min_prefix' => 10, 'max_prefix' => 34];
		$ranges['RI'] = ['min_prefix' => 35, 'max_prefix' => 39];
		$ranges['CT'] = ['min_prefix' => 40, 'max_prefix' => 49];
		$ranges['NY'] = ['min_prefix' => 50, 'max_prefix' => 134];
		$ranges['NJ'] = ['min_prefix' => 135, 'max_prefix' => 158];
		$ranges['PA'] = ['min_prefix' => 159, 'max_prefix' => 211];
		$ranges['MD'] = ['min_prefix' => 212, 'max_prefix' => 220];
		$ranges['DE'] = ['min_prefix' => 221, 'max_prefix' => 222];
		$ranges['VA'] = ['min_prefix' => 223, 'max_prefix' => 231];
		$ranges['WV'] = ['min_prefix' => 232, 'max_prefix' => 236];
		$ranges['NC'] = ['min_prefix' => 237, 'max_prefix' => 246];
		$ranges['SC'] = ['min_prefix' => 247, 'max_prefix' => 251];
		$ranges['GA'] = ['min_prefix' => 252, 'max_prefix' => 260];
		$ranges['FL'] = ['min_prefix' => 263, 'max_prefix' => 267];
		$ranges['OH'] = ['min_prefix' => 268, 'max_prefix' => 302];
		$ranges['IN'] = ['min_prefix' => 303, 'max_prefix' => 317];
		$ranges['IL'] = ['min_prefix' => 318, 'max_prefix' => 361];
		$ranges['MI'] = ['min_prefix' => 362, 'max_prefix' => 386];
		$ranges['WI'] = ['min_prefix' => 387, 'max_prefix' => 399];
		$ranges['KY'] = ['min_prefix' => 400, 'max_prefix' => 407];
		$ranges['TN'] = ['min_prefix' => 408, 'max_prefix' => 415];
		$ranges['AL'] = ['min_prefix' => 416, 'max_prefix' => 424];
		$ranges['MI'] = ['min_prefix' => 425, 'max_prefix' => 428];
		$ranges['AR'] = ['min_prefix' => 429, 'max_prefix' => 432];
		$ranges['LA'] = ['min_prefix' => 433, 'max_prefix' => 439];
		$ranges['OK'] = ['min_prefix' => 440, 'max_prefix' => 448];
		$ranges['TX'] = ['min_prefix' => 449, 'max_prefix' => 467];
		$ranges['MN'] = ['min_prefix' => 468, 'max_prefix' => 477];
		$ranges['IA'] = ['min_prefix' => 478, 'max_prefix' => 485];
		$ranges['MO'] = ['min_prefix' => 486, 'max_prefix' => 500];
		$ranges['ND'] = ['min_prefix' => 501, 'max_prefix' => 502];
		$ranges['SD'] = ['min_prefix' => 503, 'max_prefix' => 504];
		$ranges['NE'] = ['min_prefix' => 505, 'max_prefix' => 508];
		$ranges['KS'] = ['min_prefix' => 509, 'max_prefix' => 515];
		$ranges['MT'] = ['min_prefix' => 516, 'max_prefix' => 517];
		$ranges['ID'] = ['min_prefix' => 518, 'max_prefix' => 519];
		$ranges['WY'] = ['min_prefix' => 520, 'max_prefix' => 520];
		$ranges['CO'] = ['min_prefix' => 521, 'max_prefix' => 524];
		$ranges['NM'] = ['min_prefix' => 525, 'max_prefix' => 525];
		$ranges['AZ'] = ['min_prefix' => 526, 'max_prefix' => 527];
		$ranges['UT'] = ['min_prefix' => 528, 'max_prefix' => 529];
		$ranges['NV'] = ['min_prefix' => 530, 'max_prefix' => 530];
		$ranges['WA'] = ['min_prefix' => 531, 'max_prefix' => 539];
		$ranges['OR'] = ['min_prefix' => 540, 'max_prefix' => 544];
		$ranges['CA'] = ['min_prefix' => 545, 'max_prefix' => 573];
		$ranges['AK'] = ['min_prefix' => 574, 'max_prefix' => 574];
		$ranges['DC'] = ['min_prefix' => 577, 'max_prefix' => 579];

		if (!array_key_exists($state_code, $ranges)) {
			// We don't have a range for this state. Choose a state at random from the list.
			$state_code = $this->fromArray(array_keys($ranges));
		}

		$prefix = rand($ranges[$state_code]['min_prefix'], $ranges[$state_code]['min_prefix']);
		$suffix = rand(100000, 999999);
		return str_pad($prefix, 3, '0', STR_PAD_LEFT) . str_pad($suffix, 6, '0', STR_PAD_LEFT);
	}

	/**
	 * Generate a First Name
	 *
	 * Uses US Census data to get 250 most popular names for both male and female
	 *
	 * @param string $gender Do you want a male or female name? (M/F).	If null, selects a gender at random.
	 */
	public function getFirstName($gender = null)
	{
		if (empty($gender)) {
			$gender = $this->getGender();
		}

		return FirstName::where('gender', $gender)->where('rank', '<=', 250)->orderByRaw(Database::random())->first()->name;
	}

	/**
	 * Returns Gender (M/F) at random.
	 *
	 * @return string $gender
	 */
	public function getGender()
	{
		return $this->fromArray(['F', 'M']);
	}

	/**
	 * Generate a Last Name
	 *
	 * Uses US Census data to get $max most popular names for both male and female and selects one at random
	 *
	 * Pool is only 250 most frequent last names.  Increase by passing a higher value for $max
	 *
	 * @param int $max How large should our pool of names be? Default: 250
	 * @return string Last Name
	 */
	public function getLastName($max = 250)
	{
		return LastName::where('rank', '<=', $max)->orderByRaw(Database::random())->first()->name;
	}

	/**
	 * Alias for get_firstname()
	 *
	 * @param string $gender Do you want a male or female name? (M/F).	If null, selects a gender at random.
	 * @return string
	 */
	public function getMiddleName($gender = null)
	{
		return $this->getFirstName($gender);
	}

	/**
	 * Returns a random character, A-Z
	 * @return string
	 */
	public function getLetter()
	{
		$letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
		return $this->fromArray($letters);
	}

	/**
	 * Returns a Full Name
	 *
	 * @param string $gender.  Will be used to make sure both First and Middle Name are for same gender.
	 * @return \joshmoody\Mock\Entities\FullName Gender included to avoid "A Boy Named Sue".
	 */
	public function getFullName($gender = null)
	{
		if (empty($gender)) {
			$gender = $this->getGender();
		}

		$person_name = new Entities\FullName;
		$person_name->first		= $this->getFirstName($gender);
		$person_name->middle	= $this->getMiddleName($gender);
		$person_name->last		= $this->getLastName();
		$person_name->gender	= $gender;

		return $person_name;
	}

	/**
	 * Return a street name
	 *
	 * @return string
	 */
	public function getStreet()
	{
		$number = rand(100, 9999);

		$street_name = Street::orderByRaw(Database::random())->first()->name;

		return $number . ' ' . $street_name;
	}

	/**
	 * Get an apartment number.
	 *
	 * @return string
	 */
	public function getApartment()
	{
		$types = ['Apt.', 'Apartment', 'Ste.', 'Suite', 'Box'];

		// @codeCoverageIgnoreStart
		if ($this->getBool(true, false)) {
			$extra = $this->getLetter();
		} else {
			$extra = $this->getInteger(1, 9999);
		}
		// @codeCoverageIgnoreEnd

		$type = $this->fromArray($types);
		return $type . ' ' . $extra;
	}

	/**
	 * Return a state
	 *
	 * @param null $state_code
	 * @return \joshmoody\Mock\Entities\State
	 */
	public function getState($state_code = null)
	{

		if (!empty($state_code)) {
			$res = Zipcode::where('state_code', $state_code)->orderByRaw(Database::random())->first();
		} else {
			$res = Zipcode::orderByRaw(Database::random())->first();
		}

		$State = new Entities\State;
		$State->code = $res->state_code;
		$State->name = $res->state;
		return $State;
	}

	/**
	 * @param null $state_code
	 * @return mixed
	 */
	public function getZip($state_code = null)
	{

		if (!empty($state_code)) {
			return Zipcode::where('state_code', $state_code)->orderByRaw(Database::random())->first()->zip;
		} else {
			return Zipcode::orderByRaw(Database::random())->first()->zip;
		}
	}

	/**
	 * @param bool $state_code
	 * @return mixed
	 */
	public function getCity($state_code = false)
	{
		if ($state_code) {
			return Zipcode::where('state_code', $state_code)->orderByRaw(Database::random())->first()->city;
		} else {
			return Zipcode::orderByRaw(Database::random())->first()->city;
		}
	}

	/**
	 * Return object with full street info
	 *
	 * @param null $state_code
	 * @param null $zip
	 * @return \joshmoody\Mock\Entities\Address
	 */
	public function getAddress($state_code = null, $zip = null)
	{
		$address = new Entities\Address;

		if (!empty($zip) && !empty($state_code)) {
			$result = Zipcode::where('zip', $zip)->where('state_code', $state_code)->orderByRaw(Database::random())->first();
		} elseif (!empty($zip)) {
			$result = Zipcode::where('zip', $zip)->orderByRaw(Database::random())->first();
		} elseif (!empty($state_code)) {
			$result = Zipcode::where('state_code', $state_code)->orderByRaw(Database::random())->first();
		} else {
			$result = Zipcode::orderByRaw(Database::random())->first();
		}

		$address->line_1 = $this->getStreet();

		// @codeCoverageIgnoreStart
		if ($this->getBool(true, false)) {
			$address->line_2 = $this->getApartment();
		} else {
			$address->line_2 = null;
		}
		// @codeCoverageIgnoreEnd

		$address->city = $result->city;
		$address->zip = $result->zip;
		$address->county = $result->county;

		$address->state = new Entities\State;
		$address->state->code = $result->state_code;
		$address->state->name = $result->state;

		return $address;
	}

	/**
	 * Return a Company Name.  Uses a random last name plus a suffix that looks like a company name.
	 * You can optionally pass a name to serve as the prefix
	 *
	 * @param null $base_name
	 * @return string
	 */
	public function getCompanyName($base_name = null)
	{
		$suffixes = ['Corporation', 'Company', 'Company, Limited', 'Computer Repair', 'Incorporated', 'and Sons', 'Group', 'Group, PLC', 'Furniture', 'Flowers', 'Sales', 'Systems', 'Tire', 'Auto', 'Plumbing', 'Roofing', 'Realty', 'Foods', 'Books'];

		if (empty($base_name)) {
			$base_name = $this->getLastName();
		}

		return $base_name . ' ' . $this->fromArray($suffixes);
	}

	/**
	 * Return a phone number
	 *
	 * @param null $state_code
	 * @param null $zip
	 * @param bool $include_toll_free
	 * @return string
	 */
	public function getPhone($state_code = null, $zip = null, $include_toll_free = false)
	{
		if (!empty($zip)) {
			$areacodes = Zipcode::where('zip', $zip)->orderByRaw(Database::random())->first()->area_codes;
		} else {
			// Get a random state if state not provided
			$state_code = !empty($state_code) ? $state_code : $this->getState()->code;

			// Get area codes appropriate for this state
			$areacodes = Zipcode::where('state_code', $state_code)->orderByRaw(Database::random())->first()->area_codes;
		}

		// Get list of valid area codes for the state/zip code
		$code_list = explode(',', $areacodes);

		// @codeCoverageIgnoreStart
		// Add some toll free numbers into the mix.
		if ($include_toll_free === true) {
			$code_list[] = 800;
			$code_list[] = 888;
			$code_list[] = 877;
			$code_list[] = 866;
			$code_list[] = 855;
		}
		// @codeCoverageIgnoreEnd

		// Get a random area code from valid area codes
		$areacode	= $this->fromArray($code_list);
		$prefix		= rand(100, 999);
		$number		= rand(1, 9999);

		return $areacode . '-' . $prefix . '-' . str_pad($number, 4, '0', STR_PAD_LEFT);
	}

	/**
	 * @param null $base
	 * @return string
	 */
	public function getDomain($base = null)
	{
		$domain = !empty($base) ? $base : $this->getLastName();

		$domain = preg_replace('/[^0-9a-z_A-Z]/', '', $domain);

		$tld = ['.com', '.net', '.us', '.biz'];
		return strtolower($domain) . $this->fromArray($tld);
	}

	/**
	 * @param null $domain
	 * @return string
	 */
	public function getUrl($domain = null)
	{
		$protocol = ['https://www.', 'http://www.', 'http://', 'https://'];

		$domain = !empty($domain) ? $domain : $this->getDomain();

		return $this->fromArray($protocol)	. $domain;
	}

	/**
	 * Get something that looks like an IP Address
	 * @return string
	 */
	public function getIp()
	{
		$parts = [];

		for ($i=0; $i<4; $i++) {
			$parts[] = $this->getInteger(0, 255);
		}

		return join('.', $parts);
	}

	/**
	 * @param null $person_name
	 * @return string
	 */
	public function getUsername($person_name = null)
	{
		if (empty($person_name)) {
			$person_name = $this->getFullName();
		}

		$usernames = [];

		# Example Person Name: John Doe.

		$usernames[] = $person_name->first; // john
		$usernames[] = $person_name->last; // doe
		$usernames[] = $person_name->first . '.' . $person_name->last; // john.doe
		$usernames[] = $person_name->first . $person_name->last; // johndoe
		$usernames[] = substr($person_name->first, 0, 1) . $person_name->last; //jdoe

		return strtolower($this->fromArray($usernames));
	}

	/**
	 * Return an email address.
	 * You can optionally pass a name to use in the address
	 *
	 * @param null $person_name
	 * @param null $domain
	 * @return string
	 */
	public function getEmail($person_name = null, $domain = null)
	{
		$username = $this->getUsername($person_name);

		$domains = [];
		$domains[] = !empty($domain) ? $domain : $this->getDomain();
		$domains[] = 'gmail.com';
		$domains[] = 'yahoo.com';
		$domains[] = 'me.com';
		$domains[] = 'msn.com';
		$domains[] = 'hotmail.com';

		$domain = $this->fromArray($domains);

		return preg_replace('/[^0-9a-z_A-Z.]/', '', strtolower($username)) . '@' . $domain;
	}

	/**
	 * Generate internet information.  Domain, Email, URL, IP Address, Username
	 *
	 * @access public
	 * @param mixed $person_name (default: null)
	 * @param mixed $company (default: null)
	 * @return \joshmoody\Mock\Entities\Internet
	 */
	public function getInternet($person_name = null, $company = null)
	{
		if (empty($person_name)) {
			$person_name = $this->getFullName();
		}

		$internet = new Entities\Internet();
		$internet->domain	= $this->getDomain($company);
		$internet->username	= $this->getUserName($person_name);
		$internet->email	= $this->getEmail($person_name, $internet->domain);
		$internet->url		= $this->getUrl($internet->domain);
		$internet->ip		= $this->getIp();

		return $internet;
	}

	/**
	 * Generate a credit card number.
	 *
	 * @access public
	 * @param mixed $weighted (default: true) - Make it more likely to return MasterCard or Visa
	 * @return \joshmoody\Mock\Entities\CreditCard
	 */
	public function getCreditCard($weighted = true)
	{
		// Get a random card type

		if ($weighted) {
			$weight[] = ['American Express', 1];
			$weight[] = ['Discover', 2];
			$weight[] = ['MasterCard', 10];
			$weight[] = ['Visa', 10];

			foreach ($weight as $w) {
				$type = $w[0];
				$count = $w[1];

				for ($i=0; $i<$count; $i++) {
					$card_types[] = $type;
				}
			}
		} else {
			// @codeCoverageIgnoreStart
			$card_types = ['American Express', 'Discover', 'MasterCard', 'Visa'];
			// @codeCoverageIgnoreEnd
		}

		$cc = new Entities\CreditCard;

		$cc->type = $this->fromArray($card_types);

		// Get a random card number appropriate for this type that passes Luhn/Mod10 check
		$cc->number = $this->getBankNumber($cc->type);

		// Get an expiration date
		$cc->expiration = $this->getExpiration();

		return $cc;
	}

	/**
	 * Generate bank account information.
	 *
	 * @access public
	 * @return \joshmoody\Mock\Entities\BankAccount
	 */
	public function getBank()
	{
		$bank_account = new Entities\BankAccount;

		$bank_account->type = $this->fromArray(['Checking', 'Savings']);
		$bank_account->name = $this->fromArray(['First National', 'Arvest', 'Regions', 'Metropolitan', 'Wells Fargo']);

		$bank_account->account = $this->getInteger('1000', '999999999');
		$bank_account->routing = $this->getBankNumber('Routing');

		return $bank_account;
	}

	/**
	 * Generate a Person object with all relevant attributes.
	 *
	 * @access public
	 * @param mixed $state_code (default: null)
	 * @return \joshmoody\Mock\Entities\Person
	 */
	public function getPerson($state_code = null)
	{
		$state_code = !empty($state_code) ? $state_code : $this->getState()->code;

		$person = new Entities\Person;

		$person->guid = $this->getGuid();
		$person->unique_hash = $this->getUniqueHash();

		$person->name = $this->getFullName(); // Returns an object with first, middle, last, and gender properties

		// @codeCoverageIgnoreStart
		if (rand(1, 100) % 5 == 0) {
			// Self employed?  Name the business after them.
			$person->company = $this->getCompanyName($person->name->last);
		} else {
			// Generate some random company name.
			$person->company = $this->getCompanyName();
		}
		// @codeCoverageIgnoreEnd

		# Primary address
		$person->address = $this->getAddress($state_code);

		# Secondary Address.  Mailing Address?	Use same zip code and primary address
		$person->address2 = $this->getAddress($state_code, $person->address->zip);

		$person->internet = $this->getInternet($person->name, $person->company);

		# Everyone has at least 2 or three phone numbers
		$person->phone	= new stdclass();
		$person->phone->home	= $this->getPhone($state_code, $person->address->zip);
		$person->phone->mobile	= $this->getPhone($state_code, $person->address->zip);
		$person->phone->work	= $this->getPhone($state_code, $person->address->zip);

		$person->ssn	= $this->getSsn($state_code);
		$person->dln	= $this->getDln($state_code);

		$person->dob	= $this->getBirthDate();

		# Payment Implements
		$person->credit_card = $this->getCreditCard();
		$person->bank_account = $this->getBank();

		return $person;
	}

	/**
	 * I've adapted a credit card / routing number generator to meet my needs here. Original copyright below.
	 * Numbers created here will pass the Luhn Mod-10 test.
	 *
	 * @param string $type
	 * @return string
	 */
	public function getBankNumber($type = 'Visa')
	{
		$visaPrefixList[] = '4539';
		$visaPrefixList[] = '4556';
		$visaPrefixList[] = '4916';
		$visaPrefixList[] = '4532';
		$visaPrefixList[] = '4929';
		$visaPrefixList[] = '40240071';
		$visaPrefixList[] = '4485';
		$visaPrefixList[] = '4716';
		$visaPrefixList[] = '4';

		$mastercardPrefixList[] = '51';
		$mastercardPrefixList[] = '52';
		$mastercardPrefixList[] = '53';
		$mastercardPrefixList[] = '54';
		$mastercardPrefixList[] = '55';

		$amexPrefixList[] = '34';
		$amexPrefixList[] = '37';

		$discoverPrefixList[] = '6011';

		$routingPrefixList[] = '01';
		$routingPrefixList[] = '02';
		$routingPrefixList[] = '03';
		$routingPrefixList[] = '04';
		$routingPrefixList[] = '05';
		$routingPrefixList[] = '06';
		$routingPrefixList[] = '07';
		$routingPrefixList[] = '08';
		$routingPrefixList[] = '09';
		$routingPrefixList[] = '10';
		$routingPrefixList[] = '11';
		$routingPrefixList[] = '12';

		switch ($type) {
			case 'Visa':
				$bank_number = $this->completedBankNumber($visaPrefixList, 16);
				break;
			case 'Master Card':
				$bank_number = $this->completedBankNumber($mastercardPrefixList, 16);
				break;
			case 'American Express':
				$bank_number = $this->completedBankNumber($amexPrefixList, 15);
				break;
			case 'Discover':
				$bank_number = $this->completedBankNumber($discoverPrefixList, 16);
				break;
			case 'Routing':
				$bank_number = $this->completedBankNumber($routingPrefixList, 9);
				break;
			default:
				$bank_number = $this->completedBankNumber($visaPrefixList, 16);
				break;
		}

		return $bank_number;
	}

	/**
	 * @param array $prefixlist The start of the CC number as a string, any number of digits.
	 * @param $length Length of the CC number to generate. Typically 13 or 16
	 * @return string
	 */
	protected function completedBankNumber($prefixlist, $length)
	{

		$prefix = $prefixlist[rand(0, count($prefixlist)-1)];
		$ccnumber = $prefix;

		# generate digits

		while (strlen($ccnumber) < ($length - 1)) {
			$ccnumber .= rand(0, 9);
		}

		# Calculate sum

		$sum = 0;
		$pos = 0;

		$reversedCCnumber = strrev($ccnumber);

		while ($pos < $length - 1) {

			$odd = $reversedCCnumber[ $pos ] * 2;

			if ($odd > 9) {
				$odd -= 9;
			}

			$sum += $odd;

			if ($pos != ($length - 2)) {
				$sum += $reversedCCnumber[ $pos +1 ];
			}

			$pos += 2;
		}

		# Calculate check digit

		$checkdigit = ((floor($sum/10) + 1) * 10 - $sum) % 10;
		$ccnumber .= $checkdigit;

		return $ccnumber;
	}
}

/**
 * The credit card/bank account generator is not my code, I've only modified slightly to meet my needs.
 * Original copyright below.
 */

/**
 * PHP credit card number generator
 * Copyright (C) 2006 Graham King graham@darkcoding.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */


<?php

namespace PhpOffice\PhpSpreadsheet\Style;

use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;

class Fill extends Supervisor
{
    // Fill types
    const FILL_NONE = 'none';
    const FILL_SOLID = 'solid';
    const FILL_GRADIENT_LINEAR = 'linear';
    const FILL_GRADIENT_PATH = 'path';
    const FILL_PATTERN_DARKDOWN = 'darkDown';
    const FILL_PATTERN_DARKGRAY = 'darkGray';
    const FILL_PATTERN_DARKGRID = 'darkGrid';
    const FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal';
    const FILL_PATTERN_DARKTRELLIS = 'darkTrellis';
    const FILL_PATTERN_DARKUP = 'darkUp';
    const FILL_PATTERN_DARKVERTICAL = 'darkVertical';
    const FILL_PATTERN_GRAY0625 = 'gray0625';
    const FILL_PATTERN_GRAY125 = 'gray125';
    const FILL_PATTERN_LIGHTDOWN = 'lightDown';
    const FILL_PATTERN_LIGHTGRAY = 'lightGray';
    const FILL_PATTERN_LIGHTGRID = 'lightGrid';
    const FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal';
    const FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis';
    const FILL_PATTERN_LIGHTUP = 'lightUp';
    const FILL_PATTERN_LIGHTVERTICAL = 'lightVertical';
    const FILL_PATTERN_MEDIUMGRAY = 'mediumGray';

    /**
     * @var int
     */
    public $startcolorIndex;

    /**
     * @var int
     */
    public $endcolorIndex;

    /**
     * Fill type.
     *
     * @var string
     */
    protected $fillType = self::FILL_NONE;

    /**
     * Rotation.
     *
     * @var float
     */
    protected $rotation = 0;

    /**
     * Start color.
     *
     * @var Color
     */
    protected $startColor;

    /**
     * End color.
     *
     * @var Color
     */
    protected $endColor;

    /**
     * Create a new Fill.
     *
     * @param bool $isSupervisor Flag indicating if this is a supervisor or not
     *                                    Leave this value at default unless you understand exactly what
     *                                        its ramifications are
     * @param bool $isConditional Flag indicating if this is a conditional style or not
     *                                    Leave this value at default unless you understand exactly what
     *                                        its ramifications are
     */
    public function __construct($isSupervisor = false, $isConditional = false)
    {
        // Supervisor?
        parent::__construct($isSupervisor);

        // Initialise values
        if ($isConditional) {
            $this->fillType = null;
        }
        $this->startColor = new Color(Color::COLOR_WHITE, $isSupervisor, $isConditional);
        $this->endColor = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional);

        // bind parent if we are a supervisor
        if ($isSupervisor) {
            $this->startColor->bindParent($this, 'startColor');
            $this->endColor->bindParent($this, 'endColor');
        }
    }

    /**
     * Get the shared style component for the currently active cell in currently active sheet.
     * Only used for style supervisor.
     *
     * @return Fill
     */
    public function getSharedComponent()
    {
        return $this->parent->getSharedComponent()->getFill();
    }

    /**
     * Build style array from subcomponents.
     *
     * @param array $array
     *
     * @return array
     */
    public function getStyleArray($array)
    {
        return ['fill' => $array];
    }

    /**
     * Apply styles from array.
     *
     * <code>
     * $spreadsheet->getActiveSheet()->getStyle('B2')->getFill()->applyFromArray(
     *     [
     *         'fillType' => Fill::FILL_GRADIENT_LINEAR,
     *         'rotation' => 0,
     *         'startColor' => [
     *             'rgb' => '000000'
     *         ],
     *         'endColor' => [
     *             'argb' => 'FFFFFFFF'
     *         ]
     *     ]
     * );
     * </code>
     *
     * @param array $pStyles Array containing style information
     *
     * @throws PhpSpreadsheetException
     *
     * @return Fill
     */
    public function applyFromArray(array $pStyles)
    {
        if ($this->isSupervisor) {
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
        } else {
            if (isset($pStyles['fillType'])) {
                $this->setFillType($pStyles['fillType']);
            }
            if (isset($pStyles['rotation'])) {
                $this->setRotation($pStyles['rotation']);
            }
            if (isset($pStyles['startColor'])) {
                $this->getStartColor()->applyFromArray($pStyles['startColor']);
            }
            if (isset($pStyles['endColor'])) {
                $this->getEndColor()->applyFromArray($pStyles['endColor']);
            }
            if (isset($pStyles['color'])) {
                $this->getStartColor()->applyFromArray($pStyles['color']);
                $this->getEndColor()->applyFromArray($pStyles['color']);
            }
        }

        return $this;
    }

    /**
     * Get Fill Type.
     *
     * @return string
     */
    public function getFillType()
    {
        if ($this->isSupervisor) {
            return $this->getSharedComponent()->getFillType();
        }

        return $this->fillType;
    }

    /**
     * Set Fill Type.
     *
     * @param string $pValue Fill type, see self::FILL_*
     *
     * @return Fill
     */
    public function setFillType($pValue)
    {
        if ($this->isSupervisor) {
            $styleArray = $this->getStyleArray(['fillType' => $pValue]);
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
        } else {
            $this->fillType = $pValue;
        }

        return $this;
    }

    /**
     * Get Rotation.
     *
     * @return float
     */
    public function getRotation()
    {
        if ($this->isSupervisor) {
            return $this->getSharedComponent()->getRotation();
        }

        return $this->rotation;
    }

    /**
     * Set Rotation.
     *
     * @param float $pValue
     *
     * @return Fill
     */
    public function setRotation($pValue)
    {
        if ($this->isSupervisor) {
            $styleArray = $this->getStyleArray(['rotation' => $pValue]);
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
        } else {
            $this->rotation = $pValue;
        }

        return $this;
    }

    /**
     * Get Start Color.
     *
     * @return Color
     */
    public function getStartColor()
    {
        return $this->startColor;
    }

    /**
     * Set Start Color.
     *
     * @param Color $pValue
     *
     * @throws PhpSpreadsheetException
     *
     * @return Fill
     */
    public function setStartColor(Color $pValue)
    {
        // make sure parameter is a real color and not a supervisor
        $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue;

        if ($this->isSupervisor) {
            $styleArray = $this->getStartColor()->getStyleArray(['argb' => $color->getARGB()]);
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
        } else {
            $this->startColor = $color;
        }

        return $this;
    }

    /**
     * Get End Color.
     *
     * @return Color
     */
    public function getEndColor()
    {
        return $this->endColor;
    }

    /**
     * Set End Color.
     *
     * @param Color $pValue
     *
     * @throws PhpSpreadsheetException
     *
     * @return Fill
     */
    public function setEndColor(Color $pValue)
    {
        // make sure parameter is a real color and not a supervisor
        $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue;

        if ($this->isSupervisor) {
            $styleArray = $this->getEndColor()->getStyleArray(['argb' => $color->getARGB()]);
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
        } else {
            $this->endColor = $color;
        }

        return $this;
    }

    /**
     * Get hash code.
     *
     * @return string Hash code
     */
    public function getHashCode()
    {
        if ($this->isSupervisor) {
            return $this->getSharedComponent()->getHashCode();
        }

        return md5(
            $this->getFillType() .
            $this->getRotation() .
            $this->getStartColor()->getHashCode() .
            $this->getEndColor()->getHashCode() .
            __CLASS__
        );
    }
}