// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
// my blog about Qt for mobile: http://j.mp/qt-x
// see also /COPYRIGHT and /LICENSE

#include "shareUtils/androidshareutils.h"

#include <QUrl>
#include <QFileInfo>
#include <QDateTime>

#include <QtAndroidExtras/QAndroidJniObject>
#include <jni.h>

const static int RESULT_OK = -1;
const static int RESULT_CANCELED = 0;

AndroidShareUtils* AndroidShareUtils::mInstance = NULL;

AndroidShareUtils::AndroidShareUtils(QObject* parent) : PlatformShareUtils(parent)
{
    // we need the instance for JNI Call
    mInstance = this;
}

AndroidShareUtils* AndroidShareUtils::getInstance()
{
    if (!mInstance) {
        mInstance = new AndroidShareUtils;
        qWarning() << "AndroidShareUtils should be instantiated !";
    }

    return mInstance;
}

bool AndroidShareUtils::checkMimeTypeView(const QString &mimeType)
{
    QAndroidJniObject jsMime = QAndroidJniObject::fromString(mimeType);
    jboolean verified = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
                        "checkMimeTypeView",
                        "(Ljava/lang/String;)Z",
                        jsMime.object<jstring>());
    qDebug() << "View VERIFIED: " << mimeType << " - " << verified;
    return verified;
}

bool AndroidShareUtils::checkMimeTypeEdit(const QString &mimeType)
{
    QAndroidJniObject jsMime = QAndroidJniObject::fromString(mimeType);
    jboolean verified = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
                        "checkMimeTypeEdit",
                        "(Ljava/lang/String;)Z",
                        jsMime.object<jstring>());
    qDebug() << "Edit VERIFIED: " << mimeType << " - " << verified;
    return verified;
}

QString AndroidShareUtils::getTemporaryFileLocationPath() {
    return QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0) + "/temporaryFiles";
}

void AndroidShareUtils::shareText(const QString &text, const QUrl &url)
{
    QAndroidJniObject jsText = QAndroidJniObject::fromString(text + url.toString());
    jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
                  "shareText",
                  "(Ljava/lang/String;)Z",
                  jsText.object<jstring>());

    if(!ok) {
        qWarning() << "Unable to resolve activity from Java";
        emit shareNoAppAvailable(0);
    }
}

/*
 * As default we're going the Java - way with one simple JNI call (recommended)
 * if altImpl is true we're going the pure JNI way
 *
 * If a requestId was set we want to get the Activity Result back (recommended)
 * We need the Request Id and Result Id to control our workflow
*/
void AndroidShareUtils::sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
{
    mIsEditMode = false;

    QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
    QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
    QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
    jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
                  "sendFile",
                  "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
                  jsPath.object<jstring>(), jsTitle.object<jstring>(), jsMimeType.object<jstring>(), requestId);
    if(!ok) {
        qWarning() << "Unable to resolve activity from Java";
        emit shareNoAppAvailable(requestId);
    }
}

/*
 * As default we're going the Java - way with one simple JNI call (recommended)
 * if altImpl is true we're going the pure JNI way
 *
 * If a requestId was set we want to get the Activity Result back (recommended)
 * We need the Request Id and Result Id to control our workflow
*/
void AndroidShareUtils::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
{
    mIsEditMode = false;

    QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
    QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
    QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
    jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
                  "viewFile",
                  "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
                  jsPath.object<jstring>(), jsTitle.object<jstring>(), jsMimeType.object<jstring>(), requestId);
    if(!ok) {
        qWarning() << "Unable to resolve activity from Java";
        emit shareNoAppAvailable(requestId);
    }
}

/*
 * As default we're going the Java - way with one simple JNI call (recommended)
 * if altImpl is true we're going the pure JNI way
 *
 * If a requestId was set we want to get the Activity Result back (recommended)
 * We need the Request Id and Result Id to control our workflow
*/
void AndroidShareUtils::editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
{
    mIsEditMode = true;
    mCurrentFilePath = filePath;
    QFileInfo fileInfo = QFileInfo(mCurrentFilePath);
    mLastModified = fileInfo.lastModified().toSecsSinceEpoch();
    qDebug() << "LAST MODIFIED: " << mLastModified;

    QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
    QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
    QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);

    jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
                  "editFile",
                  "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
                  jsPath.object<jstring>(), jsTitle.object<jstring>(), jsMimeType.object<jstring>(), requestId);

    if(!ok) {
        qWarning() << "Unable to resolve activity from Java";
        emit shareNoAppAvailable(requestId);
    }
}

// used from QAndroidActivityResultReceiver
void AndroidShareUtils::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)
{
    Q_UNUSED(data);
    qDebug() << "From JNI QAndroidActivityResultReceiver: " << receiverRequestCode << "ResultCode:" << resultCode;
    processActivityResult(receiverRequestCode, resultCode);
}

// used from Activity.java onActivityResult()
void AndroidShareUtils::onActivityResult(int requestCode, int resultCode)
{
    qDebug() << "From Java Activity onActivityResult: " << requestCode << "ResultCode:" << resultCode;
    processActivityResult(requestCode, resultCode);
}

void AndroidShareUtils::processActivityResult(int requestCode, int resultCode)
{
    // we're getting RESULT_OK only if edit is done
    if(resultCode == RESULT_OK) {
        emit shareEditDone(requestCode);
    } else if(resultCode == RESULT_CANCELED) {
        if(mIsEditMode) {
            // Attention: not all Apps will give you the correct ResultCode:
            // Google Fotos will send OK if saved and CANCELED if canceled
            // Some Apps always sends CANCELED even if you modified and Saved the File
            // so you should check the modified Timestamp of the File to know if
            // you should emit shareEditDone() or shareFinished() !!!
            QFileInfo fileInfo = QFileInfo(mCurrentFilePath);
            qint64 currentModified = fileInfo.lastModified().toSecsSinceEpoch();
            qDebug() << "CURRENT MODIFIED: " << currentModified;
            if(currentModified > mLastModified) {
                emit shareEditDone(requestCode);
                return;
            }
        }
        emit shareFinished(requestCode);
    } else {
        qDebug() << "wrong result code: " << resultCode << " from request: " << requestCode;
        emit shareError(requestCode, "Share: an Error occured");
    }
}

void AndroidShareUtils::checkPendingIntents(const QString workingDirPath)
{
    QAndroidJniObject activity = QtAndroid::androidActivity();
    if(activity.isValid()) {
        // create a Java String for the Working Dir Path
        QAndroidJniObject jniWorkingDir = QAndroidJniObject::fromString(workingDirPath);
        if(!jniWorkingDir.isValid()) {
            qWarning() << "QAndroidJniObject jniWorkingDir not valid.";
            emit shareError(0, "Share: an Error occured\nWorkingDir not valid");
            return;
        }
        activity.callMethod<void>("checkPendingIntents","(Ljava/lang/String;)V", jniWorkingDir.object<jstring>());
        qDebug() << "checkPendingIntents: " << workingDirPath;
        return;
    }
    qDebug() << "checkPendingIntents: Activity not valid";
}

void AndroidShareUtils::setFileUrlReceived(const QString &url)
{
    if(url.isEmpty()) {
        qWarning() << "setFileUrlReceived: we got an empty URL";
        emit shareError(0, "Empty URL received");
        return;
    }
    qDebug() << "AndroidShareUtils setFileUrlReceived: we got the File URL from JAVA: " << url;
    QString myUrl;
    if(url.startsWith("file://")) {
        myUrl= url.right(url.length()-7);
        qDebug() << "QFile needs this URL: " << myUrl;
    } else {
        myUrl= url;
    }

    // check if File exists
    QFileInfo fileInfo = QFileInfo(myUrl);
    if(fileInfo.exists()) {
        emit fileUrlReceived(myUrl);
    } else {
        qDebug() << "setFileUrlReceived: FILE does NOT exist ";
        emit shareError(0, QString("File does not exist: %1").arg(myUrl));
    }
}

void AndroidShareUtils::setOtherUrlReceived(const QString &url, const QString &scheme)
{
    if(url.isEmpty()) {
        qWarning() << "setFileUrlReceived: we got an empty URL";
        emit shareError(0, "Empty URL received");
        return;
    }
    qDebug() << "AndroidShareUtils setOtherUrlReceived: we got the Other URL from JAVA: " << url;

    emit otherUrlReceived(url, scheme);
}

void AndroidShareUtils::setFileReceivedAndSaved(const QString &url)
{
    if(url.isEmpty()) {
        qWarning() << "setFileReceivedAndSaved: we got an empty URL";
        emit shareError(0, "Empty URL received");
        return;
    }
    qDebug() << "AndroidShareUtils setFileReceivedAndSaved: we got the File URL from JAVA: " << url;
    QString myUrl;
    if(url.startsWith("file://")) {
        myUrl= url.right(url.length()-7);
        qDebug() << "QFile needs this URL: " << myUrl;
    } else {
        myUrl= url;
    }

    // check if File exists
    QFileInfo fileInfo = QFileInfo(myUrl);
    if(fileInfo.exists()) {
        emit fileReceivedAndSaved(myUrl);
    } else {
        qDebug() << "setFileReceivedAndSaved: FILE does NOT exist ";
        emit shareError(0, QString("File does not exist: %1").arg(myUrl));
    }
}

// to be safe we check if a File Url from java really exists for Qt
// if not on the Java side we'll try to read the content as Stream
bool AndroidShareUtils::checkFileExits(const QString &url)
{
    if(url.isEmpty()) {
        qWarning() << "checkFileExits: we got an empty URL";
        emit shareError(0, "Empty URL received");
        return false;
    }
    qDebug() << "AndroidShareUtils checkFileExits: we got the File URL from JAVA: " << url;
    QString myUrl;
    if(url.startsWith("file://")) {
        myUrl= url.right(url.length()-7);
        qDebug() << "QFile needs this URL: " << myUrl;
    } else {
        myUrl= url;
    }

    // check if File exists
    QFileInfo fileInfo = QFileInfo(myUrl);
    if(fileInfo.exists()) {
        qDebug() << "Yep: the File exists for Qt";
        return true;
    } else {
        qDebug() << "Uuups: FILE does NOT exist ";
        return false;
    }
}

// instead of defining all JNICALL as demonstrated below
// there's another way, making it easier to manage all the methods
// see https://www.kdab.com/qt-android-episode-5/

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT void JNICALL
Java_de_itsblue_blueROCK_MainActivity_setFileUrlReceived(JNIEnv *env,
        jobject obj,
        jstring url)
{
    const char *urlStr = env->GetStringUTFChars(url, NULL);
    Q_UNUSED (obj)
    AndroidShareUtils::getInstance()->setFileUrlReceived(urlStr);
    env->ReleaseStringUTFChars(url, urlStr);
    return;
}

JNIEXPORT void JNICALL
Java_de_itsblue_blueROCK_MainActivity_setOtherUrlReceived(JNIEnv *env,
        jobject obj,
        jstring url,
        jstring scheme)
{
    const char *urlStr = env->GetStringUTFChars(url, NULL);
    const char *schemeStr = env->GetStringUTFChars(scheme, NULL);
    Q_UNUSED (obj)
    AndroidShareUtils::getInstance()->setOtherUrlReceived(urlStr, schemeStr);
    env->ReleaseStringUTFChars(url, urlStr);
    env->ReleaseStringUTFChars(scheme, schemeStr);
    return;
}

JNIEXPORT void JNICALL
Java_de_itsblue_blueROCK_MainActivity_setFileReceivedAndSaved(JNIEnv *env,
        jobject obj,
        jstring url)
{
    const char *urlStr = env->GetStringUTFChars(url, NULL);
    Q_UNUSED (obj)
    AndroidShareUtils::getInstance()->setFileReceivedAndSaved(urlStr);
    env->ReleaseStringUTFChars(url, urlStr);
    return;
}

JNIEXPORT bool JNICALL
Java_de_itsblue_blueROCK_MainActivity_checkFileExits(JNIEnv *env,
        jobject obj,
        jstring url)
{
    const char *urlStr = env->GetStringUTFChars(url, NULL);
    Q_UNUSED (obj)
    bool exists = AndroidShareUtils::getInstance()->checkFileExits(urlStr);
    env->ReleaseStringUTFChars(url, urlStr);
    return exists;
}

JNIEXPORT void JNICALL
Java_de_itsblue_blueROCK_MainActivity_fireActivityResult(JNIEnv *env,
        jobject obj,
        jint requestCode,
        jint resultCode)
{
    Q_UNUSED (obj)
    Q_UNUSED (env)
    AndroidShareUtils::getInstance()->onActivityResult(requestCode, resultCode);
    return;
}

#ifdef __cplusplus
}
#endif
