<?php

namespace Mailbox\Infrastructure\ImapAdapter;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
use Zend\Mail\Exception\RuntimeException;
use Zend\Mail\Protocol\Imap;

/**
 * Class ImapProtocol
 *
 * @author Aleksei Kuznetsov
 */
class ImapProtocol extends Imap implements LoggerAwareInterface
{
    use LoggerAwareTrait;


    public function __construct($host = '', $port = null, $ssl = false)
    {
        $this->logger = new NullLogger();
        parent::__construct($host, $port, $ssl);
    }


    public function sendRequest($command, $tokens = [], &$tag = null)
    {

        if (! $tag) {
            ++$this->tagCount;
            $tag = 'TAG' . $this->tagCount;
        }

        $line = $tag . ' ' . $command;

        foreach ($tokens as $token) {
            if (is_array($token)) {
                $commandRequest = $line . ' ' . $token[0] . "\r\n";

                $this->logger->debug($commandRequest);

                if (fwrite($this->socket, $commandRequest) === false) {
                    throw new RuntimeException('cannot write - connection closed?');
                }
                if (! $this->assumedNextLine('+ ')) {
                    throw new RuntimeException('cannot send literal string');
                }
                $line = $token[1];
            } else {
                $line .= ' ' . $token;
            }
        }

        $this->logger->debug($line);

        if (fwrite($this->socket, $line . "\r\n") === false) {
            throw new RuntimeException('cannot write - connection closed?');
        }
    }

    /**
     * добавлена отладка
     *
     * @param  string       $tag       the tag of your request
     * @param  bool         $dontParse if true every line is returned unparsed instead of
     *                                 the decoded tokens
     * @return null|bool|array tokens if success, false if error, null if bad request
     */
    public function readResponse($tag, $dontParse = false)
    {
        $lines = [];
        $tokens = null; // define $tokens variable before first use

        while (! $this->readLine($tokens, $tag, $dontParse)) {
            $lines[] = $tokens;
        }

        if ($dontParse) {
            // last to chars are still needed for response code

            $this->logger->debug($tokens);

            $tokens = [substr($tokens, 0, 2)];
        }

        // last line has response code
        if ($tokens[0] == 'OK') {
            return $lines ? $lines : true;
        } elseif ($tokens[0] == 'NO') {
            return false;
        }
        return;
    }

    protected function decodeLine($line)
    {
        $tokens = [];
        $stack = [];

        /*
            We start to decode the response here. The understood tokens are:
                literal
                "literal" or also "lit\\er\"al"
                {bytes}<NL>literal
                (literals*)
            All tokens are returned in an array. Literals in braces (the last understood
            token in the list) are returned as an array of tokens. I.e. the following response:
                "foo" baz {3}<NL>bar ("f\\\"oo" bar)
            would be returned as:
                array('foo', 'baz', 'bar', array('f\\\"oo', 'bar'));

            // TODO: add handling of '[' and ']' to parser for easier handling of response text
        */
        //  replace any trailing <NL> including spaces with a single space
        $line = rtrim($line) . ' ';


        while (($pos = strpos($line, ' ')) !== false) {
            $token = substr($line, 0, $pos);
            if (!strlen($token)) {
                continue;
            }
            while ($token[0] == '(') {
                array_push($stack, $tokens);
                $tokens = [];
                $token = substr($token, 1);
            }
            if ($token[0] == '"') {
                if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) {
                    $tokens[] = $matches[1];
                    $line = substr($line, strlen($matches[0]));
                    continue;
                }
            }
            if ($token[0] == '{') {
                $endPos = strpos($token, '}');
                $chars = substr($token, 1, $endPos - 1);
                if (is_numeric($chars)) {
                    $token = '';

                    $sum = 0;

                    //добавили обработку исключения на случай если не читается
                    try {
                        while ($sum < $chars) {
                            $linePart = $this->nextLine();
                            $sum += strlen($linePart);



  //                          $this->logger->debug("linepart:" . $linePart);

                            $trimPart = trim($linePart);



                            if ($trimPart    === ")" || stripos($trimPart, "FLAGS") === 0) {
                            //if( preg_match("~.*(\))~is",trim($linePart))){

                                throw new RuntimeException("не запланированный признак окончания строки");
                            }

                            $token .=  $linePart;
                        }
                        $line = '';
                        if ($sum > $chars) {
                            $line = substr($token, $chars);
                            //$this->logger->debug("finish linepart:" . $line);
                            $token = substr($token, 0, $chars);
                        } else {
                            $linePart = $this->nextLine();
                           // $this->logger->debug("finish linepart:" . $linePart);
                            $line .=  $linePart;
                        }

                        $tokens[] = $token;
                        $line = trim($line) . ' ';

                        continue;
                    } catch (RuntimeException $ex) {
                        $this->logger->error($ex->getMessage());

                        $orgLine = trim($trimPart);

                        $line = preg_replace("~(.*)\)~is", "$1", $orgLine);


                        $tokens[] = $token;

                        if (!empty($line) && $line !== $orgLine) {
                            $tokens[] = $line;
                        }


                        //$line = ' ';
                    }
                }
            }

            if ($stack && $token[strlen($token) - 1] == ')') {
              //  $this->logger->debug("нашли ) в " . $token);

                // closing braces are not separated by spaces, so we need to count them
                $braces = strlen($token);
                $token = rtrim($token, ')');
                // only count braces if more than one
                $braces -= strlen($token) + 1;
                // only add if token had more than just closing braces
                if (rtrim($token) != '') {
                    $tokens[] = rtrim($token);
                }
                $token = $tokens;
                $tokens = array_pop($stack);
                // special handline if more than one closing brace
                while ($braces-- > 0) {
                    $tokens[] = $token;
                    $token = $tokens;
                    $tokens = array_pop($stack);
                }
            } else {
                //$this->logger->debug("НЕ нашли ) в " . $token);
            }
            $tokens[] = $token;
            $line = substr($line, $pos + 1);
        }


        // maybe the server forgot to send some closing braces
        while ($stack) {
            $child = $tokens;
            $tokens = array_pop($stack);
            $tokens[] = $child;
        }

        

        return $tokens;
    }
}
