#!/usr/bin/env python3
"""
Source code formatter for tokyo-express.net
Performs:
  1. Indentation: leading spaces → tabs
  2. PHP comments: '# ' at line start → '// '
  3. HTML DOCTYPE: old DOCTYPE → <!DOCTYPE html>
  4. HTML charset meta: http-equiv Content-Type → <meta charset="..."> (UTF-8 files: UTF-8, Shift_JIS files: Shift_JIS)
  5. HTML tag case: uppercase tags → lowercase
  6. lang=ja → lang="ja"
"""

import os
import re
import sys

BASE = os.path.dirname(os.path.abspath(__file__))

TARGET_DIRS = [
    'coursemaster', 'drive', 'drived', 'hino', 'hinonew',
    'menu', 'packview', 'report', 'reportd', 'stocklist', 'totalreport', 'view'
]

ROOT_FILES = [
    'zcommon.php', 'zcommon.js', 'dbproc.php', 'new.php',
    'packfix.php', 'regist.php', 'beep.html', 'test.html'
]

EXCLUDED_DIRS = {
    'drive20260208', 'driveoldbackup', 'drivever2.0',
    'hino20260208', 'hino20260216', 'hino20260217', 'hino20260218',
    'report20260213'
}

# DOCTYPE pattern: matches <!DOCTYPE HTML PUBLIC "..." optional-url>
# Handles tabs inside quoted strings, optional second quoted string, optional newline between them
DOCTYPE_PATTERN = re.compile(
    r'<!DOCTYPE\s+HTML\s+PUBLIC\s+["\'][^"\']*["\']\s*(?:["\'][^"\']*["\']\s*)?>',
    re.IGNORECASE | re.DOTALL
)

# Meta charset pattern: <meta http-equiv="Content-Type" content="text/html; charset=...">
# Handles tabs/spaces as attribute separators and within content value
META_CHARSET_PATTERN = re.compile(
    r'<meta\s+http-equiv=["\']Content-Type["\']\s+content=["\']text/html;\s*charset=([^"\']*)["\'](\s*/?)>',
    re.IGNORECASE
)

# HTML tags to lowercase
HTML_TAGS = ('html', 'head', 'body', 'title', 'style', 'script', 'meta', 'link')
TAG_OPEN_PATTERN = re.compile(
    r'<(' + '|'.join(HTML_TAGS) + r')(\s|>|/)',
    re.IGNORECASE
)
TAG_CLOSE_PATTERN = re.compile(
    r'</(' + '|'.join(HTML_TAGS) + r')>',
    re.IGNORECASE
)

# lang=ja without quotes
LANG_PATTERN = re.compile(r'\blang=ja\b(?!["\'])')


def get_target_files():
    """Collect all target .php, .js, .html files."""
    files = []
    for fname in ROOT_FILES:
        fpath = os.path.join(BASE, fname)
        if os.path.exists(fpath):
            files.append(fpath)
    for dirname in TARGET_DIRS:
        dirpath = os.path.join(BASE, dirname)
        if not os.path.isdir(dirpath):
            continue
        for root, dirs, filenames in os.walk(dirpath):
            dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]
            for fname in sorted(filenames):
                if fname.endswith(('.php', '.js', '.html')):
                    files.append(os.path.join(root, fname))
    return files


def detect_encoding(fpath):
    """Detect file encoding: utf-8 or shift_jis."""
    with open(fpath, 'rb') as f:
        raw = f.read()
    if raw.startswith(b'\xef\xbb\xbf'):
        return 'utf-8-sig'
    try:
        raw.decode('utf-8')
        return 'utf-8'
    except UnicodeDecodeError:
        return 'shift_jis'


def detect_indent_size(lines):
    """Detect whether file uses 2-space or 4-space indentation."""
    two_count = 0
    four_count = 0
    for line in lines:
        m = re.match(r'^( +)[^ \t\n]', line)
        if m:
            n = len(m.group(1))
            if n % 4 == 0:
                four_count += 1
            elif n % 2 == 0:
                two_count += 1
    return 2 if two_count > four_count else 4


def convert_indent_line(line, tab_size):
    """Convert leading spaces to tabs for a single line."""
    m = re.match(r'^( +)(.*)', line, re.DOTALL)
    if not m:
        return line
    spaces = len(m.group(1))
    tabs = spaces // tab_size
    remainder = spaces % tab_size
    return '\t' * tabs + ' ' * remainder + m.group(2)


def process_indent(content):
    """Convert leading space indentation to tabs."""
    lines = content.splitlines(keepends=True)
    # Check if any space-indented lines exist
    has_space_indent = any(re.match(r'^  +[^ \t\n]', line) for line in lines)
    if not has_space_indent:
        return content
    tab_size = detect_indent_size(lines)
    new_lines = []
    for line in lines:
        if re.match(r'^  +[^ \t\n]', line):
            new_lines.append(convert_indent_line(line, tab_size))
        else:
            new_lines.append(line)
    return ''.join(new_lines)


def process_php_comments(content):
    """Convert '# comment' to '// comment' at line start in PHP."""
    def replace_hash(m):
        return m.group(1) + '// ' + m.group(2)
    return re.sub(r'^(\s*)# (.+)', replace_hash, content, flags=re.MULTILINE)


def process_html(content, encoding, ext='.html'):
    """Apply HTML normalization."""
    is_utf8 = encoding in ('utf-8', 'utf-8-sig')

    # 1. DOCTYPE
    content = DOCTYPE_PATTERN.sub('<!DOCTYPE html>', content)

    # 2. Meta charset
    # Use single quotes in PHP files to avoid breaking double-quoted PHP strings
    q = "'" if ext == '.php' else '"'
    if is_utf8:
        content = META_CHARSET_PATTERN.sub(f'<meta charset={q}UTF-8{q}>', content)
    else:
        # Shift_JIS: modernize meta tag format but keep charset value
        def replace_meta_sjis(m):
            charset_val = m.group(1).strip()
            return f'<meta charset={q}{charset_val}{q}>'
        content = META_CHARSET_PATTERN.sub(replace_meta_sjis, content)

    # 3. Uppercase tags → lowercase
    content = TAG_OPEN_PATTERN.sub(lambda m: '<' + m.group(1).lower() + m.group(2), content)
    content = TAG_CLOSE_PATTERN.sub(lambda m: '</' + m.group(1).lower() + '>', content)

    # 4. lang=ja → lang="ja" (use single quotes in PHP files to avoid breaking double-quoted strings)
    if ext == '.php':
        content = LANG_PATTERN.sub("lang='ja'", content)
    else:
        content = LANG_PATTERN.sub('lang="ja"', content)

    return content


def process_file(fpath):
    """Process a single file. Returns (changed: bool, lines_changed: int)."""
    ext = os.path.splitext(fpath)[1].lower()
    encoding = detect_encoding(fpath)

    try:
        with open(fpath, 'r', encoding=encoding, errors='replace') as f:
            original = f.read()
    except Exception as e:
        print(f'  ERROR reading {os.path.relpath(fpath, BASE)}: {e}')
        return False, 0

    content = original

    # 1. Indentation (all file types)
    content = process_indent(content)

    # 2. PHP comment style
    if ext == '.php':
        content = process_php_comments(content)

    # 3. HTML normalization (.html and .php)
    if ext in ('.html', '.php'):
        content = process_html(content, encoding, ext)

    if content == original:
        return False, 0

    # Count changed lines
    orig_lines = original.splitlines()
    new_lines_list = content.splitlines()
    changed_count = sum(1 for a, b in zip(orig_lines, new_lines_list) if a != b)
    changed_count += abs(len(orig_lines) - len(new_lines_list))

    try:
        with open(fpath, 'w', encoding=encoding, errors='replace', newline='') as f:
            f.write(content)
    except Exception as e:
        print(f'  ERROR writing {os.path.relpath(fpath, BASE)}: {e}')
        return False, 0

    return True, changed_count


def main():
    files = get_target_files()
    print(f'Found {len(files)} target files\n')

    changed_files = 0
    total_lines = 0

    for fpath in files:
        rel = os.path.relpath(fpath, BASE)
        changed, lines = process_file(fpath)
        if changed:
            changed_files += 1
            total_lines += lines
            print(f'  CHANGED ({lines:3d} lines): {rel}')

    print(f'\n{"="*50}')
    print(f'Done. Changed {changed_files} files, {total_lines} lines total.')


if __name__ == '__main__':
    main()
