/*
Copyright 2018 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package com.google.rbm.samples.dialogflow;

// [START of Dialogflow helper class]

// [START import_libraries]
import com.google.api.gax.core.CredentialsProvider;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.cloud.dialogflow.v2beta1.*;
import com.google.protobuf.Value;

import java.awt.*;
import java.io.File;
import java.io.FileInputStream;
import java.security.MessageDigest;
import java.util.Map;
import java.util.logging.Logger;
// [END import_libraries]

/**
 * DialogFlow helper class for detecting intent from plaintext RBM responses.
 */
public class DialogFlowHelper {
    private static final Logger logger = Logger.getLogger(DialogFlowHelper.class.getName());

    // TODO: replace the project id with the project id of your Dialogflow model
    // this can be found under the gear icon in the Dialogflow console
    private static String DIALOGFLOW_PROJECT_ID = "DIALOGFLOW_PROJECT_ID";

    private static String LANGUAGE_CODE = "EN";

    private SessionsSettings.Builder sessionsSettings;

    public DialogFlowHelper() {
        initCredentials();
    }

    private void initCredentials() {
        // TODO: update the service account credentials file if needed
        String credentialsFileLocation = "dialogflow-service-account-credentials.json";

        ClassLoader classLoader = getClass().getClassLoader();
        File file = new File(classLoader.getResource(credentialsFileLocation).getFile());

        try {
            // create the credential provider based on the JSON file
            CredentialsProvider credentialsProvider =
                    FixedCredentialsProvider.create(ServiceAccountCredentials
                            .fromStream(
                                    new FileInputStream(file)
                            )
                    );

            // create a session settings object to use for DialogFlow calls
            sessionsSettings = SessionsSettings
                    .newBuilder()
                    .setCredentialsProvider(credentialsProvider);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Returns the result of detect intent with texts as inputs.
     * @param text The plaintext to detect the intent with.
     * @param msisdn The phone number in E.164 format.
     * @return A wrapper object with important about the intent returned from DialogFlow.
     */
    public DialogFlowResponse detectIntentTexts(String text, String msisdn) {
        try {
            byte[] bytesOfMessage = msisdn.getBytes("UTF-8");

            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] theDigest = md.digest(bytesOfMessage);

            // generate a session id based on a hash of the client's phone number
            String sessionId = new String(theDigest);

            // call the detect intent function with the correct parameters
           return detectIntentTexts(DIALOGFLOW_PROJECT_ID, text, sessionId, LANGUAGE_CODE);
        } catch(Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * Returns the result of detect intent with event as input.
     * @param eventName The event name to trigger an intent.
     * @param msisdn The phone number in E.164 format.
     * @return A wrapper object with important about the intent returned from DialogFlow.
     */
    public DialogFlowResponse detectIntentFromEvent(String eventName, String msisdn) {
        try {
            byte[] bytesOfMessage = msisdn.getBytes("UTF-8");

            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] theDigest = md.digest(bytesOfMessage);

            // generate a session id based on a hash of the client's phone number
            String sessionId = new String(theDigest);

            // call the detect intent function with the correct parameters
            return detectIntentFromEvent(DIALOGFLOW_PROJECT_ID, eventName, sessionId, LANGUAGE_CODE);
        } catch(Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    // [START dialogflow_detect_intent_text]
    /**
     * Returns the result of detect intent with texts as inputs.
     *
     * Using the same `session_id` between requests allows continuation of the conversation.
     * @param projectId Project/Agent Id.
     * @param text The text intents to be detected based on what a user says.
     * @param sessionId Identifier of the DetectIntent session.
     * @param languageCode Language code of the query.
     * @return A wrapper object with important about the intent returned from DialogFlow.
     */
    private DialogFlowResponse detectIntentTexts(String projectId,
                                                 String text,
                                                 String sessionId,
                                                 String languageCode) throws Exception {
        // Detect intents for the text input
        // Set the text (hello) and language code (en-US) for the query
        TextInput.Builder textInput = TextInput
                .newBuilder().setText(text).setLanguageCode(languageCode);

        // Build the query with the TextInput
        QueryInput queryInput = QueryInput.newBuilder().setText(textInput).build();

        return detectIntent(projectId, sessionId, queryInput);
    }
    // [END dialogflow_detect_intent_text]

    // [START dialogflow_detect_intent_event]
    /**
     * Returns the result of detect intent with event name as input.
     *
     * @param projectId Project/Agent Id.
     * @param eventName The event name to trigger an intent.
     * @param sessionId Identifier of the DetectIntent session.
     * @param languageCode Language code of the query.
     * @return A wrapper object with important about the intent returned from DialogFlow.
     */
    private DialogFlowResponse detectIntentFromEvent(String projectId,
                                                 String eventName,
                                                 String sessionId,
                                                 String languageCode) throws Exception {
        // Detect intents for the event name input
        EventInput.Builder eventInput = EventInput
                .newBuilder().setName(eventName).setLanguageCode(languageCode);

        // Build the query with the EventInput
        QueryInput queryInput = QueryInput.newBuilder().setEvent(eventInput).build();

        return detectIntent(projectId, sessionId, queryInput);
    }
    // [END dialogflow_detect_intent_event]

    private DialogFlowResponse detectIntent(String projectId,
                                            String sessionId,
                                            QueryInput queryInput) throws Exception {
        // Instantiates a client
        try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings.build())) {
            // Set the session name using the sessionId (UUID) and projectID (my-project-id)
            SessionName session = SessionName.of(projectId, sessionId);

            // Performs the detect intent request
            DetectIntentResponse response = sessionsClient.detectIntent(session, queryInput);

            // Display the query result
            QueryResult queryResult = response.getQueryResult();

            logger.info("====================");
            logger.info("Query Text: " + queryResult.getQueryText());
            logger.info("Detected Intent:" +
                    queryResult.getIntent().getDisplayName());
            logger.info("Fulfillment Text: " + queryResult.getFulfillmentText());

            for(Map.Entry<String, Value> entry: queryResult.getParameters().getFieldsMap().entrySet()) {
                logger.info(entry.getKey() + " " + entry.getValue().getStringValue());
            }

            // this is a hack because the DialogFlow API does not include the actionIncomplete flag
            boolean isComplete = !queryResult.getFulfillmentText().contains("?");

            // construct a response object with the parameters we need
            return new DialogFlowResponse(queryResult.getIntent().getDisplayName(),
                    queryResult.getFulfillmentText(),
                    isComplete,
                    queryResult.getParameters().getFieldsMap()
            );
        }
    }
}
// [END of Dialogflow helper class]