<?php
// Author: Sergi Diaz Planas

include "./utils/idvalidation.php";
include "./utils/protocol.php";
include "./config.php";

// SESSION START -----------------------------------------------------
if (version_compare(PHP_VERSION, '5.4.0', '<')) // support for multiple php versions
{
    if (session_id() == '') { session_start(); }
} 
else  
{
    if (session_status() == PHP_SESSION_NONE) { session_start(); }
}
//---------------------------------------------------------------------

// ATTEND DEMANDS FROM CLIENT -----------------------------------------
$request   = "";
$codedImg  = "";
$data      = "";
if(isset($_POST[Protocol::$requestName])) { $request = $_POST[Protocol::$requestName]; }
if(isset($_POST[Protocol::$imageName])) { $codedImg = $_POST[Protocol::$imageName]; }
if(isset($_POST[Protocol::$dataName])) { $data = $_POST[Protocol::$dataName];}

// Regardless the request, try to decode base64 string into an image
$tempString = explode(",", $codedImg);  // extract the base64 encoded chain from message
if(!$tempString[1])
{
    // Stop if the string recieved from client has an unknown format
    print json_encode(Protocol::errorMessage("Request message has to contain a base64-encoded image."));
    exit(1);
}
// Conversion to an image.
$b64 = $tempString[1];
$decodedImg = getImage($b64);
if (!$decodedImg)
{ 
    // We cannot do anymore if the string was not correctly encoded!
    print json_encode(Protocol::errorMessage("Input recieved cannot be decoded into an image."));
    exit(1);
}

// Redirect to request management
if ($request == Protocol::$referenceRequest) 
{ 
    manageReferenceRequest($decodedImg, $data);
} 
elseif ($request == Protocol::$verificationRequest)
{
    manageVerificationRequest($decodedImg);
} 
else // exit if unknown request code
{
    print json_encode(Protocol::errorMessage("Unknown request code " . $request));
    exit(1);
} 
//---------------------------------------------------------------------------

// REQUEST MANAGEMENT FUNCTIONS ---------------------------------------------

// Attend a request for setting a new reference face image.
function manageReferenceRequest($image, $data)
{
    // Try to retrieve the rectangles from data, and exit if unable to do it.
    $rectangles = Protocol::getRectangles($data);
    if (!$rectangles )
    {      
        print json_encode(Protocol::errorMessage("Cannot read the rectangles."));
        exit(1);
    }

    // Get a face detector object
    $detector = getDetector();

    // Crop the image into three subimages, each containing the document photo, the DNI
    // number and the person names (name + surnames)
    $photoImage = $image->crop($rectangles["photo"]["x"], $rectangles["photo"]["y"], $rectangles["photo"]["w"], $rectangles["photo"]["h"]);
    $dniImage = $image->crop($rectangles["dni"]["x"], $rectangles["dni"]["y"], $rectangles["dni"]["w"], $rectangles["dni"]["h"]);
    $namesImage = $image->crop($rectangles["names"]["x"], $rectangles["names"]["y"], $rectangles["names"]["w"], $rectangles["names"]["h"]);     
    $surnamesImage = $image->crop($rectangles["surnames"]["x"], $rectangles["surnames"]["y"], $rectangles["surnames"]["w"], $rectangles["surnames"]["h"]);

    // Exit if the image could not be cropped correctly.
    if ($photoImage->isEmpty() || $dniImage->isEmpty() || $namesImage->isEmpty())
    {
        print json_encode(Protocol::errorMessage("Given rectangle dimensions do not match the document size."));
        exit(1);
    }

    if (!$detector->isLoaded()) {
        $detector->loadClassifier(Config::$classifierPath);
        $detector->loadPredictor(Config::$predictorPath);
    }

    $face = $detector->getFace($photoImage);
    // Exit if no face has been found.
    if ($face->isEmpty())
    {
        print json_encode(Protocol::errorMessage("No face is to be found in the image."));
        exit(1);
    }

    // Check whether the face is centered in the photo rectange and the eyes aligned
    if ((2*$face->getLeftX() > $rectangles["photo"]["w"]) || (2*$face->getLeftY() > $rectangles["photo"]["h"]) ||
        abs($face->getLeftY() - $face->getRightY()) > Config::$yTolerance)
    {
        print json_encode(Protocol::errorMessage("The document has no been placed correctly."));
        exit(1);
    }

    // Do the document reading
    $reader = getReader();  // get a reader component object
    $reader->setDataPath(Config::$tessdataPath); 
    $dniText = $reader->read($dniImage);
    $dniText = IDValidation::cleanUpText($dniText);
    // Read names
    $namesText = $reader->read($namesImage); 
    $names = IDValidation::extractNames($namesText);
    // Read surnames
    $surnamesText = $reader->read($surnamesImage);
    $surnames = IDValidation::extractNames($namesText);

    // Check if the document is a valid identity document
    if (!IDValidation::validateDNI($dniText)) 
    {
        // The document provided is not a valid document
        //print json_encode(Protocol::errorMessage("The document provided is not a valid document. Invalid id: ". $dniText . ", names= " . $namesText));
        print json_encode(Protocol::errorMessage($namesText));
        exit(1);
    }

    // Set face as the reference face image
    setReference($face);

    // Pack the information read from document and return it to the client
    print json_encode(Protocol::responseToReference($dniText, $names[0], $surnames));
    exit(0);
}

// Attend a request for validating a face image against a preset reference.
function manageVerificationRequest($image)
{
    // Try to retrieve the reference image from session. Exit if no session has been set
    if (!isset($_SESSION[Config::$referenceSessionName]))
    {
        print json_encode(Protocol::errorMessage("A reference has to be set before attempting a validation."));
        exit(1);
    }
    $reference = $_SESSION[Config::$referenceSessionName];

    // Get a detector object and try to get the face from input image
    $detector = getDetector();
    $face = $detector->getFace($image);

    // Exit if no face has been found.
    if ($face->isEmpty())
    {
        print json_encode(Protocol::errorMessage("No face is to be found in the image."));
        exit(1);
    }

    // Get a verifier and use it to compare input face with reference
    $verifier = getVerifier();
    if ($verifier->samePerson($reference, $face)) // successful verification
    {
        // Store the user in session
        $_SESSION["username"] = "user";

        print json_encode(Protocol::responseToVerification());
        exit(1);
    }
    else    // failed verification
    {
        print json_encode(Protocol::errorMessage("Verification failed!."));
        exit(1);
    }
}

// Get an instance of WFDetector.
function getDetector()
{
    // Instantiate a new object if current session does not store a detector or
    // if the server is configurated to do it.
    if (!isset($_SESSION[Config::$detectorSessionName]) || !Config::$detectorStoreInSession)
    {
        $detector = new WFDetector();
        // Load the detector
        $detector->loadClassifier(Config::$classifierPath);
        $detector->loadPredictor(Config::$predictorPath);
        // Send an error message if the detector could not be loaded
        if (!$detector->isLoaded())
        {
            print json_encode(Protocol::errorMessage("Unable to access face detector."));
            exit(1);
        }
        // Store in session if required
        if (Config::$detectorStoreInSession) { $_SESSION[Config::$detectorSessionName] = $detector; }  
        return $detector;   // return object
    }
    return $_SESSION[Config::$detectorSessionName]; // return from session
}

// Get an instance of WFVerifier.
function getVerifier()
{
    // Instantiate a new object if current session does not store a verifier or
    // if the server is configurated to do it.
    if (!isset($_SESSION[Config::$verifierSessionName]) || !Config::$verifierStoreInSession) 
    {
        $verifier = new WFVerifier();
        $verifier->loadNet(Config::$netPath);   // load verifier
        // Send an error message if the verifier could not be loaded
        if (!$verifier->isLoaded())
        {
            print json_encode(Protocol::errorMessage("Unable to access face verifier."));
            exit(1);
        }
        // Store in session if required
        if (Config::$verifierStoreInSession) { $_SESSION[Config::$verifierSessionName] = $verifier; }
        return $verifier;   // return object 
    }
    return $_SESSION[Config::$verifierSessionName]; // return from session
}

// Get an instance of WFReader.
function getReader()
{
    // Instantiate a new object if current session does not store a reader or
    // if the server is configurated to do it.
    if (!isset($_SESSION[Config::$readerSessionName]) || !Config::$readerStoreInSession)
    {
        $reader = new WFReader();
        $reader->setDataPath(Config::$tessdataPath);    // set a new path for tesseract data
        // Store in session if required
        if (Config::$readerStoreInSession) { $_SESSION[Config::$readerSessionName] = $reader; }
        return $reader; // return object
    }
    return $_SESSION[Config::$readerSessionName]; // return from session
}

// Return an WFImage object containing the image encoded in a given string.
function getImage($b64) 
{
    $image = new WFImage();
    $image->load($b64);

    // Return false if decoding failed
    if ($image->isEmpty()) 
    {
        return false;
    }
    return $image;
}

// Store in session an WFFace object as the reference to be used when making face verifications.
function setReference($ref)
{
    if (!$ref->isEmpty())
    {
        $_SESSION[Config::$referenceSessionName] = $ref;
    }
}


//---------------------------------------------------------------------------