/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                           License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of the copyright holders may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "precomp.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/ml.hpp"

#include <iostream>
#include <fstream>
#include <queue>

#ifdef HAVE_TESSERACT
#if !defined(USE_STD_NAMESPACE)
#define USE_STD_NAMESPACE
#endif
#include <tesseract/baseapi.h>
#include <tesseract/resultiterator.h>
#endif

namespace cv
{
namespace text
{

using namespace std;

void OCRTesseract::run(Mat& image, string& output_text, vector<Rect>* component_rects,
                       vector<string>* component_texts, vector<float>* component_confidences,
                       int component_level)
{
    CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) );
    CV_Assert( (component_level == OCR_LEVEL_TEXTLINE) || (component_level == OCR_LEVEL_WORD) );
    output_text.clear();
    if (component_rects != NULL)
        component_rects->clear();
    if (component_texts != NULL)
        component_texts->clear();
    if (component_confidences != NULL)
        component_confidences->clear();
}

void OCRTesseract::run(Mat& image, Mat& mask, string& output_text, vector<Rect>* component_rects,
                       vector<string>* component_texts, vector<float>* component_confidences,
                       int component_level)
{
    CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) );
    CV_Assert( mask.type() == CV_8UC1 );
    CV_Assert( (component_level == OCR_LEVEL_TEXTLINE) || (component_level == OCR_LEVEL_WORD) );
    output_text.clear();
    if (component_rects != NULL)
        component_rects->clear();
    if (component_texts != NULL)
        component_texts->clear();
    if (component_confidences != NULL)
        component_confidences->clear();
}

CV_WRAP String OCRTesseract::run(InputArray image, int min_confidence, int component_level)
{
    std::string output1;
    std::string output2;
    vector<string> component_texts;
    vector<float> component_confidences;
    Mat image_m = image.getMat();
    run(image_m, output1, NULL, &component_texts, &component_confidences, component_level);
    for(unsigned int i = 0; i < component_texts.size(); i++)
    {
        // cout << "confidence: " << component_confidences[i] << " text:" << component_texts[i] << endl;
        if(component_confidences[i] > min_confidence)
        {
            output2 += component_texts[i];
        }
    }
    return String(output2);
}

CV_WRAP String OCRTesseract::run(InputArray image, InputArray mask, int min_confidence, int component_level)
{
    std::string output1;
    std::string output2;
    vector<string> component_texts;
    vector<float> component_confidences;
    Mat image_m = image.getMat();
    Mat mask_m = mask.getMat();
    run(image_m, mask_m, output1, NULL, &component_texts, &component_confidences, component_level);
    for(unsigned int i = 0; i < component_texts.size(); i++)
    {
        // cout << "confidence: " << component_confidences[i] << " text:" << component_texts[i] << endl;

        if(component_confidences[i] > min_confidence)
        {
            output2 += component_texts[i];
        }
    }
    return String(output2);
}


class OCRTesseractImpl CV_FINAL : public OCRTesseract
{
private:
#ifdef HAVE_TESSERACT
    tesseract::TessBaseAPI tess;
#endif

public:
    //Default constructor
    OCRTesseractImpl(const char* datapath, const char* language, const char* char_whitelist, int oemode, int psmode)
    {

#ifdef HAVE_TESSERACT
        const char *lang = "eng";
        if (language != NULL)
            lang = language;

        if (tess.Init(datapath, lang, (tesseract::OcrEngineMode)oemode))
        {
            cout << "OCRTesseract: Could not initialize tesseract." << endl;
            throw 1;
        }

        //cout << "OCRTesseract: tesseract version " << tess.Version() << endl;

        tesseract::PageSegMode pagesegmode = (tesseract::PageSegMode)psmode;
        tess.SetPageSegMode(pagesegmode);

        // tessedit_whitelist default changes from [0-9a-zA-Z] to "".
        // See https://github.com/opencv/opencv_contrib/issues/3457
        if(char_whitelist != NULL)
            tess.SetVariable("tessedit_char_whitelist", char_whitelist);
        else
            tess.SetVariable("tessedit_char_whitelist", "");

        tess.SetVariable("save_best_choices", "T");
#else
        cout << "OCRTesseract("<<oemode<<psmode<<"): Tesseract not found." << endl;
        if (datapath != NULL)
            cout << "            " << datapath << endl;
        if (language != NULL)
            cout << "            " << language << endl;
        if (char_whitelist != NULL)
            cout << "            " << char_whitelist << endl;
#endif
    }

    ~OCRTesseractImpl() CV_OVERRIDE
    {
#ifdef HAVE_TESSERACT
        tess.End();
#endif
    }

    void run(Mat& image, string& output, vector<Rect>* component_rects=NULL,
             vector<string>* component_texts=NULL, vector<float>* component_confidences=NULL,
             int component_level=0) CV_OVERRIDE
    {

        CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) );

#ifdef HAVE_TESSERACT

        if (component_texts != 0)
            component_texts->clear();
        if (component_rects != 0)
            component_rects->clear();
        if (component_confidences != 0)
            component_confidences->clear();

        tess.SetImage((uchar*)image.data, image.size().width, image.size().height, image.channels(), image.step1());
        tess.Recognize(0);
        char *outText;
        outText = tess.GetUTF8Text();
        output = string(outText);
        if (outText != NULL)
            delete [] outText;

        if ( (component_rects != NULL) || (component_texts != NULL) || (component_confidences != NULL) )
        {
            tesseract::ResultIterator* ri = tess.GetIterator();
            tesseract::PageIteratorLevel level = tesseract::RIL_WORD;
            if (component_level == OCR_LEVEL_TEXTLINE)
                level = tesseract::RIL_TEXTLINE;

            if (ri != 0) {
                do {
                    const char* word = ri->GetUTF8Text(level);
                    if (word == NULL)
                        continue;
                    float conf = ri->Confidence(level);
                    int x1, y1, x2, y2;
                    ri->BoundingBox(level, &x1, &y1, &x2, &y2);

                    if (component_texts != 0)
                        component_texts->push_back(string(word));
                    if (component_rects != 0)
                        component_rects->push_back(Rect(x1,y1,x2-x1,y2-y1));
                    if (component_confidences != 0)
                        component_confidences->push_back(conf);

                    delete[] word;
                } while (ri->Next(level));
                delete ri;
            }
        }

        tess.Clear();

#else

        cout << "OCRTesseract(" << component_level << image.type() <<"): Tesseract not found." << endl;
        output.clear();
        if(component_rects)
            component_rects->clear();
        if(component_texts)
            component_texts->clear();
        if(component_confidences)
            component_confidences->clear();
#endif
    }

    void run(Mat& image, Mat& mask, string& output, vector<Rect>* component_rects=NULL,
             vector<string>* component_texts=NULL, vector<float>* component_confidences=NULL,
             int component_level=0) CV_OVERRIDE
    {
        CV_Assert( mask.type() == CV_8UC1 );
        CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) );

        run( mask, output, component_rects, component_texts, component_confidences, component_level);
    }

    void setWhiteList(const String& char_whitelist) CV_OVERRIDE
    {
  #ifdef HAVE_TESSERACT
        tess.SetVariable("tessedit_char_whitelist", char_whitelist.c_str());
  #else
        CV_UNUSED(char_whitelist);
  #endif
    }
};

Ptr<OCRTesseract> OCRTesseract::create(const char* datapath, const char* language,
                                       const char* char_whitelist, int oem, int psmode)
{
    return makePtr<OCRTesseractImpl>(datapath, language, char_whitelist, oem, psmode);
}


}
}
