<?php
/**
 * barcode.php – pure-PHP Code-39 SVG generator.
 *
 * Code-39 advantages:
 *   - Universally readable by any USB / wedge barcode scanner.
 *   - No font dependency on the print machine.
 *   - SVG output is sharp at any print resolution (label printers love it).
 *
 * Character set: 0-9, A-Z, "-", ".", "$", "/", "+", "%", space, and "*"
 * (start/stop sentinel). Lowercase is auto-uppercased; unsupported chars
 * are stripped. Resulting SVG is wrapped with start/stop sentinels.
 */

const CODE39_TABLE = [
    '0'=>'NNNWWNWNN','1'=>'WNNWNNNNW','2'=>'NNWWNNNNW','3'=>'WNWWNNNNN',
    '4'=>'NNNWWNNNW','5'=>'WNNWWNNNN','6'=>'NNWWWNNNN','7'=>'NNNWNNWNW',
    '8'=>'WNNWNNWNN','9'=>'NNWWNNWNN',
    'A'=>'WNNNNWNNW','B'=>'NNWNNWNNW','C'=>'WNWNNWNNN','D'=>'NNNNWWNNW',
    'E'=>'WNNNWWNNN','F'=>'NNWNWWNNN','G'=>'NNNNNWWNW','H'=>'WNNNNWWNN',
    'I'=>'NNWNNWWNN','J'=>'NNNNWWWNN','K'=>'WNNNNNNWW','L'=>'NNWNNNNWW',
    'M'=>'WNWNNNNWN','N'=>'NNNNWNNWW','O'=>'WNNNWNNWN','P'=>'NNWNWNNWN',
    'Q'=>'NNNNNNWWW','R'=>'WNNNNNWWN','S'=>'NNWNNNWWN','T'=>'NNNNWNWWN',
    'U'=>'WWNNNNNNW','V'=>'NWWNNNNNW','W'=>'WWWNNNNNN','X'=>'NWNNWNNNW',
    'Y'=>'WWNNWNNNN','Z'=>'NWWNWNNNN',
    '-'=>'NWNNNNWNW','.'=>'WWNNNNWNN',' '=>'NWWNNNWNN',
    '$'=>'NWNWNWNNN','/'=>'NWNWNNNWN','+'=>'NWNNNWNWN','%'=>'NNNWNWNWN',
    '*'=>'NWNNWNWNN', // start/stop sentinel
];

const CODE39_VALID_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%';

function code39_sanitize(string $s): string
{
    $s = strtoupper(trim($s));
    $out = '';
    $len = strlen($s);
    for ($i = 0; $i < $len; $i++) {
        if (strpos(CODE39_VALID_CHARS, $s[$i]) !== false) $out .= $s[$i];
    }
    return $out;
}

/**
 * Render Code-39 SVG for the given text.
 *
 * @param string $text          The data to encode (auto-uppercased & sanitized).
 * @param int    $height        Pixel height of the bars.
 * @param int    $narrowWidth   Narrow bar width in pixels.
 * @param int    $wideRatio     Wide:narrow ratio (typically 2 or 3).
 * @param bool   $includeText   Render the human-readable text below the bars.
 * @return string SVG markup or empty string on bad input.
 */
function code39_svg(string $text, int $height = 60, int $narrowWidth = 2, int $wideRatio = 3, bool $includeText = true): string
{
    $clean = code39_sanitize($text);
    if ($clean === '') return '';

    $payload = '*' . $clean . '*';
    $bars = []; // [x, w, isBar]
    $x = 0;
    $intercharGap = $narrowWidth; // narrow space between chars
    $w_narrow = $narrowWidth;
    $w_wide   = $narrowWidth * max(2, $wideRatio);

    $chars = str_split($payload);
    for ($i = 0; $i < count($chars); $i++) {
        $c = $chars[$i];
        if (!isset(CODE39_TABLE[$c])) continue;
        $pattern = CODE39_TABLE[$c]; // 9 elements: B-S-B-S-B-S-B-S-B
        for ($k = 0; $k < 9; $k++) {
            $w = $pattern[$k] === 'W' ? $w_wide : $w_narrow;
            $isBar = ($k % 2 === 0);
            $bars[] = [$x, $w, $isBar];
            $x += $w;
        }
        if ($i !== count($chars) - 1) {
            $x += $intercharGap;
        }
    }

    $totalW = $x;
    $totalH = $height + ($includeText ? 18 : 0);

    $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' . $totalW . ' ' . $totalH . '" preserveAspectRatio="xMidYMin meet" style="width:100%; height:auto; max-width:100%;">';
    $svg .= '<rect width="' . $totalW . '" height="' . $totalH . '" fill="#fff"/>';
    foreach ($bars as $b) {
        if (!$b[2]) continue;
        $svg .= '<rect x="' . $b[0] . '" y="0" width="' . $b[1] . '" height="' . $height . '" fill="#000"/>';
    }
    if ($includeText) {
        $fontSize = 13;
        $svg .= '<text x="' . ($totalW / 2) . '" y="' . ($height + 14) . '" font-family="Consolas, monospace" font-size="' . $fontSize . '" text-anchor="middle" fill="#000">'
              . htmlspecialchars($clean, ENT_QUOTES) . '</text>';
    }
    $svg .= '</svg>';
    return $svg;
}

/**
 * Compute the recommended barcode value for a candidate.
 *
 * Rule: {FACILITY_CODE}-{CANDIDATE_NUMBER}, both uppercased & Code-39 sanitised.
 * Falls back to F{facility_id}-{candidate_number} if facility has no code.
 */
function barcode_from_candidate(string $candidateNumber, ?string $facilityCode, int $facilityId): string
{
    $cn = code39_sanitize($candidateNumber);
    if ($cn === '') $cn = 'X' . $facilityId;

    $prefix = $facilityCode ? code39_sanitize($facilityCode) : 'F' . $facilityId;
    if ($prefix === '') $prefix = 'F' . $facilityId;

    return code39_sanitize($prefix . '-' . $cn);
}
