<?php
h2o::addTag('trans');
h2o::addTag('blocktrans');
h2o::addLookup(array('H2o_I18n', 'gettext'));

class Trans_Tag extends H2o_Node {
    var $text = null;
    function __construct($argstring, $parser, $position = 0) {
        $this->text = stripcslashes(substr($argstring, 1, -1));
    }
    
    function render($context, $stream) {
        if ($this->text) {
            $gettext = H2o_I18n::$gettext;
            $stream->write(call_user_func($gettext, $this->text));
        }
    }
}

class Blocktrans_Tag extends H2o_Node {
    private $singular, $plural;
    private $vars = array();
    private $aliases = array();
    private $count;

    function __construct($argstring, $parser, $position = 0) {
        # get all aliases alias
        if (!empty($argstring)) { 
            $arguments = array_map('trim', explode(',', $argstring));
            foreach($arguments as $argument) {
                if (strpos($argument, '=')) {
                    list($alias, $variable) = explode('=', $argument);
                } else {
                    $variable = $alias = $argument;
                }    
                $this->aliases[trim($alias)] = H2o_Parser::parseArguments($variable);
            }
        }

        # Parse singular and plural block
        $this->singular = $parser->parse('plural', 'endblocktrans');
        
        if ($parser->token->content === 'plural') {
            $this->plural = $parser->parse('endblocktrans');
        }
        
        # compile nodes into string format
        if ($this->singular)
            $this->singular = $this->compile_nodes($this->singular);

        if ($this->plural)
            $this->plural = $this->compile_nodes($this->plural);
    }
    
    function render($context, $stream) {
        $context->push();
        $this->count = false;
        $cache = array();
        foreach($this->aliases as $alias => $data) {
            $variable = array_shift($data);
            $filters = $data;
            $object = $context->resolve($variable);
            
            if (is_null($object)) continue;
            
            if (!empty($filters))
                $object = $context->applyFilters($object, $filters);

            $cache[$alias] = $object;
            if ($this->count === false)
                $this->count = $object;
        }
        
        # Implicit object count lookup
        if (!is_integer($this->count))
            $this->count = count($this->count);
            
        # Get translation
        $ngettext = H2o_I18n::$ngettext;
        $gettext = H2o_I18n::$gettext;
        if ($this->plural)
            $output = call_user_func($ngettext, $this->singular, $this->plural, $this->count);
        else
            $output = call_user_func($gettext, $this->singular);

        # Variable in output
        foreach(array_keys($this->vars) as $var) {
            $object = isset($cache[$var])? $cache[$var]: $context->resolve($var);
            if (!is_null($object)) {
                $output = str_replace("%({$var})", $object, $output);
            }
        }
        $context->pop();
        $stream->write($output);
    }
    
    function compile_nodes($nodes) {
        $output = array();
        foreach ($nodes as $node) {
            if ($node instanceOf VariableNode) {
                if (is_sym($node->variable)) 
                    $var = sym_to_str($node->variable);
                else 
                    $var = $node->variable;
                $output[] = "%({$var})";
                $this->vars[$var] = 1;
            } elseif ($node instanceOf TextNode) {
                $output[] = str_replace("\r", '', $node->content);
            }
        }
        return join('', $output);
    }
}

class H2o_I18n {
    var $locale;
    var $charset = 'UTF-8';
    var $locale_dir;
    var $tmp_dir;
    var $extensions = array('html','tpl');
    var $gettext_path = '';
    var $gettext_setup = false;
    static $gettext = 'gettext';
    static $ngettext = 'ngettext';
    
    function __construct($path, $options = array()) {
        if (is_file($path))
            $path = dirname($path) . DS;

        $this->searchpath = realpath($path).DS;
        $this->locale_dir = $this->searchpath .'locale'.DS;
        $this->options = $options;
        
        if (isset($options['tmp_dir']))
            $this->tmp_dir = $options['tmp_dir'];
        else
            $this->tmp_dir = $this->searchpath.'tmp' .DS;
        
        if (isset($options['ext']))
            $this->extensions = $options['ext'];
            
        if (isset($options['charset']))
            $this->charset = $options['charset'];
            
        if (isset($options['locale']) && $options['locale'])
            $this->setLocale($options['locale']);
        
        if (isset($options['extract_message']) && $options['extract_message'])
            $this->extract();
        
        if (isset($options['compile_message']) && $options['compile_message'])
            $this->compile();
        
        if (!is_dir($this->locale_dir) && !mkdir($this->locale_dir))
            throw new Exception('locale directory not found and failed to created '.$this->searchpath);
    }
    
    function gettext($name, $context) {
        $gettext = self::$gettext;
        if (!is_string($name)) return ;
        $syntax = '/_\(((?:".*?")|(?:\'.*?\'))\)/';
        if (preg_match($syntax, $name, $match)) {
            $text = stripcslashes(substr($match[1], 1, -1));
            return call_user_func($gettext, $text);
        }
    }
    
    function setupGettext() {
        if (isset($this->options['gettext_path']))
            $this->gettext_path = $this->options['gettext_path'];

        if (!file_exists($this->gettext_path))
            $this->gettext_path = $this->searchpath.$this->gettext_path;

        $this->gettext_path = realpath($this->gettext_path).DS;

        if (!exec($this->gettext_path."xgettext -V")) {
            throw new Exception(
                "xgettext binary cannot be found, if you are using Windows system either install through cygwin
                or read instruction here http://docs.djangoproject.com/en/dev/topics/i18n/#gettext-on-windows"
            );
        }
        
        $this->gettext_setup = true;
    }
    
    function extract() {
        if (!$this->gettext_setup)
            $this->setupGettext();

        if (!class_exists('H2o_Lexer', false))
            require H2O_ROOT.'h2o/parser.php';
        # get all tempaltes
        $templates = $this->getTemplates($this->searchpath, $this->extensions, array(dirname(dirname($this->gettext_path))));
        # Get all locales in translation path
        if (!is_dir($this->tmp_dir))
            mkdir($this->tmp_dir);
        
        # foreach locale
        foreach(glob($this->locale_dir.'*') as $dir) {
            if (!is_dir($dir)) continue;
            $locale = basename($dir);
            $lc_messages = $dir . DS . 'LC_MESSAGES'. DS;
            $pot_file = $lc_messages."messages.pot";
            $po_file = $lc_messages."messages.po";
    
            if (!is_dir($lc_messages))
                mkdir($lc_messages);
            
            if (is_file($pot_file))
                unlink($pot_file);
            
            # Compile messages into php file
            $sourcecode = array();
            foreach ($templates as $file) {
                # compile template into php code
                $compiled_src = templize(file_get_contents($file));
                if (!$compiled_src)
                  continue;
                $sourcecode[] = $compiled_src;
            }
            $compiled_file = $this->tmp_dir. 'template_source.php';
            file_put_contents($compiled_file, "<?php ". join(";\n", $sourcecode) ." ?>" );

            # run xgettext to extract all translation string
            $return = '';
            $extra_arg = '';
            if (is_file($pot_file)) 
                $extra_arg = "--omit-header"; 

            $cmd = "{$this->gettext_path}xgettext -L PHP {$extra_arg} --from-code UTF-8 -o - \"{$compiled_file}\"";
            if (!exec($cmd, $return)){
                throw new Exception('Failed to parse template file');
            }
            list($nplurals, $plural) = $this->getPluralForm($locale);
            $replace = array(
                'charset=CHARSET' =>  'charset=UTF-8',
                "#: {$compiled_file}" => "#: {$file}",
                "INTEGER" => $nplurals,
                "EXPRESSION" => $plural,
                "PACKAGE VERSION" => "template translation",
                "\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n" => ""
            );
            $return = join("\n", $return);
            $return = str_replace(array_keys($replace), $replace , $return ."\n");
            file_put_contents($pot_file, $return, FILE_APPEND);
            
            # merge messages for each language
            if (is_file($pot_file)) {
                $return = $error = '';
                $cmd = $this->gettext_path."msguniq --to-code=utf-8 \"{$pot_file}\"";
                
                if (!exec($cmd, $return))
                    throw new Exception('Msgunique failed');
                
                file_put_contents($pot_file, join("\n", $return));
                if (is_file($po_file) && trim(file_get_contents($po_file)) !== '') {
                    $return = '';
                    $cmd = sprintf($this->gettext_path.'msgmerge -q "%s" "%s"', $po_file, $pot_file);
                    exec($cmd, $return);
                    file_put_contents($po_file, join("\n", $return));
                } else {
                    copy($pot_file, $po_file);
                }
            }
            @unlink($pot_file);
        }
        @unlink($compiled_file);
        @rmdir($this->tmp_dir);
    }

    function compile() {
        if (!$this->gettext_setup)
            $this->setupGettext();
        
        foreach(glob($this->locale_dir.'*') as $dir) {
          if (!is_dir($dir)) continue;
          $locale = basename($dir);
          $lc_messages = $dir.DS.'LC_MESSAGES'.DS;
          $po_file = $lc_messages."messages.po";
          $mo_file = $lc_messages."messages.mo";
    
          if (!is_dir($lc_messages))
              mkdir($lc_messages);
    
          $cmd = $this->gettext_path."msgfmt --check-format -o {$mo_file} {$po_file}";
          exec($cmd, $return);
      }
    }
    
    function setLocale($locale, $charset = null) {
        $this->locale = $locale;
        if (!$charset)
            $charset = $this->charset;
        
        putenv("LC_ALL={$locale}");
        setlocale(LC_ALL, $locale);
        if (!is_dir($this->locale_dir))
            throw new Exception('Cannot find Locale message path');
        bindtextdomain("messages", $this->locale_dir);
        bind_textdomain_codeset('messages', $charset);
        textdomain("messages");
    }
    
    function getPluralForm($locale = 'en') {
        $default= array("2", "(n != 1)");
        $default_locale = array(
            'en', 'fi', 'de', 'da', 'el', 'eo', 'es', 'et', 'eu', 'af', 'az', 'bg', 'bn', 'ca', 'nn', 'no', 'pt', 'sv'
        );
        // In-regular plural forms
        $map = array(
            'fr' => array("2", "(n >= 1)"),
            'ja' => array("1", "0"), 'zh' => array("1", "0"),
            'pl' => array("3", "(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2)"),
            'ro' => array("3", "(n==1 ? 0 : (n==0 or (n%100 > 0 && n%100 < 20)) ? 1 : 2)"),
            'ru' => array("3", "(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2)"),
        );
        $locale = current(explode('-', $locale));
        if (isset($map[$locale])) {
            return $map[$locale];
        }
        return $default;
    }
    
    function getTemplates($path, $exts, $exclude = array()) {
        $results = array();
        foreach(new DirectoryIterator($path) as $f) {
            if ($f->isDot()) continue;
    
            if ($f->isFile()) {
                $ext = end(explode(".", $f->getFilename()));
                if ($ext && in_array($ext, $exts) ) {
                    $results[] = $f->getPathname();
                }
            } elseif ($f->isDir() && !in_array($f->getPathname(), $exclude)) {
                $results = array_merge($results, $this->getTemplates($f->getPathname(), $exts, $exclude));
            }
        }
        return $results;
    }
}

/*
    Compile
*/
function templize($source) {
    $output  = array();
    $inline_re = '/^\s*trans\s+("(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')\s*/';
    $block_re = "/^\s*blocktrans(?:\s+|$)/";
    $endblock_re = "/^\s*endblocktrans$/";
    $plural_re = "/^\s*plural$/";
    $var_re = '{
        _\(
            (
            "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" |   # Double Quote string   
            \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' # Single Quote String
            )
        \)
    }x';
    
    $lexer = new H2o_Lexer(h2o::getOptions());
    $tokenstream = $lexer->tokenize($source);
    
    $in_block = false;
    $is_plural = false;
    
    $singulars = array();
    $plurals = array();
    
    while($t = $tokenstream->next()) {
        if ($in_block) {
            if ($t->type == 'block' && $t->content == 'endblocktrans') {
                if ($is_plural) {
                    $output[] = sprintf(
                        " ngettext('%s', '%s', \$count)", join('', $singulars), join('', $plurals)
                    ); 
                } else {
                    $output[] = sprintf(" gettext('%s')", join('', $singulars)); 
                }
                $singulars = $plurals = array();
                $in_block = $is_plural = false ;
            } 
            elseif ($t->type == 'block' && $t->content == 'plural') {
                $is_plural = true;
            }
            elseif ($t->type == 'text') {
                if ($is_plural)
                    $plurals[] = addslashes($t->content);
                else
                    $singulars[] = addslashes($t->content);
            }
            elseif ($t->type == 'variable') {
                @list($var, $filters ) = explode('|', $t->content);
                if ($is_plural)
                    $plurals[] = sprintf("%%(%s)", $var);
                else
                    $singulars[] = sprintf("%%(%s)", $var);
            }
            elseif ($t->type == 'block') {
                throw new Exception('No block tag is allowed in translation block');
            }
        } else {
            if ($t->type == 'block') {
                if (preg_match($inline_re, $t->content, $matches)) {
                    $output[] = sprintf(" gettext(%s)", $matches[1]);
                }
                elseif (preg_match($block_re, $t->content, $matches)) {
                    $in_block = true;
                } 
            } elseif ($t->type == 'variable') {
                if (preg_match($var_re, $t->content, $matches)) {
                    $output[] = sprintf(" gettext(%s)", ($matches[1]));
                }
            }
        }
    }
    $result = str_replace("\r", '', implode(";\n", $output));
    if ($result)
    return "\n".$result . ";\n";
}

?>
