<?php

class SEA {

// South East Asian shaper
// sea_category
    const OT_X = 0;
    const OT_C = 1;
    const OT_IV = 2;  # Independent Vowel
    const OT_T = 3;  # Tone Marks
    const OT_H = 4;  # Halant
    const OT_A = 10; # Anusvara
    const OT_GB = 12; # Generic Base	(OT_DOTTEDCIRCLE in Indic)
    const OT_CM = 17; # Consonant Medial
    const OT_MR = 22; # Medial Ra
    const OT_VAbv = 26;
    const OT_VBlw = 27;
    const OT_VPre = 28;
    const OT_VPst = 29;
// ? From Indic categories
    const OT_ZWNJ = 5;
    const OT_ZWJ = 6;
    const OT_M = 7;
    const OT_SM = 8;
    const OT_VD = 9;
    const OT_NBSP = 11;
    const OT_RS = 13;
    const OT_Coeng = 14;
    const OT_Repha = 15;
    const OT_Ra = 16;

// Based on sea_category used to make string to find syllables
// OT_ to string character (using e.g. OT_C from INDIC) hb-ot-shape-complex-sea-private.hh 
    public static $sea_category_char = array(
        'x',
        'C',
        'V',
        'T',
        'H',
        'x',
        'x',
        'x',
        'x',
        'x',
        'A',
        'x',
        'G',
        'x',
        'x',
        'x',
        'x',
        'M',
        'x',
        'x',
        'x',
        'x',
        'R',
        'x',
        'x',
        'x',
        'a',
        'b',
        'p',
        't',
    );

    /* Visual positions in a syllable from left to right. */

// sea_position
    const POS_START = 0;
    const POS_RA_TO_BECOME_REPH = 1;
    const POS_PRE_M = 2;
    const POS_PRE_C = 3;
    const POS_BASE_C = 4;
    const POS_AFTER_MAIN = 5;
    const POS_ABOVE_C = 6;
    const POS_BEFORE_SUB = 7;
    const POS_BELOW_C = 8;
    const POS_AFTER_SUB = 9;
    const POS_BEFORE_POST = 10;
    const POS_POST_C = 11;
    const POS_AFTER_POST = 12;
    const POS_FINAL_C = 13;
    const POS_SMVD = 14;
    const POS_END = 15;

    public static function set_sea_properties(&$info, $scriptblock) {
        $u = $info['uni'];
        $type = self::sea_get_categories($u);
        $cat = ($type & 0x7F);
        $pos = ($type >> 8);

        /*
         * Re-assign category
         */
        // Medial Ra
        if ($u == 0x1A55 || $u == 0xAA34) {
            $cat = self::OT_MR;
        }

        /*
         * Re-assign position.
         */
        if ($cat == self::OT_M) { // definitely "OT_M" in HarfBuzz - although this does not seem to have been defined ? should be OT_MR
            switch ($pos) {
                case self::POS_PRE_C: $cat = self::OT_VPre;
                    break;
                case self::POS_ABOVE_C: $cat = self::OT_VAbv;
                    break;
                case self::POS_BELOW_C: $cat = self::OT_VBlw;
                    break;
                case self::POS_POST_C: $cat = self::OT_VPst;
                    break;
            }
        }

        $info['sea_category'] = $cat;
        $info['sea_position'] = $pos;
    }

// syllable_type
    const CONSONANT_SYLLABLE = 0;
    const BROKEN_CLUSTER = 1;
    const NON_SEA_CLUSTER = 2;

    public static function set_syllables(&$o, $s, &$broken_syllables) {
        $ptr = 0;
        $syllable_serial = 1;
        $broken_syllables = false;
        while ($ptr < strlen($s)) {
            $match = '';
            $syllable_length = 1;
            $syllable_type = self::NON_SEA_CLUSTER;

            // CONSONANT_SYLLABLE Consonant syllable
            if (preg_match('/^(C|V|G)(p|a|b|t|HC|M|R|T|A)*/', substr($s, $ptr), $ma)) {
                $syllable_length = strlen($ma[0]);
                $syllable_type = self::CONSONANT_SYLLABLE;
            }
            // BROKEN_CLUSTER syllable
            else if (preg_match('/^(p|a|b|t|HC|M|R|T|A)+/', substr($s, $ptr), $ma)) {
                $syllable_length = strlen($ma[0]);
                $syllable_type = self::BROKEN_CLUSTER;
                $broken_syllables = true;
            }

            for ($i = $ptr; $i < $ptr + $syllable_length; $i++) {
                $o[$i]['syllable'] = ($syllable_serial << 4) | $syllable_type;
            }
            $ptr += $syllable_length;
            $syllable_serial++;
            if ($syllable_serial == 16)
                $syllable_serial = 1;
        }
    }

    public static function initial_reordering(&$info, $GSUBdata, $broken_syllables, $scriptblock, $dottedcircle) {

        if ($broken_syllables && $dottedcircle) {
            self::insert_dotted_circles($info, $dottedcircle);
        }

        $count = count($info);
        if (!$count)
            return;
        $last = 0;
        $last_syllable = $info[0]['syllable'];
        for ($i = 1; $i < $count; $i++) {
            if ($last_syllable != $info[$i]['syllable']) {
                self::initial_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $i);
                $last = $i;
                $last_syllable = $info[$last]['syllable'];
            }
        }
        self::initial_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $count);
    }

    public static function insert_dotted_circles(&$info, $dottedcircle) {
        $idx = 0;
        $last_syllable = 0;
        while ($idx < count($info)) {
            $syllable = $info[$idx]['syllable'];
            $syllable_type = ($syllable & 0x0F);
            if ($last_syllable != $syllable && $syllable_type == self::BROKEN_CLUSTER) {
                $last_syllable = $syllable;
                $dottedcircle[0]['syllable'] = $info[$idx]['syllable'];
                array_splice($info, $idx, 0, $dottedcircle);
            } else
                $idx++;
        }
    }

    public static function initial_reordering_syllable(&$info, $GSUBdata, $scriptblock, $start, $end) {
        /* broken_cluster: We already inserted dotted-circles, so just call the standalone_cluster. */

        $syllable_type = ($info[$start]['syllable'] & 0x0F);
        if ($syllable_type == self::NON_SEA_CLUSTER) {
            return;
        }
        if ($syllable_type == self::BROKEN_CLUSTER) {
            /* For dotted-circle, this is what Uniscribe does:
             * If dotted-circle is the last glyph, it just does nothing. */
            if ($info[$end - 1]['sea_category'] == self::OT_GB) {
                return;
            }
        }

        $base = $start;
        $i = $start;
        for (; $i < $base; $i++)
            $info[$i]['sea_position'] = self::POS_PRE_C;
        if ($i < $end) {
            $info[$i]['sea_position'] = self::POS_BASE_C;
            $i++;
        }
        for (; $i < $end; $i++) {
            if ($info[$i]['sea_category'] == self::OT_MR) { /* Pre-base reordering */
                $info[$i]['sea_position'] = self::POS_PRE_C;
                continue;
            }
            if ($info[$i]['sea_category'] == self::OT_VPre) { /* Left matra */
                $info[$i]['sea_position'] = self::POS_PRE_M;
                continue;
            }
            $info[$i]['sea_position'] = self::POS_AFTER_MAIN;
        }

        /* Sit tight, rock 'n roll! */
        self::bubble_sort($info, $start, $end - $start);
    }

    public static function final_reordering(&$info, $GSUBdata, $scriptblock) {
        $count = count($info);
        if (!$count)
            return;
        $last = 0;
        $last_syllable = $info[0]['syllable'];
        for ($i = 1; $i < $count; $i++) {
            if ($last_syllable != $info[$i]['syllable']) {
                self::final_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $i);
                $last = $i;
                $last_syllable = $info[$last]['syllable'];
            }
        }
        self::final_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $count);
    }

    public static function final_reordering_syllable(&$info, $GSUBdata, $scriptblock, $start, $end) {
        /*
         * Nothing to do here at present!
         */
    }

    public static $sea_table = array(
        /* New Tai Lue  (1980..19DF) */

        /* 1980 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* 1988 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* 1990 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* 1998 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* 19A0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* 19A8 */ 3841, 3841, 3841, 3841, 3840, 3840, 3840, 3840,
        /* 19B0 */ 2823, 2823, 2823, 2823, 2823, 775, 775, 775,
        /* 19B8 */ 2823, 2823, 775, 2823, 2823, 2823, 2823, 2823,
        /* 19C0 */ 2823, 3857, 3857, 3857, 3857, 3857, 3857, 3857,
        /* 19C8 */ 3843, 3843, 3840, 3840, 3840, 3840, 3840, 3840,
        /* 19D0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
        /* 19D8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
        /* Tai Tham  (1A20..1AAF) */

        /* 1A20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* 1A28 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* 1A30 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* 1A38 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* 1A40 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* 1A48 */ 3841, 3841, 3841, 3841, 3841, 3842, 3842, 3842,
        /* 1A50 */ 3842, 3842, 3842, 3841, 3841, 3857, 3857, 3857,
        /* 1A58 */ 3857, 3857, 3857, 3857, 3857, 3857, 3857, 3840,
        /* 1A60 */ 3844, 2823, 1543, 2823, 2823, 1543, 1543, 1543,
        /* 1A68 */ 1543, 2055, 2055, 1543, 2055, 2823, 775, 775,
        /* 1A70 */ 775, 775, 775, 1543, 1543, 3843, 3843, 3843,
        /* 1A78 */ 3843, 3843, 3840, 3840, 3840, 3840, 3840, 3840,
        /* 1A80 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
        /* 1A88 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
        /* 1A90 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
        /* 1A98 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
        /* 1AA0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
        /* 1AA8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
        /* Cham  (AA00..AA5F) */

        /* AA00 */ 3842, 3842, 3842, 3842, 3842, 3842, 3841, 3841,
        /* AA08 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* AA10 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* AA18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* AA20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
        /* AA28 */ 3841, 1543, 1543, 1543, 1543, 2055, 1543, 775,
        /* AA30 */ 775, 1543, 2055, 3857, 3857, 3857, 3857, 3840,
        /* AA38 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
        /* AA40 */ 3857, 3857, 3857, 3857, 3857, 3857, 3857, 3857,
        /* AA48 */ 3857, 3857, 3857, 3857, 3857, 3857, 3840, 3840,
        /* AA50 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
        /* AA58 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
    );

    public static function sea_get_categories($u) {
        if (0x1980 <= $u && $u <= 0x19DF)
            return self::$sea_table[$u - 0x1980]; // offset 0 for New Tai Lue  
        if (0x1A20 <= $u && $u <= 0x1AAF)
            return self::$sea_table[$u - 0x1A20 + 96]; // offset for Tai Tham  
        if (0xAA00 <= $u && $u <= 0xAA5F)
            return self::$sea_table[$u - 0xAA00 + 96 + 144];  // Cham  
        if ($u == 0x00A0)
            return 3851; // (ISC_CP | (IMC_x << 8))
        if ($u == 0x25CC)
            return 3851; // (ISC_CP | (IMC_x << 8))
        return 3840; // (ISC_x | (IMC_x << 8))
    }

    public static function bubble_sort(&$arr, $start, $len) {
        if ($len < 2) {
            return;
        }
        $k = $start + $len - 2;
        while ($k >= $start) {
            for ($j = $start; $j <= $k; $j++) {
                if ($arr[$j]['sea_position'] > $arr[$j + 1]['sea_position']) {
                    $t = $arr[$j];
                    $arr[$j] = $arr[$j + 1];
                    $arr[$j + 1] = $t;
                }
            }
            $k--;
        }
    }

}

// end Class
?>