"""Tag and node definition for the built-in "tablerow" tag."""

import collections.abc
import math
import sys

from itertools import islice

from typing import List
from typing import TextIO
from typing import Iterator

from liquid.token import Token
from liquid.token import TOKEN_TAG
from liquid.token import TOKEN_EXPRESSION

from liquid.ast import Node
from liquid.ast import BlockNode

from liquid.context import Context
from liquid.expression import LoopExpression
from liquid.lex import tokenize_loop_expression

from liquid.parse import expect
from liquid.parse import parse_loop_expression
from liquid.parse import get_parser

from liquid.tag import Tag
from liquid.stream import TokenStream


TAG_TABLEROW = sys.intern("tablerow")
TAG_ENDTABLEROW = sys.intern("endtablerow")


class TableRow(collections.abc.Mapping):
    """Table row helper variables."""

    __slots__ = (
        "name",
        "it",
        "length",
        "ncols",
        "item",
        "first",
        "last",
        "index",
        "index0",
        "rindex",
        "rindex0",
        "col",
        "col0",
        "col_first",
        "col_last",
        "keys",
        "row",
        "nrows",
    )

    def __init__(self, name: str, it: Iterator, length: int, ncols: int):
        self.name = name
        self.it = it
        self.length = length
        self.ncols = ncols
        self.item = None

        self.first = False
        self.last = False
        self.index = 0
        self.index0 = -1
        self.rindex = self.length + 1
        self.rindex0 = self.length

        self.col = 0
        self.col0 = -1
        self.col_first = True
        self.col_last = False

        # Zero based row counter is not exposed to templates.
        self.row = 0
        self.nrows = math.ceil(self.length / self.ncols)

        self.keys: List[str] = [
            "length",
            "index",
            "index0",
            "rindex",
            "rindex0",
            "first",
            "last",
            "col",
            "col0",
            "col_first",
            "col_last",
        ]

    def __repr__(self):  # pragma: no cover
        return f"TableRow(name='{self.name}', length={self.length})"

    def __getitem__(self, key):
        if key in self.keys:
            return getattr(self, key)
        raise KeyError(key)

    def __len__(self):
        return len(self.keys)

    def __iter__(self):
        return self

    def __next__(self):
        return next(self.it)

    def step(self):
        """Set the value for the current/next loop iteration and update forloop
        helper variables."""
        self.index += 1
        self.index0 += 1
        self.rindex -= 1
        self.rindex0 -= 1

        if self.index0 == 0:
            self.first = True
        else:
            self.first = False

        if self.rindex0 == 0:
            self.last = True
        else:
            self.last = False

        self.col0 = self.index0 % self.ncols
        self.col = self.col0 + 1

        if self.col == 1:
            self.col_first = True
        else:
            self.col_first = False

        if self.col == self.ncols:
            self.col_last = True
        else:
            self.col_last = False

        if self.col == 1:
            self.row += 1


class TablerowNode(Node):
    """Parse tree node for the built-in "tablerow" tag."""

    __slots__ = ("tok", "expression", "block")

    def __init__(
        self,
        tok: Token,
        expression: LoopExpression,
        block: BlockNode,
    ):
        self.tok = tok
        self.expression = expression
        self.block = block

    def __str__(self) -> str:
        return f"tablerow({ self.expression }) {{ {self.block} }}"

    def render_to_output(self, context: Context, buffer: TextIO):
        name = self.expression.name
        loop_iter, length = self.expression.evaluate(context)

        if self.expression.cols:
            cols = self.expression.cols.evaluate(context)
            assert isinstance(cols, int)
        else:
            cols = length

        loop_iter = grouper(loop_iter, cols)
        tablerow = TableRow(name, loop_iter, length, cols)

        namespace = {
            "tablerowloop": tablerow,
            name: None,
        }

        with context.extend(namespace):
            for i, row in enumerate(tablerow):
                buffer.write(f'<tr class="row{i+1}">')

                for j, itm in enumerate(row):
                    tablerow.step()
                    namespace[name] = itm

                    buffer.write(f'<td class="col{j+1}">')
                    self.block.render(context=context, buffer=buffer)
                    buffer.write("</td>")

                buffer.write("</tr>")


class TablerowTag(Tag):
    """The built-in "tablerow" tag."""

    name = TAG_TABLEROW
    end = TAG_ENDTABLEROW

    def parse(self, stream: TokenStream) -> TablerowNode:
        parser = get_parser(self.env)

        expect(stream, TOKEN_TAG, value=TAG_TABLEROW)
        tok = stream.current
        stream.next_token()

        expect(stream, TOKEN_EXPRESSION)
        expr_iter = tokenize_loop_expression(stream.current.value)
        loop_expression = parse_loop_expression(TokenStream(expr_iter))
        stream.next_token()

        block = parser.parse_block(stream, (TAG_ENDTABLEROW,))
        expect(stream, TOKEN_TAG, value=TAG_ENDTABLEROW)

        return TablerowNode(tok, expression=loop_expression, block=block)


def grouper(iterator: Iterator, n):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3) --> ABC DEF G"
    return iter(lambda: tuple(islice(iterator, n)), ())
