/*
 * Copyright 2019 Google LLC
 *
 * 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.cloud.examples.dialogflow.utils;

import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.Credentials;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.dialogflow.v2beta1.AudioEncoding;
import com.google.cloud.dialogflow.v2beta1.InputAudioConfig;
import com.google.cloud.dialogflow.v2beta1.DetectIntentRequest;
import com.google.cloud.dialogflow.v2beta1.DetectIntentResponse;
import com.google.cloud.dialogflow.v2beta1.KnowledgeAnswers;
import com.google.cloud.dialogflow.v2beta1.KnowledgeBasesSettings;
import com.google.cloud.dialogflow.v2beta1.OutputAudioConfig;
import com.google.cloud.dialogflow.v2beta1.OutputAudioEncoding;
import com.google.cloud.dialogflow.v2beta1.QueryInput;
import com.google.cloud.dialogflow.v2beta1.QueryParameters;
import com.google.cloud.dialogflow.v2beta1.QueryResult;
import com.google.cloud.dialogflow.v2beta1.SentimentAnalysisRequestConfig;
import com.google.cloud.dialogflow.v2beta1.SessionName;
import com.google.cloud.dialogflow.v2beta1.SessionsClient;
import com.google.cloud.dialogflow.v2beta1.SessionsSettings;
import com.google.cloud.dialogflow.v2beta1.TextInput;
import com.google.cloud.examples.dialogflow.AppController;
import com.google.protobuf.ByteString;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.TimeZone;


public class ApiRequest {

    private String token = null;
    private Date tokenExpiration = null;

    public ApiRequest() {
        // Variables needed to retrieve an auth token
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'",
                java.util.Locale.getDefault());
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    }

    /**
     * function to call: detect Intent Sentiment Analysis | Detect Intent With TTS | KnowledgeBase
     *
     * @param accessToken :   access token received from fcm
     * @param expiryTime  :   expiry time received from fcm
     * @param msg         :   message sent by the user
     * @param tts         :   send message to text to speech if true
     * @param sentiment   :   send message to sentiment analysis if true
     * @param knowledge   :   send message to knowledge base if true
     * @return :   response from the server
     */
    public String callAPI(String accessToken, Date expiryTime, String msg, byte[] audioBytes, boolean tts,
                          boolean sentiment, boolean knowledge) {
        this.token = accessToken;
        this.tokenExpiration = expiryTime;

        return detectIntent(msg, audioBytes, tts, sentiment, knowledge);
    }

    /**
     * function to getting the results from the dialogflow
     *
     * @param msg        :   message sent by the user
     * @param audioBytes :   audio sent by the user
     * @param tts        :   send message to text to speech if true
     * @param sentiment  :   send message to sentiment analysis if true
     * @param knowledge  :   send message to knowledge base if true
     * @return :   response from the server
     */
    private String detectIntent(String msg, byte[] audioBytes, boolean tts, boolean sentiment,
                                boolean knowledge) {
        try {
            AccessToken accessToken = new AccessToken(token, tokenExpiration);
            Credentials credentials = GoogleCredentials.create(accessToken);
            FixedCredentialsProvider fixedCredentialsProvider =
                    FixedCredentialsProvider.create(credentials);
            SessionsSettings sessionsSettings = SessionsSettings.newBuilder()
                    .setCredentialsProvider(fixedCredentialsProvider).build();
            SessionsClient sessionsClient = SessionsClient.create(sessionsSettings);
            SessionName sessionName = SessionName.of(AppController.PROJECT_ID,
                    AppController.SESSION_ID);

            QueryInput queryInput;
            if (msg != null) {
                // Set the text (hello) and language code (en-US) for the query
                TextInput textInput = TextInput.newBuilder()
                        .setText(msg)
                        .setLanguageCode("en-US")
                        .build();

                // Build the query with the TextInput
                queryInput = QueryInput.newBuilder().setText(textInput).build();
            } else {
                // Instructs the speech recognizer how to process the audio content.
                InputAudioConfig inputAudioConfig = InputAudioConfig.newBuilder()
                        .setAudioEncoding(AudioEncoding.AUDIO_ENCODING_AMR)
                        .setLanguageCode("en-US")
                        .setSampleRateHertz(8000)
                        .build();

                // Build the query with the TextInput
                queryInput = QueryInput.newBuilder().setAudioConfig(inputAudioConfig).build();
            }

            DetectIntentRequest detectIntentRequest = getDetectIntentRequest(sessionName,
                    queryInput, tts, sentiment, knowledge, fixedCredentialsProvider, audioBytes);

            DetectIntentResponse detectIntentResponse =
                    sessionsClient.detectIntent(detectIntentRequest);

            sessionsClient.close();

            if (tts) {
                AppController.playAudio(detectIntentResponse.getOutputAudio().toByteArray());
            }

            if (msg != null) {
                return handleResults(detectIntentResponse);
            } else {
                return String.format(
                        "%s|%s",
                        handleResults(detectIntentResponse),
                        detectIntentResponse.getQueryResult().getQueryText());
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            return ex.getMessage();
        }
    }

    /**
     * function to get the DetectIntentRequest object
     *
     * @param sessionName              : sessionName object
     * @param queryInput               : queryInput object
     * @param tts                      : if text to speech is true
     * @param sentiment                : if sentiment analysis is true
     * @param knowledge                : if knowledge base is true
     * @param fixedCredentialsProvider : fixedCredentialsProvider for knowledgebase
     * @return : DetectIntentRequest object
     */
    private DetectIntentRequest getDetectIntentRequest(SessionName sessionName,
            QueryInput queryInput, boolean tts, boolean sentiment, boolean knowledge,
            FixedCredentialsProvider fixedCredentialsProvider, byte[] audioBytes) throws Exception {

        DetectIntentRequest.Builder detectIntentRequestBuilder = DetectIntentRequest.newBuilder()
                .setSession(sessionName.toString())
                .setQueryInput(queryInput);

        if (audioBytes != null) {
            detectIntentRequestBuilder.setInputAudio(ByteString.copyFrom(audioBytes));
        }

        QueryParameters.Builder queryParametersBuilder = QueryParameters.newBuilder();

        if (tts) {
            OutputAudioEncoding audioEncoding = OutputAudioEncoding.OUTPUT_AUDIO_ENCODING_MP3;
            int sampleRateHertz = 16000;
            OutputAudioConfig outputAudioConfig = OutputAudioConfig.newBuilder()
                    .setAudioEncoding(audioEncoding)
                    .setSampleRateHertz(sampleRateHertz)
                    .build();

            detectIntentRequestBuilder.setOutputAudioConfig(outputAudioConfig);
        }

        if (sentiment) {
            SentimentAnalysisRequestConfig sentimentAnalysisRequestConfig =
                    SentimentAnalysisRequestConfig.newBuilder()
                            .setAnalyzeQueryTextSentiment(true).build();

            queryParametersBuilder
                    .setSentimentAnalysisRequestConfig(sentimentAnalysisRequestConfig);
        }


        if (knowledge) {
            KnowledgeBasesSettings knowledgeSessionsSettings = KnowledgeBasesSettings.newBuilder()
                    .setCredentialsProvider(fixedCredentialsProvider).build();
            ArrayList<String> knowledgeBaseNames =
                    KnowledgeBaseUtils.listKnowledgeBases(
                            AppController.PROJECT_ID, knowledgeSessionsSettings);

            if (knowledgeBaseNames.size() > 0) {
                // As an example, we'll only grab the first Knowledge Base
                queryParametersBuilder.addKnowledgeBaseNames(knowledgeBaseNames.get(0));
            }
        }

        QueryParameters queryParameters = queryParametersBuilder.build();
        detectIntentRequestBuilder.setQueryParams(queryParameters);

        return detectIntentRequestBuilder.build();
    }

    /**
     * function to handle the results
     *
     * @param detectIntentResponse :   detectIntentResponse object
     * @return :   String response
     */
    private String handleResults(DetectIntentResponse detectIntentResponse) {
        QueryResult queryResult = detectIntentResponse.getQueryResult();
        StringBuilder response = new StringBuilder();

        KnowledgeAnswers knowledgeAnswers = queryResult.getKnowledgeAnswers();
        for (KnowledgeAnswers.Answer answer : knowledgeAnswers.getAnswersList()) {
            response.append(answer.getAnswer()).append("\n");
        }

        response.append(queryResult.getFulfillmentText());

        if (queryResult.hasSentimentAnalysisResult()) {
            String magnitude = String.format("(Magnitude: %s; ",
                    queryResult.getSentimentAnalysisResult().getQueryTextSentiment().getMagnitude());
            response.append(magnitude);

            String score = String.format("score: %s)",
                    queryResult.getSentimentAnalysisResult().getQueryTextSentiment().getScore());
            response.append(score);
        }

        return response.toString();
    }
}