/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* ***************************************************************************
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as 
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 *  As a special exception, you may use this file as part of a free software
 *  library without restriction.  Specifically, if other files instantiate
 *  templates or use macros or inline functions from this file, or you compile
 *  this file and link it with other files to produce an executable, this
 *  file does not by itself cause the resulting executable to be covered by
 *  the GNU General Public License.  This exception does not however
 *  invalidate any other reasons why the executable file might be covered by
 *  the GNU General Public License.
 *
 ****************************************************************************
 */

/* 
 * Author: Marco Danelutto <marcod@di.unipi.it> 
 * Date:   September 2015
 * 
 */

#include <opencv2/opencv.hpp>
#include <ff/pipeline.hpp>
#include <ff/farm.hpp>

using namespace ff; 
using namespace cv;

// reads freame and sends them to the next stage
struct Source : ff_node_t<cv::Mat> {
    cv::VideoCapture cap; 

    Source(cv::VideoCapture cappe):cap(cappe) {}
  
    cv::Mat * svc(cv::Mat *) {
	for(;;) {
	    Mat * frame = new Mat();
	    if(cap.read(*frame))  ff_send_out(frame);
	    else {
		std::cout << "End of stream in input" << std::endl; 
		break;
	    }
	}
	return EOS;
    }
}; 

// this stage applys all the filters:  the GaussianBlur filter and the Sobel one, 
// and it then sends the result to the next stage
struct Stage1 : ff_node_t<cv::Mat> {
    cv::Mat * svc(cv::Mat *frame) {
	Mat frame1;
	cv::GaussianBlur(*frame, frame1, cv::Size(0, 0), 3);
	cv::addWeighted(*frame, 1.5, frame1, -0.5, 0, *frame);
	cv::Sobel(*frame,*frame,-1,1,0,3);
	return frame;
  }

}; 

struct StageEnhance : ff_node_t<cv::Mat> {
    cv::Mat * svc(cv::Mat *frame) {
	Mat frame1;
	cv::GaussianBlur(*frame, frame1, cv::Size(0, 0), 3);
	cv::addWeighted(*frame, 1.5, frame1, -0.5, 0, *frame);
	return frame;
  }

}; 

struct StageSobel : ff_node_t<cv::Mat> {
    cv::Mat * svc(cv::Mat *frame) {
	cv::Sobel(*frame,*frame,-1,1,0,3);
	return frame;
  }

}; 

cv::Mat * filters (cv::Mat * frame, ff_node* const) {
    Mat frame1;
    cv::GaussianBlur(*frame, frame1, cv::Size(0, 0), 3);
    cv::addWeighted(*frame, 1.5, frame1, -0.5, 0, *frame);
    cv::Sobel(*frame,*frame,-1,1,0,3);
    return frame;
}

// this stage shows the output
struct Drain: ff_node_t<cv::Mat> {

    Drain(bool ovf, cv::VideoWriter vw):outvideo(ovf),outputVideoFile(vw) {}

    int svc_init() {
	if(outvideo) 
        namedWindow("edges",1);
	return 0; 
    }

    cv::Mat *svc (cv::Mat * frame) {
	if(outvideo) {
	    imshow("edges", *frame);
	    waitKey(30);    // why 30ms here ?
	} else {
        outputVideoFile << *frame; 
    }
	delete frame;
	return GO_ON;
    }
protected:
    const bool outvideo; 
    cv::VideoWriter outputVideoFile; 
}; 

int main(int argc, char *argv[]) {

    if(argc == 1) {
      std::cout << "Usage is: " << argv[0] 
		<< " input_filename whichoutput shapeNo nw1 [nw2]" 
                << std::endl <<
          "\twhichoutput = 1 (window) = 0 (file) " <<  std::endl << 
          "\tshapeno = 0 (Source|Farm|Drain) \n\t\t= 1 (Farm(Source)|Drain)) \n\t\t= 2 (Farm(Source,Drain) \n\t\t=3 (Source|Farm|Farm|Drain) " << 
          std::endl;
      return(0); 
    }

    // output 
    bool video_output = false; 
    if(atoi(argv[2]) == 1) {
        video_output = true; 
        std::cout << "Showing video " << std::endl;
    }

    int shapeNo = atoi(argv[3]); 
    int nw2 = 0; 
    if(shapeNo == 3) 
        nw2 = atoi(argv[5]); 
    std::cout << "Pattern is " << shapeNo << std::endl; 

    // pardegree 
    size_t nw1 = 1;
    if(argc == 5) {
      nw1 = atol(argv[4]); 
    }

    // opening input video
    VideoCapture cap(argv[1]); 
    // VideoCapture cap(0); // default camera input
	if(!cap.isOpened())  {  
	    std::cout << "Error opening input file" << std::endl;
	    return(-1);
	} 

    // opening output video
    cv::VideoWriter outputVideoFile; 
    if(!video_output) {
        int ex = static_cast<int>(cap.get(CV_CAP_PROP_FOURCC));
        // int ex = 0x00000021;
        Size S = Size((int) cap.get(CV_CAP_PROP_FRAME_WIDTH),
                      (int) cap.get(CV_CAP_PROP_FRAME_HEIGHT));  
        outputVideoFile.open("ciccio.mp4", ex, cap.get(CV_CAP_PROP_FPS), S, true);
        if (!outputVideoFile.isOpened()) {
            std::cout  << "Could not open the output video for write "  << std::endl;
            return -1;
        } else {
            std::cout << "output file opened " << std::endl; 
        }
    }
    

    switch(shapeNo) {
    case 0: {
        ff_Pipe<> pipe(make_unique<Source>(cap));
        pipe.add_stage(make_unique<ff_OFarm<cv::Mat> >([nw1]() {
                    std::vector<std::unique_ptr<ff_node> > W; 
                    for(size_t i=0; i<nw1; i++) 
                        W.push_back(make_unique<Stage1>());
                    return W;
                    
                } ()
                ));	  
        pipe.add_stage(make_unique<Drain>(video_output, outputVideoFile));
        ffTime(START_TIME);
        // starts the pipeline and waits for termination
        if (pipe.run_and_wait_end()<0) {
            error("running pipeline");
            return -1;
        }
        ffTime(STOP_TIME);

        std::cout << "Elapsed (Source|Farm(" << nw1 << ")|Drain) ";
        std::cout << ffTime(GET_TIME) << " ms\n";

        break; 
    }
    case 1: {
        ff_OFarm<cv::Mat> ofarm(filters, nw1); 
        ofarm.setEmitterF(new Source(cap));

        // creates the pipe and adds the first stage
        ff_Pipe<> pipe(ofarm);
    
    
        // adds the last stage 
        pipe.add_stage(make_unique<Drain>(video_output, outputVideoFile));

        ffTime(START_TIME);
        // starts the pipeline and waits for termination
        if (pipe.run_and_wait_end()<0) {
            error("running pipeline");
            return -1;
        }
        ffTime(STOP_TIME);

        std::cout << "Elapsed (Farm(Source," << nw1 << ")|Drain),"; 
        std::cout << ffTime(GET_TIME) << " ms\n";

        break; 
    }
    case 2: { // this does not work, the file produced does not make sense
        ff_OFarm<cv::Mat> ofarm(filters, nw1); 
        ofarm.setEmitterF(new Source(cap));
        ofarm.setCollectorF(new Drain(video_output, outputVideoFile));


        ffTime(START_TIME);
        // starts the pipeline and waits for termination
        if (ofarm.run_and_wait_end()<0) {
            error("running pipeline");
            return -1;
        }
        ffTime(STOP_TIME);

        std::cout << "Elapsed (Farm(Source,Drain," << nw1 << ")),"; 
        std::cout << ffTime(GET_TIME) << " ms\n";

        break; 
    }
    case 3: {
        ff_Pipe<> pipe(make_unique<Source>(cap));
        pipe.add_stage(make_unique<ff_OFarm<cv::Mat> >([nw1]() {
                    std::vector<std::unique_ptr<ff_node> > W; 
                    for(size_t i=0; i<nw1; i++) 
                        W.push_back(make_unique<StageEnhance>());
                    return W;
                    
                } ()
                ));	  
        pipe.add_stage(make_unique<ff_OFarm<cv::Mat> >([nw2]() {
                    std::vector<std::unique_ptr<ff_node> > W; 
                    for(size_t i=0; i<nw2; i++) 
                        W.push_back(make_unique<StageSobel>());
                    return W;
                    
                } ()
                ));	  
        pipe.add_stage(make_unique<Drain>(video_output, outputVideoFile));
        ffTime(START_TIME);
        // starts the pipeline and waits for termination
        if (pipe.run_and_wait_end()<0) {
            error("running pipeline");
            return -1;
        }
        ffTime(STOP_TIME);

        std::cout << "Elapsed (Source|Farm(" << nw1 << "),Farm(" <<nw2<< ")|Drain) ";
        std::cout << ffTime(GET_TIME) << " ms\n";



        break; 
    }
    default: 
        std::cout << "Wrong shape no " << std::endl; 
    }

    return 0;
}

