/*
This file was part of GSoC Project: Facemark API for OpenCV
Final report: https://gist.github.com/kurnianggoro/74de9121e122ad0bd825176751d47ecc
Student: Laksono Kurnianggoro
Mentor: Delia Passalacqua
*/

/*----------------------------------------------
 * Usage:
 * facemark_demo_lbf <face_cascade_model> <saved_model_filename> <training_images> <annotation_files> [test_files]
 *
 * Example:
 * facemark_demo_lbf ../face_cascade.xml ../LBF.model ../images_train.txt ../points_train.txt ../test.txt
 *
 * Notes:
 * the user should provides the list of training images_train
 * accompanied by their corresponding landmarks location in separated files.
 * example of contents for images_train.txt:
 * ../trainset/image_0001.png
 * ../trainset/image_0002.png
 * example of contents for points_train.txt:
 * ../trainset/image_0001.pts
 * ../trainset/image_0002.pts
 * where the image_xxxx.pts contains the position of each face landmark.
 * example of the contents:
 *  version: 1
 *  n_points:  68
 *  {
 *  115.167660 220.807529
 *  116.164839 245.721357
 *  120.208690 270.389841
 *  ...
 *  }
 * example of the dataset is available at https://ibug.doc.ic.ac.uk/download/annotations/ibug.zip
 *--------------------------------------------------*/

#include <stdio.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include "opencv2/core.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/face.hpp"

using namespace std;
using namespace cv;
using namespace cv::face;

static bool myDetector( InputArray image, OutputArray roi, CascadeClassifier *face_detector);
static bool parseArguments(int argc, char** argv, String & cascade,
   String & model, String & images, String & annotations, String & testImages
);

int main(int argc, char** argv)
{
    String cascade_path,model_path,images_path, annotations_path, test_images_path;
    if(!parseArguments(argc, argv, cascade_path,model_path,images_path, annotations_path, test_images_path))
       return -1;

    /*create the facemark instance*/
    FacemarkLBF::Params params;
    params.model_filename = model_path;
    params.cascade_face = cascade_path;
    Ptr<FacemarkLBF> facemark = FacemarkLBF::create(params);

    CascadeClassifier face_cascade;
    face_cascade.load(params.cascade_face.c_str());
    facemark->setFaceDetector((FN_FaceDetector)myDetector, &face_cascade);

    /*Loads the dataset*/
    std::vector<String> images_train;
    std::vector<String> landmarks_train;
    loadDatasetList(images_path,annotations_path,images_train,landmarks_train);

    Mat image;
    std::vector<Point2f> facial_points;
    for(size_t i=0;i<images_train.size();i++){
        printf("%i/%i :: %s\n", (int)(i+1), (int)images_train.size(),images_train[i].c_str());
        image = imread(images_train[i].c_str());
        loadFacePoints(landmarks_train[i],facial_points);
        facemark->addTrainingSample(image, facial_points);
    }

    /*train the Algorithm*/
    facemark->training();

    /*test using some images*/
    String testFiles(images_path), testPts(annotations_path);
    if(!test_images_path.empty()){
        testFiles = test_images_path;
        testPts = test_images_path; //unused
    }
    std::vector<String> images;
    std::vector<String> facePoints;
    loadDatasetList(testFiles, testPts, images, facePoints);

    std::vector<Rect> rects;
    CascadeClassifier cc(params.cascade_face.c_str());
    for(size_t i=0;i<images.size();i++){
        std::vector<std::vector<Point2f> > landmarks;
        cout<<images[i];
        Mat img = imread(images[i]);
        facemark->getFaces(img, rects);
        facemark->fit(img, rects, landmarks);

        for(size_t j=0;j<rects.size();j++){
            drawFacemarks(img, landmarks[j], Scalar(0,0,255));
            rectangle(img, rects[j], Scalar(255,0,255));
        }

        if(rects.size()>0){
            cout<<endl;
            imshow("result", img);
            waitKey(0);
        }else{
            cout<<"face not found"<<endl;
        }
    }
}

bool myDetector(InputArray image, OutputArray faces, CascadeClassifier *face_cascade)
{
    Mat gray;

    if (image.channels() > 1)
        cvtColor(image, gray, COLOR_BGR2GRAY);
    else
        gray = image.getMat().clone();

    equalizeHist(gray, gray);

    std::vector<Rect> faces_;
    face_cascade->detectMultiScale(gray, faces_, 1.4, 2, CASCADE_SCALE_IMAGE, Size(30, 30));
    Mat(faces_).copyTo(faces);
    return true;
}

bool parseArguments(int argc, char** argv,
    String & cascade,
    String & model,
    String & images,
    String & annotations,
    String & test_images
){
    const String keys =
        "{ @c cascade         |      | (required) path to the face cascade xml file fo the face detector }"
        "{ @i images          |      | (required) path of a text file contains the list of paths to all training images}"
        "{ @a annotations     |      | (required) Path of a text file contains the list of paths to all annotations files}"
        "{ @m model           |      | (required) path to save the trained model }"
        "{ t test-images      |      | Path of a text file contains the list of paths to the test images}"
        "{ help h usage ?     |      | facemark_demo_lbf -cascade -images -annotations -model [-t] \n"
         " example: facemark_demo_lbf ../face_cascade.xml ../images_train.txt ../points_train.txt ../lbf.model}"
    ;
    CommandLineParser parser(argc, argv,keys);
    parser.about("hello");

    if (parser.has("help")){
        parser.printMessage();
        return false;
    }

    cascade = String(parser.get<String>("cascade"));
    model = String(parser.get<string>("model"));
    images = String(parser.get<string>("images"));
    annotations = String(parser.get<string>("annotations"));
    test_images = String(parser.get<string>("t"));

    cout<<"cascade : "<<cascade.c_str()<<endl;
    cout<<"model : "<<model.c_str()<<endl;
    cout<<"images : "<<images.c_str()<<endl;
    cout<<"annotations : "<<annotations.c_str()<<endl;

    if(cascade.empty() || model.empty() || images.empty() || annotations.empty()){
        std::cerr << "one or more required arguments are not found" << '\n';

        parser.printMessage();
        return false;
    }

    return true;
}