/*
  main.cpp
  
  Author: Sergi Diaz Planas (2017)
*/
#include <phpcpp.h>

#include "Image.h"
#include "Face.h"
#include "FaceDetector.h"
#include "FaceVerifier.h"
#include "Reader.h"
#include "utils.h"

#include <opencv2/imgproc/imgproc.hpp>

// Image
class WFImage : public Php::Base
{
private:
    Image image;
public:
    WFImage() { this->image = Image(); }
    virtual ~WFImage() = default;

    Image getImage()           { return this->image;  }
    void setImage(Image image) { this->image = image; }

    Php::Value base64()  { 
        if (this->image.isEmpty()) { return ""; }
        std::string b64 = mat_to_base64(this->image.getImage());
        return "";       
    }
    Php::Value isEmpty() { return this->image.isEmpty(); }
    Php::Value load(Php::Parameters &params)
    {
        return this->image.load(params[0]);
    }
    Php::Value width()  { return this->image.getImage().cols; }
    Php::Value height() { return this->image.getImage().rows; }
    Php::Value crop(Php::Parameters &params)
    {
        try
        {
            // Convert from Image to WFImage before returning
            WFImage *wfImage = new WFImage();
            wfImage->setImage(this->image.crop(params[0], params[1], params[2], params[3]));
            return Php::Object("WFImage", wfImage);
        }
        catch (int e) {}
    return Php::Object("WFImage", new WFImage());
    }

    void storeImage(Php::Parameters &params)
    {
        std::string path = params[0];
        cv::Mat matrix = this->image.getImage();

        cv::imwrite(path, matrix);
    }
};

// Face
class WFFace : public Php::Base
{
private:
    Face face;
public:
    WFFace() { this->face = Face(); }
    virtual ~WFFace() = default;

    Face getFace()          { return this->face; }
    void setFace(Face face) { this->face = face; }
    Php::Value isEmpty()    { return this->face.isEmpty(); }

    Php::Value getLeftX()  { return this->face.getLeftX(); }
    Php::Value getLeftY()  { return this->face.getLeftY(); }
    Php::Value getRightX() { return this->face.getRightX(); }
    Php::Value getRightY() { return this->face.getRightY(); }
};

// FaceDetector
class WFDetector : public Php::Base
{
private:
    FaceDetector detector;
public:
    WFDetector() { detector = FaceDetector(); }
    virtual ~WFDetector() = default;

    Php::Value isLoaded(Php::Parameters &params)
    {
        return this->detector.isLoaded();
    }
    Php::Value loadClassifier(Php::Parameters &params)
    {
        try
        {
            return this->detector.loadClassifier(params[0]);
        }
        catch (int e) {}
        return false;
    }
    Php::Value loadPredictor(Php::Parameters &params)
    {
        try
        {
            return this->detector.loadShapePredictor(params[0]);
        }
        catch (int e) {}
        return false;
    }
    Php::Value getFace(Php::Parameters &params)
    {
        try
        {
            // Store the first parameter in a variable
            Php::Value object = params[0];
            // Check if parameter is an instance of WFImage
            if (!object.instanceOf("WFImage"))
            {
                // Return an empty face
                return Php::Object("WFFace", new WFFace());
            }
            // Cast the PHP object to C++
            WFImage *image = (WFImage *)object.implementation();

            // Convert from Face to WFIFace before returning
            Face detectedFace = this->detector.getFace(image->getImage());
            WFFace *wfFace = new WFFace();
            wfFace->setFace(detectedFace);

            return Php::Object("WFFace", wfFace);
        }
        catch (int e) {}
        return Php::Object("WFFace", new WFFace());
    }
};

// FaceVerifier
class WFVerifier : public Php::Base
{
private:
    FaceVerifier verifier;
public:
    WFVerifier() { verifier = FaceVerifier(); }
    virtual ~WFVerifier() = default;

    Php::Value loadNet(Php::Parameters &params)
    {
        try
        {
            return this->verifier.loadNet(params[0]);
        }
        catch (int e) { return false; }
    }
    Php::Value isLoaded(Php::Parameters &params)
    {
        return this->verifier.isLoaded();
    }
    Php::Value samePerson(Php::Parameters &params)
    {
        try
        {
            // Store the parameters in php variables
            Php::Value object1 = params[0];
            Php::Value object2 = params[0];
            // Check if parameters are instances of WFFace
            if (!object1.instanceOf("WFFace") || !object2.instanceOf("WFFace"))
            {
                return false;
            }
            // Cast the PHP objects to C++
            WFFace *face1 = (WFFace *)object1.implementation();
            WFFace *face2 = (WFFace *)object2.implementation();

            return this->verifier.samePerson(face1->getFace(),
                                             face2->getFace());
        }
        catch (int e) {}
        return false;
    }
    void setThreshold(Php::Parameters &params)
    {
        try
        {
            double th = params[0];
            this->verifier.setThreshold(th);
        }
        catch (int e) {}
    }
};

// Reader
class WFReader : public Php::Base
{
private:
    Reader reader;
public:
    WFReader() { reader = Reader(); }
    virtual ~WFReader() = default;

    void setDataPath(Php::Parameters &params)
    {
        std::string path = params[0];
        this->reader.setLocalDataPath(path);
    }

    Php::Value read(Php::Parameters &params)
    {
        try
        {
            // Store the first parameter in a variable
            Php::Value object = params[0];
            // Check if parameter is an instance of WFImage
            if (!object.instanceOf("WFImage"))
            {
                // Return an empty strimg
                return "";
            }
            // Cast the PHP object to C++
            WFImage *image = (WFImage *)object.implementation();
            return this->reader.read(image->getImage());
        }
        catch (int e) {}
        return "";
    }
};


/* Switch to C context to ensure that the get_module() function
 * is callable by C programs (which the Zend engine is)
 *
 * */
extern "C"
{

    /**
     *  Function that is called by PHP right after the PHP process
     *  has started, and that returns an address of an internal PHP
     *  strucure with all the details and features of your extension
     *
     *  @return void*   a pointer to an address that is understood by PHP
     */
    PHPCPP_EXPORT void *get_module()
    {
        static Php::Extension extension("webface", "1.0");

        // Classes wrapped in this extension
        Php::Class<WFImage> image("WFImage");
        Php::Class<WFFace>  face("WFFace");
        Php::Class<WFDetector> detector("WFDetector");
        Php::Class<WFVerifier> verifier("WFVerifier");
        Php::Class<WFReader> reader("WFReader");

        // Descrive the classes so that PHP knows which methods are accessible.

        // WFImage
        image.method<&WFImage::base64>("base64");
        image.method<&WFImage::isEmpty>("isEmpty");
        image.method<&WFImage::load>("load",
        {
            Php::ByVal("file", Php::Type::String)
        });
        image.method<&WFImage::width>("width");
        image.method<&WFImage::height>("height");
        image.method<&WFImage::crop>("crop",
        {
            Php::ByVal("x", Php::Type::Numeric),
            Php::ByVal("y", Php::Type::Numeric),
            Php::ByVal("w", Php::Type::Numeric),
            Php::ByVal("h", Php::Type::Numeric)
        });
        image.method<&WFImage::storeImage>("storeImage",
        {
            Php::ByVal("file", Php::Type::String)
        });

        // WFFace
        face.method<&WFFace::isEmpty>("isEmpty");
        face.method<&WFFace::getLeftX>("getLeftX");
        face.method<&WFFace::getLeftY>("getLeftY");
        face.method<&WFFace::getRightX>("getRightX");
        face.method<&WFFace::getRightY>("getRightY");

        // WFDetector
        detector.method<&WFDetector::loadClassifier>("loadClassifier",
        {
            Php::ByVal("file", Php::Type::String)
        });
        detector.method<&WFDetector::isLoaded>("isLoaded");
        detector.method<&WFDetector::loadPredictor>("loadPredictor",
        {
            Php::ByVal("file", Php::Type::String)
        });
        detector.method<&WFDetector::getFace>("getFace");

        // WFVerifier
        verifier.method<&WFVerifier::loadNet>("loadNet",
        {
            Php::ByVal("file", Php::Type::String)
        });
        verifier.method<&WFVerifier::isLoaded>("isLoaded");
        verifier.method<&WFVerifier::samePerson>("samePerson");

        // WFReader
        reader.method<&WFReader::setDataPath>("setDataPath", 
        {
            Php::ByVal("file", Php::Type::String)
        });
        reader.method<&WFReader::read>("read");

        // Add the classes to the extension before returning it.
        extension.add(std::move(image));
        extension.add(std::move(face));
        extension.add(std::move(detector));
        extension.add(std::move(verifier));
        extension.add(std::move(reader));
        return extension;
    }
}
