<?php
/**
 * ==========================================================
 * Passport OCR Engine v2.0
 * Part 1 - Foundation
 * Author : ChatGPT + Sahil
 * Version: 2.0.0
 * ==========================================================
 */

ini_set('max_execution_time', 300);
ini_set('memory_limit', '512M');

/*==========================================================
    CONFIG
==========================================================*/

$debug = false;

$AZURE_ENDPOINT = "https://shaikhocr.cognitiveservices.azure.com/vision/v3.2/read/analyze";

$AZURE_KEY = "3nsmBKQOMz3KnJvalYrpoVWUObvdgqYSOnO0bovRjoDkx6xmXVy4JQQJ99BEACYeBjFXJ3w3AAAFACOGNiLO";

/*==========================================================
    START
==========================================================*/

try {

    validateUpload();

    $operationUrl = uploadToAzure();

    $ocrText = pollAzure($operationUrl);


    if (trim($ocrText) == "") {
        throw new Exception("No text detected from OCR.");
    }

    $normalized = normalizeOCR($ocrText);

    $mrz = findMRZ($normalized);

echo "<h1 style='color:red'>OCR V2 LOADED</h1>";
exit;

    $passport = parseMRZ($mrz);

    $ocr = parseOCRLabels($normalized);

    $final = mergePassportData($passport, $ocr);

    validatePassport($final);

    $confidence = calculateConfidence($final);

    $final["_meta"] = [
        "confidence" => $confidence,
        "mrz_found" => ($mrz['line1'] != ""),
        "ocr_found" => ($ocrText != "")
    ];

    if ($debug) {
        debugOCR($ocrText, $mrz, $final);
    }

    responseJSON($final);



} catch (Exception $e) {

    http_response_code(500);

    header("Content-Type: application/json");

    echo json_encode([
        "success" => false,
        "message" => $e->getMessage()
    ]);

    exit;
}

/*==========================================================
    DATE FORMATER
==========================================================*/
function formatDate($date)
{
    if (preg_match('/(\d{2})[\/.-](\d{2})[\/.-](\d{4})/', $date, $m)) {
        return $m[3] . "-" . $m[2] . "-" . $m[1];
    }
    return "";
}

/*==========================================================
    VALIDATE UPLOAD
==========================================================*/


function validateUpload()
{

    if (!isset($_FILES['image']))
        throw new Exception("Image not uploaded.");

    if ($_FILES['image']['error'] != UPLOAD_ERR_OK)
        throw new Exception("Upload failed.");

    $ext = strtolower(pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION));

    $allow = [
        'jpg',
        'jpeg',
        'png',
        'pdf'
    ];

    if (!in_array($ext, $allow))
        throw new Exception("Invalid file type.");

}

/*==========================================================
    CONTENT TYPE
==========================================================*/

function getContentType($filename)
{

    $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

    switch ($ext) {

        case "jpg":
        case "jpeg":
            return "image/jpeg";

        case "png":
            return "image/png";

        case "pdf":
            return "application/pdf";

        default:
            return "";
    }

}

/*==========================================================
    UPLOAD TO AZURE
==========================================================*/

function uploadToAzure()
{

    global $AZURE_ENDPOINT;
    global $AZURE_KEY;

    $file = $_FILES['image']['tmp_name'];

    $binary = file_get_contents($file);

    $contentType = getContentType($_FILES['image']['name']);

    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $AZURE_ENDPOINT);

    curl_setopt($ch, CURLOPT_POST, true);

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    curl_setopt($ch, CURLOPT_HEADER, true);

    curl_setopt($ch, CURLOPT_POSTFIELDS, $binary);

    curl_setopt($ch, CURLOPT_HTTPHEADER, [

        "Ocp-Apim-Subscription-Key: " . $AZURE_KEY,

        "Content-Type: " . $contentType

    ]);

    $response = curl_exec($ch);

    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);

    if (curl_errno($ch)) {
        $err = curl_error($ch);
        curl_close($ch);
        throw new Exception($err);
    }

    if ($status != 202) {
        curl_close($ch);
        throw new Exception("Azure upload failed. HTTP Status: " . $status);
    }

    if (curl_errno($ch))
        throw new Exception(curl_error($ch));

    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);

    $header = substr($response, 0, $headerSize);

    curl_close($ch);

    if (!preg_match('/Operation-Location:\s*(.+)/i', $header, $m))
        throw new Exception("Operation URL not received.");

    return trim($m[1]);

}

/*==========================================================
    POLL AZURE
==========================================================*/

function pollAzure($operationUrl)
{

    global $AZURE_KEY;

    $retry = 0;

    while ($retry < 15) {

        sleep(1);

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $operationUrl);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        curl_setopt($ch, CURLOPT_HTTPHEADER, [

            "Ocp-Apim-Subscription-Key: " . $AZURE_KEY

        ]);

        $response = curl_exec($ch);

        curl_close($ch);

        $json = json_decode($response, true);

        if (!isset($json['status'])) {

            $retry++;

            continue;
        }

        if ($json['status'] == "failed")
            throw new Exception("Azure OCR Failed.");

        if ($json['status'] == "succeeded") {

            return collectOCRText($json);

        }

        $retry++;

    }

    throw new Exception("Azure Timeout.");

}

/*==========================================================
    OCR TEXT
==========================================================*/

// function collectOCRText($json)
// {

//     $text = "";

//     if (empty($json['analyzeResult']['readResults']))
//         return "";

//     foreach ($json['analyzeResult']['readResults'] as $page) {

//         foreach ($page['lines'] as $line) {

//             $text .= trim($line['text']) . "\n";

//         }

//     }

//     return trim($text);

// }


function collectOCRText($json)
{
    echo "<pre>";

    foreach ($json['analyzeResult']['readResults'] as $page) {

        foreach ($page['lines'] as $line) {

            echo "LINE:\n";
            print_r($line);
            echo "\n--------------------\n";

        }

    }

    exit;
}



/*==========================================================
    NORMALIZE OCR
==========================================================*/

function normalizeOCR($text)
{
    $text = strtoupper($text);

    $text = str_replace("\r", "\n", $text);

    $text = preg_replace('/[ \t]+/', ' ', $text);

    $text = preg_replace('/\n+/', "\n", $text);

    // Remove spaces around < only
    $text = preg_replace('/[ \t]*<[ \t]*/', '<', $text);

    return trim($text);
}


/*==========================================================
    FIND MRZ
==========================================================*/

// function findMRZ($text)
// {
//     $lines = preg_split("/\r\n|\n|\r/", $text);

//     $mrz1 = "";
//     $mrz2 = "";

//     for ($i = 0; $i < count($lines); $i++) {

//         $line = strtoupper(trim($lines[$i]));

//         // Remove spaces only for matching
//         $clean = preg_replace('/\s+/', '', $line);

//         if (strpos($clean, "P<") === 0) {

//             $mrz1 = $clean;

//             if (isset($lines[$i + 1])) {
//                 $mrz2 = preg_replace('/\s+/', '', strtoupper(trim($lines[$i + 1])));
//             }

//             break;
//         }
//     }

//     return [
//         "line1" => $mrz1,
//         "line2" => $mrz2
//     ];
// }

function findMRZ($text)
{
    $lines = preg_split("/\r\n|\n|\r/", $text);

    echo "<pre>";

    foreach($lines as $i=>$line){

        echo "LINE ".$i." = [".$line."]\n";

    }

    echo "\n--------------------------\n";

    $mrz1="";
    $mrz2="";

    for($i=0;$i<count($lines);$i++){

        $line = strtoupper(trim($lines[$i]));

        $clean = preg_replace('/\s+/','',$line);

        echo "CHECK : ".$clean."\n";

        if(strpos($clean,"P<")===0){

            echo "\nFOUND MRZ AT LINE ".$i."\n";

            $mrz1 = $clean;

            if(isset($lines[$i+1])){

                $mrz2 = preg_replace('/\s+/','',strtoupper(trim($lines[$i+1])));

            }

            break;

        }

    }

    echo "\nMRZ1=".$mrz1."\n";
    echo "MRZ2=".$mrz2."\n";

    exit;
}

/*==========================================================
    PARSE MRZ
==========================================================*/

function parseMRZ($mrz)
{

    $data = [];

    $mrz1 = $mrz['line1'];
    $mrz2 = $mrz['line2'];

    echo "<pre>";

    echo "MRZ1 = " . $mrz1 . "<br>";
    echo "LEN1 = " . strlen($mrz1) . "<br><br>";

    echo "MRZ2 = " . $mrz2 . "<br>";
    echo "LEN2 = " . strlen($mrz2) . "<br>";

    exit;

    if ($mrz1 == "" || $mrz2 == "")
        return $data;

    $mrz1 = str_pad($mrz1, 44, "<");
    $mrz2 = str_pad($mrz2, 44, "<");
    
    /* Passport Number */
    $passport = substr($mrz2, 0, 9);

    echo $passport;
    exit;

    $passport = strtoupper($passport);

    $passport = str_replace("<", "", $passport);

    $chars = str_split($passport);

    foreach ($chars as $k => $v) {

        if ($k > 0) {

            if ($v == "O")
                $chars[$k] = "0";

            if ($v == "I")
                $chars[$k] = "1";

        }

    }

    $passport = implode("", $chars);

    $data["Passport Number"] = trim($passport);


    /* Nationality */

    $nat = substr($mrz2, 10, 3);

    $nat = str_replace("1", "I", $nat);

    $nat = str_replace("0", "O", $nat);

    $country = [

        "IND" => "INDIAN",
        "PAK" => "PAKISTANI",
        "BGD" => "BANGLADESHI",
        "NPL" => "NEPALI",
        "LKA" => "SRI LANKAN"

    ];

    $data["Nationality"] =
        isset($country[$nat]) ? $country[$nat] : $nat;


    /* DOB */

    $dob = substr($mrz2, 13, 6);

    $yy = intval(substr($dob, 0, 2));

    $year = ($yy > intval(date("y")))
        ? "19" . $yy
        : "20" . $yy;

    $data["Date of Birth"] =
        $year . "-" .
        substr($dob, 2, 2) . "-" .
        substr($dob, 4, 2);


    /* Gender */

    $data["Sex"] = substr($mrz2, 20, 1);

    /* Expiry */

    $exp = substr($mrz2, 21, 6);

    $yy = intval(substr($exp, 0, 2));

    $year = ($yy >= 80) ? 1900 + $yy : 2000 + $yy;

    $data["Date of Expiry"] =
        sprintf(
            "%04d-%02d-%02d",
            $year,
            substr($exp, 2, 2),
            substr($exp, 4, 2)
        );

    /* Name */

    $name = substr($mrz1, 5);

    $parts = preg_split('/<<+/', $name);

    if (count($parts) < 2) {
        $parts = explode("<", $name);
    }

    $surname = "";

    $given = "";

    if (isset($parts[0])) {

        $surname = str_replace("<", " ", $parts[0]);

        $surname = preg_replace('/\s+/', ' ', trim($surname));

    }

    if (count($parts) > 1) {

        $givenParts = array_slice($parts, 1);

        $given = implode(" ", $givenParts);

        $given = str_replace("<", " ", $given);

        $given = preg_replace('/\s+/', ' ', trim($given));

    }

    $data["Surname"] = $surname;

    $data["Given Names"] = $given;

    return $data;

}

/*==========================================================
    PARSE OCR LABELS
==========================================================*/

function parseOCRLabels($text)
{

    $data = [];

    $lines = explode("\n", $text);

    $total = count($lines);

    for ($i = 0; $i < $total; $i++) {

        $line = strtoupper(trim($lines[$i]));

        if ($line == "")
            continue;

        /*--------------------------------------------------
            PLACE OF BIRTH
        --------------------------------------------------*/

        if (
            strpos($line, "PLACE OF BIRTH") !== false ||
            strpos($line, "BIRTH PLACE") !== false ||
            strpos($line, "जन्म स्थान") !== false
        ) {

            for ($j = $i + 1; $j < $total; $j++) {

                $next = trim($lines[$j]);

                if ($next == "")
                    continue;

                if (preg_match('/\d{2}[\/.-]\d{2}[\/.-]\d{4}/', $next))
                    continue;

                if (strlen($next) < 3)
                    continue;

                $data["Place of Birth"] = strtoupper($next);

                break;

            }

        }

        /*--------------------------------------------------
            PLACE OF ISSUE
        --------------------------------------------------*/

        if (
            strpos($line, "PLACE OF ISSUE") !== false ||
            strpos($line, "ISSUE PLACE") !== false ||
            strpos($line, "जारी करने का स्थान") !== false
        ) {

            for ($j = $i + 1; $j < $total; $j++) {

                $next = trim($lines[$j]);

                if ($next == "")
                    continue;

                if (preg_match('/\d{2}[\/.-]\d{2}[\/.-]\d{4}/', $next))
                    continue;

                if (strlen($next) < 3)
                    continue;

                $data["Place of Issue"] = strtoupper($next);

                break;

            }

        }

        /*--------------------------------------------------
          DATE OF ISSUE
        --------------------------------------------------*/

        if (
            strpos($line, "DATE OF ISSUE") !== false ||
            strpos($line, "ISSUE DATE") !== false ||
            strpos($line, "जारी करने की तिथि") !== false
        ) {

            $dates = [];

            // Look for the next 2 dates (Issue + Expiry)
            for ($j = $i; $j < min($i + 6, $total); $j++) {

                if (preg_match('/(\d{2}[\/.-]\d{2}[\/.-]\d{4})/', $lines[$j], $m)) {
                    $dates[] = formatDate($m[1]);
                }

                if (count($dates) == 2)
                    break;
            }

            if (count($dates) > 0) {

                sort($dates); // Earlier date = Issue Date

                $data["Date of Issue"] = $dates[0];
            }

        }

    }

    return $data;

}

/*==========================================================
    MERGE DATA
==========================================================*/

function mergePassportData($mrz, $ocr)
{

    return array_merge($ocr, $mrz);

}

/*==========================================================
    VALIDATE PASSPORT
==========================================================*/

function validatePassport(&$data)
{

    $required = [

        "Passport Number",
        "Surname",
        "Given Names",
        "Nationality",
        "Sex",
        "Date of Birth",
        "Date of Expiry"

    ];

    foreach ($required as $field) {

        if (!isset($data[$field]))
            $data[$field] = "";

    }

    /* Passport Number */

    if ($data["Passport Number"] != "") {

        $data["Passport Number"] = preg_replace('/[^A-Z0-9]/', '', $data["Passport Number"]);

    }

    /* Gender */

    if (isset($data["Sex"])) {

        $g = strtoupper(trim($data["Sex"]));

        if ($g != "M" && $g != "F")
            $g = "";

        $data["Sex"] = $g;

    }

    /* Nationality */

    if (isset($data["Nationality"])) {

        $nat = strtoupper(trim($data["Nationality"]));

        $country = [

            "IND" => "INDIAN",
            "PAK" => "PAKISTANI",
            "BGD" => "BANGLADESHI",
            "NPL" => "NEPALI",
            "LKA" => "SRI LANKAN"

        ];

        if (isset($country[$nat]))
            $nat = $country[$nat];

        $data["Nationality"] = $nat;

    }

    /*-------------------------------------------------------
        FIX ISSUE DATE USING EXPIRY
    -------------------------------------------------------*/

    if (
        !empty($data["Date of Expiry"])
    ) {

        $expiry = strtotime($data["Date of Expiry"]);

        $issue = !empty($data["Date of Issue"])
            ? strtotime($data["Date of Issue"])
            : 0;

        $fix = false;

        if (!$issue) {

            $fix = true;

        } elseif ($issue >= $expiry) {

            $fix = true;

        } else {

            $years = ($expiry - $issue) / (365 * 24 * 60 * 60);

            if ($years < 4 || $years > 11) {

                $fix = true;

            }

        }

        if ($fix) {

            $d = new DateTime($data["Date of Expiry"]);

            $d->modify("-10 years");

            $d->modify("+1 day");

            $data["Date of Issue"] = $d->format("Y-m-d");

        }

    }

    /* Remove double spaces */

    foreach ($data as $k => $v) {

        $v = trim($v);

        $v = preg_replace('/\s+/', ' ', $v);

        $data[$k] = $v;

    }

}

/*==========================================================
    OCR CONFIDENCE
==========================================================*/

function calculateConfidence($data)
{

    $score = 0;

    $fields = [

        "Passport Number",
        "Surname",
        "Given Names",
        "Nationality",
        "Sex",
        "Date of Birth",
        "Date of Issue",
        "Date of Expiry",
        "Place of Birth",
        "Place of Issue"

    ];

    foreach ($fields as $f) {

        if (isset($data[$f]) && trim($data[$f]) != "")
            $score += 10;

    }

    return $score;

}

/*==========================================================
    DEBUG
==========================================================*/

function debugOCR($ocr, $mrz, $final)
{

    echo "<pre>";

    echo "=========== OCR TEXT ===========\n\n";

    echo htmlspecialchars($ocr);

    echo "\n\n=========== MRZ ===========\n\n";

    print_r($mrz);

    echo "\n\n=========== FINAL ===========\n\n";

    print_r($final);

    echo "</pre>";

    exit;

}


/*==========================================================
    JSON RESPONSE
==========================================================*/

function responseJSON($data)
{

    header("Content-Type: application/json");

    echo json_encode($data, JSON_PRETTY_PRINT);

    exit;

}
