#! python3

from sys import argv
from pathlib import Path
from re import compile
from enum import Enum
from inflection import camelize


RE_CLASS = compile(r'^use [a-zA-Z_][a-zA-Z0-9_\\]*;$')
RE_PARAM = compile(r'^[a-zA-Z_][a-zA-Z0-9_]* \$[a-zA-Z_][a-zA-Z0-9_]*,?$')

PARAM_BEGIN = r'function ('
PARAM_END = r') {'
PARAM_EMPTY = r'function () {'
HEAD_END = r'?>'
FOOT_BEGIN = r'<?php'
FOOT_END = r'};'


class Stage(Enum):
    CLASS = 1
    PARAMETER = 2
    TEMPLATE = 3


def partition(file):
    classes = [
        r'use Exception;',
        r'use lzx\html\Template;',
    ]
    parameters = []
    template = []

    stage = Stage.CLASS
    for line_no, line in enumerate(file, 1):
        if line_no == 1:
            continue

        line = line.strip()

        if stage == Stage.CLASS:
            if not line:
                continue

            if RE_CLASS.fullmatch(line):
                classes.append(line)
            elif PARAM_BEGIN == line or PARAM_EMPTY == line:
                stage = Stage.PARAMETER
                continue
            else:
                raise Exception(f'Error: line {line_no}: {line}')
        elif stage == Stage.PARAMETER:
            if RE_PARAM.fullmatch(line):
                parameters.append(line.rstrip(','))
            elif PARAM_END == line:
                continue
            elif HEAD_END == line:
                stage = Stage.TEMPLATE
                continue
            else:
                raise Exception(f'Error: line {line_no}: {line}')
        elif stage == Stage.TEMPLATE:
            template.append(line)

    if len(template) < 2 or template[-2] != FOOT_BEGIN or template[-1] != FOOT_END:
        raise Exception(
            f'Error: template should end with {FOOT_BEGIN}' + "\n" + FOOT_END)

    template = template[:-2]
    classes = list(set(classes))
    classes.sort()

    return (classes, parameters, template)


def func(parameter):
    var_type = parameter.split(' ')[0]
    var_name = parameter.split('$')[-1]
    return '''
    public function get''' + camelize(var_name) + f'(): ?{var_type}' + '''
    {
        if (array_key_exists(''' + f"'{var_name}'" + ''', $this->data)) {
            return $this->data''' + f"['{var_name}'];" + '''
        }
        return null;
    }

    public function set''' + camelize(var_name) + f'({parameter}): self' + '''
    {
        if ($this->cache) {
            throw new Exception(self::FINALIZED);
        }
        $this->data''' + f"['{var_name}'] = ${var_name};" + '''
        return $this;
    }
'''


def tpl_str(lines):
    for i in range(len(lines)):
        if lines[i] == r'<?php':
            lines[i] = r'<?php '
        elif lines[i] == r'?>':
            lines[i] = r' ?>'

    out = ''.join(lines)
    # preserve newline for TEXT (non-HTML) template
    if out.replace('?>', '').find('>') < 0:
        out = "\n".join(lines)

    return out


def php(namespace, cls_name, classes, parameters, template):
    return r'''<?php

declare(strict_types=1);

/**
 * DO NOT EDIT
 * generated by script/build_template.py
 */

namespace ''' + namespace + ''';

''' + "\n".join(classes) + r'''

class ''' + cls_name + r''' extends Template
{
    public function __construct()
    {
    }
''' + ''.join(func(p) for p in parameters) + r'''
    public function __toString()
    {
        if (!$this->cache) {
            foreach ($this->onBeforeRender as $callback) {
                $callback($this);
            }

            extract($this->data);
            ob_start();
?>

''' + tpl_str(template) + r'''

<?php
            $output = ob_get_clean();
            $this->cache = trim($output);
        }
        return $this->cache;
    }
}
'''


if __name__ == '__main__':
    for input_file in (Path(__file__).parent.parent / 'server' / 'theme' / 'roselife').glob('**/*.tpl.php'):
        cls_name = camelize(input_file.name.replace(
            '.tpl.php', '').replace('.', '_'))

        p = input_file.absolute().with_name(f'{cls_name}.php').parts
        i = p.index('theme')
        output_file = Path(*p[:i]).joinpath('gen', *p[i:])

        output_file.parent.mkdir(parents=True, exist_ok=True)

        p = output_file.parent.parts
        i = p.index('theme')
        namespace = 'site\\gen\\' + '\\'.join(p[i:])

        php_current = ''
        if output_file.exists():
            with output_file.open() as output:
                php_current = output.read()

        with input_file.open() as input:
            classes, parameters, template = partition(input)
            php_new = php(namespace, cls_name, classes, parameters, template)

            if php_new != php_current:
                with output_file.open('w') as output:
                    output.write(php_new)
                print(f'updated: {input_file}')

#! python3

from sys import argv
from pathlib import Path
from re import compile
from enum import Enum
from inflection import camelize


RE_CLASS = compile(r'^use [a-zA-Z_][a-zA-Z0-9_\\]*;$')
RE_PARAM = compile(r'^[a-zA-Z_][a-zA-Z0-9_]* \$[a-zA-Z_][a-zA-Z0-9_]*,?$')

PARAM_BEGIN = r'function ('
PARAM_END = r') {'
PARAM_EMPTY = r'function () {'
HEAD_END = r'?>'
FOOT_BEGIN = r'<?php'
FOOT_END = r'};'


class Stage(Enum):
    CLASS = 1
    PARAMETER = 2
    TEMPLATE = 3


def partition(file):
    classes = [
        r'use Exception;',
        r'use lzx\html\Template;',
    ]
    parameters = []
    template = []

    stage = Stage.CLASS
    for line_no, line in enumerate(file, 1):
        if line_no == 1:
            continue

        line = line.strip()

        if stage == Stage.CLASS:
            if not line:
                continue

            if RE_CLASS.fullmatch(line):
                classes.append(line)
            elif PARAM_BEGIN == line or PARAM_EMPTY == line:
                stage = Stage.PARAMETER
                continue
            else:
                raise Exception(f'Error: line {line_no}: {line}')
        elif stage == Stage.PARAMETER:
            if RE_PARAM.fullmatch(line):
                parameters.append(line.rstrip(','))
            elif PARAM_END == line:
                continue
            elif HEAD_END == line:
                stage = Stage.TEMPLATE
                continue
            else:
                raise Exception(f'Error: line {line_no}: {line}')
        elif stage == Stage.TEMPLATE:
            template.append(line)

    if len(template) < 2 or template[-2] != FOOT_BEGIN or template[-1] != FOOT_END:
        raise Exception(
            f'Error: template should end with {FOOT_BEGIN}' + "\n" + FOOT_END)

    template = template[:-2]
    classes = list(set(classes))
    classes.sort()

    return (classes, parameters, template)


def func(parameter):
    var_type = parameter.split(' ')[0]
    var_name = parameter.split('$')[-1]
    return '''
    public function get''' + camelize(var_name) + f'(): ?{var_type}' + '''
    {
        if (array_key_exists(''' + f"'{var_name}'" + ''', $this->data)) {
            return $this->data''' + f"['{var_name}'];" + '''
        }
        return null;
    }

    public function set''' + camelize(var_name) + f'({parameter}): self' + '''
    {
        if ($this->cache) {
            throw new Exception(self::FINALIZED);
        }
        $this->data''' + f"['{var_name}'] = ${var_name};" + '''
        return $this;
    }
'''


def tpl_str(lines):
    for i in range(len(lines)):
        if lines[i] == r'<?php':
            lines[i] = r'<?php '
        elif lines[i] == r'?>':
            lines[i] = r' ?>'

    out = ''.join(lines)
    # preserve newline for TEXT (non-HTML) template
    if out.replace('?>', '').find('>') < 0:
        out = "\n".join(lines)

    return out


def php(namespace, cls_name, classes, parameters, template):
    return r'''<?php

declare(strict_types=1);

/**
 * DO NOT EDIT
 * generated by script/build_template.py
 */

namespace ''' + namespace + ''';

''' + "\n".join(classes) + r'''

class ''' + cls_name + r''' extends Template
{
    public function __construct()
    {
    }
''' + ''.join(func(p) for p in parameters) + r'''
    public function __toString()
    {
        if (!$this->cache) {
            foreach ($this->onBeforeRender as $callback) {
                $callback($this);
            }

            extract($this->data);
            ob_start();
?>

''' + tpl_str(template) + r'''

<?php
            $output = ob_get_clean();
            $this->cache = trim($output);
        }
        return $this->cache;
    }
}
'''


if __name__ == '__main__':
    for input_file in (Path(__file__).parent.parent / 'server' / 'theme' / 'roselife').glob('**/*.tpl.php'):
        cls_name = camelize(input_file.name.replace(
            '.tpl.php', '').replace('.', '_'))

        p = input_file.absolute().with_name(f'{cls_name}.php').parts
        i = p.index('theme')
        output_file = Path(*p[:i]).joinpath('gen', *p[i:])

        output_file.parent.mkdir(parents=True, exist_ok=True)

        p = output_file.parent.parts
        i = p.index('theme')
        namespace = 'site\\gen\\' + '\\'.join(p[i:])

        php_current = ''
        if output_file.exists():
            with output_file.open() as output:
                php_current = output.read()

        with input_file.open() as input:
            classes, parameters, template = partition(input)
            php_new = php(namespace, cls_name, classes, parameters, template)

            if php_new != php_current:
                with output_file.open('w') as output:
                    output.write(php_new)
                print(f'updated: {input_file}')

#! python3 public function get {         if (array_key_exists( , $this->data)) {             return $this->data }         return null;     }      public function set {         if ($this->cache) {             throw new Exception(self::FINALIZED);         }         $this->data return $this;     } # preserve newline for TEXT (non-HTML) template <?php  declare(strict_types=1);  /**  * DO NOT EDIT  * generated by script/build_template.py  */  namespace ; class extends Template {     public function __construct()     {     } public function __toString()     {         if (!$this->cache) {             foreach ($this->onBeforeRender as $callback) {                 $callback($this);             }              extract($this->data);             ob_start(); ?> <?php             $output = ob_get_clean();             $this->cache = trim($output);         }         return $this->cache;     } }