<?php

namespace LWVendor\PhpOffice\PhpSpreadsheet\Cell;

use LWVendor\PhpOffice\PhpSpreadsheet\Exception;
use LWVendor\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class CellRange implements AddressRange
{
    /**
     * @var CellAddress
     */
    protected $from;
    /**
     * @var CellAddress
     */
    protected $to;
    public function __construct(CellAddress $from, CellAddress $to)
    {
        $this->validateFromTo($from, $to);
    }
    private function validateFromTo(CellAddress $from, CellAddress $to) : void
    {
        // Identify actual top-left and bottom-right values (in case we've been given top-right and bottom-left)
        $firstColumn = \min($from->columnId(), $to->columnId());
        $firstRow = \min($from->rowId(), $to->rowId());
        $lastColumn = \max($from->columnId(), $to->columnId());
        $lastRow = \max($from->rowId(), $to->rowId());
        $fromWorksheet = $from->worksheet();
        $toWorksheet = $to->worksheet();
        $this->validateWorksheets($fromWorksheet, $toWorksheet);
        $this->from = $this->cellAddressWrapper($firstColumn, $firstRow, $fromWorksheet);
        $this->to = $this->cellAddressWrapper($lastColumn, $lastRow, $toWorksheet);
    }
    private function validateWorksheets(?Worksheet $fromWorksheet, ?Worksheet $toWorksheet) : void
    {
        if ($fromWorksheet !== null && $toWorksheet !== null) {
            // We could simply compare worksheets rather than worksheet titles; but at some point we may introduce
            //    support for 3d ranges; and at that point we drop this check and let the validation fall through
            //    to the check for same workbook; but unless we check on titles, this test will also detect if the
            //    worksheets are in different spreadsheets, and the next check will never execute or throw its
            //    own exception.
            if ($fromWorksheet->getTitle() !== $toWorksheet->getTitle()) {
                throw new Exception('3d Cell Ranges are not supported');
            } elseif ($fromWorksheet->getParent() !== $toWorksheet->getParent()) {
                throw new Exception('Worksheets must be in the same spreadsheet');
            }
        }
    }
    private function cellAddressWrapper(int $column, int $row, ?Worksheet $worksheet = null) : CellAddress
    {
        $cellAddress = Coordinate::stringFromColumnIndex($column) . (string) $row;
        return new class($cellAddress, $worksheet) extends CellAddress
        {
            public function nextRow(int $offset = 1) : CellAddress
            {
                /** @var CellAddress $result */
                $result = parent::nextRow($offset);
                $this->rowId = $result->rowId;
                $this->cellAddress = $result->cellAddress;
                return $this;
            }
            public function previousRow(int $offset = 1) : CellAddress
            {
                /** @var CellAddress $result */
                $result = parent::previousRow($offset);
                $this->rowId = $result->rowId;
                $this->cellAddress = $result->cellAddress;
                return $this;
            }
            public function nextColumn(int $offset = 1) : CellAddress
            {
                /** @var CellAddress $result */
                $result = parent::nextColumn($offset);
                $this->columnId = $result->columnId;
                $this->columnName = $result->columnName;
                $this->cellAddress = $result->cellAddress;
                return $this;
            }
            public function previousColumn(int $offset = 1) : CellAddress
            {
                /** @var CellAddress $result */
                $result = parent::previousColumn($offset);
                $this->columnId = $result->columnId;
                $this->columnName = $result->columnName;
                $this->cellAddress = $result->cellAddress;
                return $this;
            }
        };
    }
    public function from() : CellAddress
    {
        // Re-order from/to in case the cell addresses have been modified
        $this->validateFromTo($this->from, $this->to);
        return $this->from;
    }
    public function to() : CellAddress
    {
        // Re-order from/to in case the cell addresses have been modified
        $this->validateFromTo($this->from, $this->to);
        return $this->to;
    }
    public function __toString() : string
    {
        // Re-order from/to in case the cell addresses have been modified
        $this->validateFromTo($this->from, $this->to);
        if ($this->from->cellAddress() === $this->to->cellAddress()) {
            return "{$this->from->fullCellAddress()}";
        }
        $fromAddress = $this->from->fullCellAddress();
        $toAddress = $this->to->cellAddress();
        return "{$fromAddress}:{$toAddress}";
    }
}
