// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.

/*
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
*/

#include "precomp.hpp"
#include "opencv2/face/facemark_train.hpp"

/*dataset parser*/
#include <fstream>
#include <sstream>
#include <string>
#include <stdlib.h>     /* atoi */

namespace cv {
namespace face {

using namespace std;

CParams::CParams(String s, double sf, int minN, Size minSz, Size maxSz){
    cascade = s;
    scaleFactor = sf;
    minNeighbors = minN;
    minSize = minSz;
    maxSize = maxSz;

    if (!face_cascade.load(cascade))
    {
        CV_Error_(Error::StsBadArg, ("Error loading face_cascade: %s", cascade.c_str()));
    }
}

bool getFaces(InputArray image, OutputArray faces, CParams* params)
{
    CV_Assert(params);
    Mat gray;
    std::vector<Rect> roi;

    cvtColor(image.getMat(), gray, COLOR_BGR2GRAY);
    equalizeHist(gray, gray);

    params->face_cascade.detectMultiScale( gray, roi, params->scaleFactor, params->minNeighbors, CASCADE_SCALE_IMAGE, params->minSize, params->maxSize);

    Mat(roi).copyTo(faces);
    return true;
}

bool loadDatasetList(String imageList, String groundTruth, std::vector<String> & images, std::vector<String> & landmarks){
    std::string line;

    /*clear the output containers*/
    images.clear();
    landmarks.clear();

    /*open the files*/
    std::ifstream infile;
    infile.open(imageList.c_str(), std::ios::in);
    std::ifstream ss_gt;
    ss_gt.open(groundTruth.c_str(), std::ios::in);
    if ((!infile) || !(ss_gt)) {
       printf("No valid input file was given, please check the given filename.\n");
       return false;
    }

     /*load the images path*/
    while (getline (infile, line)){
        images.push_back(line);
    }

    /*load the points*/
    while (getline (ss_gt, line)){
        landmarks.push_back(line);
    }

    return true;
}

bool loadTrainingData(String filename, std::vector<String> & images, OutputArray _facePoints, char delim, float offset){
    std::string line;
    std::string item;
    std::vector<Point2f> pts;
    std::vector<float> raw;

    // FIXIT
    std::vector<std::vector<Point2f> > & facePoints =
        *(std::vector<std::vector<Point2f> >*) _facePoints.getObj();

    std::ifstream infile;
    infile.open(filename.c_str(), std::ios::in);
    if (!infile) {
        CV_Error_(Error::StsBadArg, ("No valid input file was given, please check the given filename: %s", filename.c_str()));
    }

    /*clear the output containers*/
    images.clear();
    facePoints.clear();

    /*the main loading process*/
    while (getline (infile, line)){
        std::istringstream ss(line); // string stream for the current line

        /*pop the image path*/
        getline (ss, item, delim);
        images.push_back(item);

        /*load all numbers*/
        raw.clear();
        while (getline (ss, item, delim)){
            raw.push_back((float)atof(item.c_str()));
        }

        /*convert to opencv points*/
        pts.clear();
        for(unsigned i = 0;i< raw.size();i+=2){
            pts.push_back(Point2f(raw[i]+offset,raw[i+1]+offset));
        }
        facePoints.push_back(pts);
    } // main loading process

    return true;
}

bool loadTrainingData(String imageList, String groundTruth, std::vector<String> & images, OutputArray _facePoints, float offset){
    std::string line;
    std::vector<Point2f> facePts;

    // FIXIT
    std::vector<std::vector<Point2f> > & facePoints =
            *(std::vector<std::vector<Point2f> >*) _facePoints.getObj();

    /*clear the output containers*/
    images.clear();
    facePoints.clear();

    /*load the images path*/
    std::ifstream infile;
    infile.open(imageList.c_str(), std::ios::in);
    if (!infile) {
       CV_Error_(Error::StsBadArg, ("No valid input file was given, please check the given filename: %s", imageList.c_str()));
    }

    while (getline (infile, line)){
        images.push_back(line);
    }

    /*load the points*/
    std::ifstream ss_gt(groundTruth.c_str());
    while (getline (ss_gt, line)){
        facePts.clear();
        loadFacePoints(line, facePts, offset);
        facePoints.push_back(facePts);
    }

    return true;
}

bool loadFacePoints(String filename, OutputArray points, float offset){
    vector<Point2f> pts;

    std::string line, item;
    std::ifstream infile(filename.c_str());

    /*pop the version*/
    std::getline(infile, line);
    CV_Assert(line.compare(0,7,"version")==0);

    /*pop the number of points*/
    std::getline(infile, line);
    CV_Assert(line.compare(0,8,"n_points")==0);

    /*get the number of points*/
    std::string item_npts;
    int npts;

    std::istringstream linestream(line);
    linestream>>item_npts>>npts;

    /*pop out '{' character*/
    std::getline(infile, line);

    /*main process*/
    int cnt = 0;
    std::string x, y;
    pts.clear();
    while (std::getline(infile, line) && cnt<npts )
    {
        cnt+=1;

        std::istringstream ss(line);
        ss>>x>>y;
        pts.push_back(Point2f((float)atof(x.c_str())+offset,(float)atof(y.c_str())+offset));

    }

    Mat(pts).copyTo(points);
    return true;
}

bool getFacesHAAR(InputArray image, OutputArray faces, const String& face_cascade_name)
{
    Mat gray;
    vector<Rect> roi;
    CascadeClassifier face_cascade;
    CV_Assert(face_cascade.load(face_cascade_name) && "Can't loading face_cascade");
    cvtColor(image.getMat(), gray, COLOR_BGR2GRAY);
    equalizeHist(gray, gray);
    face_cascade.detectMultiScale(gray, roi, 1.1, 2, 0|CASCADE_SCALE_IMAGE, Size(30, 30));
    Mat(roi).copyTo(faces);
    return true;
}

bool loadTrainingData(vector<String> filename,vector< vector<Point2f> >
                          & trainlandmarks,vector<String> & trainimages)
{
    string img;
    vector<Point2f> temp;
    string s;
    string tok;
    vector<string> coordinates;
    ifstream f1;
    for(unsigned long j=0;j<filename.size();j++){
        f1.open(filename[j].c_str(),ios::in);
        if(!f1.is_open()){
            cout<<filename[j]<<endl;
            CV_Error(Error::StsError, "File can't be opened for reading!");
        }
        //get the path of the image whose landmarks have to be detected
        getline(f1,img);
        //push the image paths in the vector
        trainimages.push_back(img);
        img.clear();
        while(getline(f1,s)){
            Point2f pt;
            stringstream ss(s); // Turn the string into a stream.
            while(getline(ss, tok,',')) {
                coordinates.push_back(tok);
                tok.clear();
            }
            pt.x = (float)atof(coordinates[0].c_str());
            pt.y = (float)atof(coordinates[1].c_str());
            coordinates.clear();
            temp.push_back(pt);
        }
        trainlandmarks.push_back(temp);
        temp.clear();
        f1.close();
    }
    return true;
}

void drawFacemarks(InputOutputArray image, InputArray points, Scalar color){
    Mat img = image.getMat();
    vector<Point2f> pts = points.getMat();
    for(size_t i=0;i<pts.size();i++){
        circle(img, pts[i],3, color,-1);
    }
}
} /* namespace face */
} /* namespace cv */