/*
 * Copyright (C) 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;

// [START of the chat bot for Bonjour Rail]

// [START import_libraries]
import com.google.api.services.rcsbusinessmessaging.v1.model.*;
import com.google.rbm.samples.lib.*;
import com.google.rbm.samples.lib.cards.CardOrientation;
import com.google.rbm.samples.lib.cards.CardWidth;
import com.google.rbm.samples.lib.cards.MediaHeight;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
// [END import_libraries]

/**
 * Chat bot for the Bonjour Rail demo.
 *
 * All conversation is handled by this class.
 */
public class BonjourRailBot {
    // logger for info and error messages
    private static final Logger logger = Logger.getLogger(BonjourRailBot.class.getName());

    private static final String EXCEPTION_WAS_THROWN = "an exception was thrown";

    // constant for the URL to the rail ticket
    private static final String RAIL_TICKET_URL =
            "https://storage.googleapis.com/bonjour-rail.appspot.com/rail-ticket.png";

    private static final String SEATING_CHART_URL =
            "https://storage.googleapis.com/bonjour-rail.appspot.com/seating-chart-example.png";

    // constant for suggestion to view trip information
    private static final SuggestionHelper TRIP_INFORMATION_OPTION
            = new SuggestionHelper("Trip Information", "trip_info");

    // constant for suggestion to view food menu options
    private static final SuggestionHelper MENU_OPTION
            = new SuggestionHelper("Menu Options", "menu_options");

    // constant for suggestion to view the seating chart
    private static final SuggestionHelper SEATING_CHART_OPTION
            = new SuggestionHelper("Seating Chart", "seating_charting");

    // constant for suggestion to change the assigned seat
    private static final SuggestionHelper CHANGE_SEAT_OPTION
            = new SuggestionHelper("Change Seat", "change_seat");

    // constant for suggestion to speak with an agent
    private static final SuggestionHelper TALK_TO_AGENT_OPTION
            = new SuggestionHelper("Speak With an Agent", "talk_to_agent");

    // constant for suggestion to view the map of the trip
    private static final SuggestionHelper MAP_OPTION
            = new SuggestionHelper("View Map", "map");

    // constant for suggestion to save the trip as a calendar event
    private static final SuggestionHelper CALENDAR_OPTION
            = new SuggestionHelper("Add to Calendar", "calendar");

    // constant for suggestion to book open seat 14
    private static final SuggestionHelper OPEN_SEAT_14
            = new SuggestionHelper("Seat 14", "seat_14");

    // constant for suggestion to book open seat 15
    private static final SuggestionHelper OPEN_SEAT_15
            = new SuggestionHelper("Seat 15", "seat_15");

    // constant for suggestion to book open seat 21
    private static final SuggestionHelper OPEN_SEAT_21
            = new SuggestionHelper("Seat 21", "seat_21");

    // constant for ordering ham and cheese meal
    private static final SuggestionHelper ORDER_HAM_AND_CHEESE
            = new SuggestionHelper("Order", "order-ham-and-cheese");

    // constant for ordering ham and cheese meal
    private static final SuggestionHelper ORDER_CHICKEN_VEGGIE
            = new SuggestionHelper("Order", "order-chicken-veggie");

    // constant for ordering ham and cheese meal
    private static final SuggestionHelper ORDER_CHEESE_PLATE
            = new SuggestionHelper("Order", "order-cheese-plate");

    // constant for ordering ham and cheese meal
    private static final SuggestionHelper ORDER_APPLE_WALNUT
            = new SuggestionHelper("Order", "order-apple-walnut");

    // constant for carousel ham and cheese food item
    private static final StandaloneCardHelper HAM_AND_CHEESE_CARD =
            new StandaloneCardHelper(
                    "Ham and cheese sandwich",
                    "With choice of beverage",
                    "https://storage.googleapis.com/bonjour-rail.appspot.com/ham-and-cheese.png",
                    ORDER_HAM_AND_CHEESE);

    // constant for carousel chicken veggie wrap food item
    private static final StandaloneCardHelper CHICKEN_VEGGIE_WRAP_CARD =
            new StandaloneCardHelper(
                    "Chicken veggie wrap",
                    "With choice of beverage",
                    "https://storage.googleapis.com/bonjour-rail.appspot.com/chicken-veggie-wrap.png",
                    ORDER_CHICKEN_VEGGIE);

    // constant for carousel cheese plate food item
    private static final StandaloneCardHelper CHEESE_PLATE_CARD =
            new StandaloneCardHelper(
                    "Assorted cheese plate",
                    "With choice of beverage",
                    "https://storage.googleapis.com/bonjour-rail.appspot.com/assorted-cheese-plate.png",
                    ORDER_CHEESE_PLATE);

    // constant for carousel apple walnut food item
    private static final StandaloneCardHelper APPLE_WALNUT_CARD =
            new StandaloneCardHelper(
                    "Apple walnut salad",
                    "With choice of beverage",
                    "https://storage.googleapis.com/bonjour-rail.appspot.com/apple-walnut-salad.png",
                    ORDER_APPLE_WALNUT);

    // the phone number, in E.164 format, to start a conversation with
    private String msisdn;

    // wrapper class for the RBM API, makes calls simpler
    private RbmApiHelper rbmApiHelper;

    /**
     * Constructor for the Bonjour Rail bot.
     * @param msisdn The phone number in E.164 format.
     */
    public BonjourRailBot(String msisdn) {
        this.msisdn = msisdn;

        this.rbmApiHelper = new RbmApiHelper();
    }

    /**
     * Sends the initial greeting of to the user.
     */
    public void sendGreeting() throws IOException {
        List<Suggestion> suggestedActions = getDefaultSuggestionList();

        // create a standalone card for the rail ticket
        StandaloneCard standaloneCard = rbmApiHelper.createStandaloneCard(
                "Hello, welcome to Bonjour Rail.",
                "How can I help you?",
                null,
                MediaHeight.TALL,
                CardOrientation.VERTICAL,
                suggestedActions
        );

        rbmApiHelper.sendStandaloneCard(standaloneCard, msisdn);
    }

    /**
     * Response message handler for client responses.
     * @param userResponse The text of the user response.
     */
    public void handleResponse(String userResponse) {
        // send an is typing receipt to user unless this is a response from a suggested action
        if(!userResponse.equals(MAP_OPTION.getPostbackData())
                && !userResponse.equals(CALENDAR_OPTION.getPostbackData())
                && !userResponse.equals(TALK_TO_AGENT_OPTION.getPostbackData())) {
            // let the client know we are creating a response
            rbmApiHelper.sendIsTypingMessage(this.msisdn);
        }

        // check response against known actions
        if(userResponse.equals(TRIP_INFORMATION_OPTION.getText())
                || userResponse.equals(TRIP_INFORMATION_OPTION.getPostbackData())) {
            // client chose to view their rail ticket
            sendRailTicket();

            // send follow up options
            sendEngagementPrompt();
        }
        else if(userResponse.equals(MENU_OPTION.getText())
                || userResponse.equals(MENU_OPTION.getPostbackData())) {
            // client chose to view the food menu
            sendFoodMenu();
        }
        else if(userResponse.contains("order-")) {
            sendFoodOrderFollowup();
        }
        else if(userResponse.equals(SEATING_CHART_OPTION.getText())
                || userResponse.equals(SEATING_CHART_OPTION.getPostbackData())) {
            // client chose to view the seating chart
            sendSeatingChart();

            // send follow up options
            sendEngagementPrompt();
        }
        else if(userResponse.equals(CHANGE_SEAT_OPTION.getText())
                || userResponse.equals(CHANGE_SEAT_OPTION.getPostbackData())) {
            // client chose to change their seat
            sendSeatingChangeOptions();
        }
        else if(userResponse.equals(OPEN_SEAT_14.getPostbackData())
                || userResponse.equals(OPEN_SEAT_15.getPostbackData())
                || userResponse.equals(OPEN_SEAT_21.getPostbackData())) {
            // client chose to change their seat
            sendSeatChangedNotification(userResponse);

            // send the default follow up options
            sendDefaultFollowUp();
        }
        else if(userResponse.equals(MAP_OPTION.getPostbackData())
                || userResponse.equals(CALENDAR_OPTION.getPostbackData())
                || userResponse.equals(TALK_TO_AGENT_OPTION.getPostbackData())) {
            // client viewed the map or added the event to their calendar, do not send response
            logger.info(userResponse + " action by user");
        }
        else {
            // we are not sure what the client wants, so send an explanation
            sendBackupMessage();
        }
    }

    /**
     * Creates the default set of suggested replies for a client.
     * @return A list of suggested replies.
     */
    private List<Suggestion> getDefaultSuggestionList() {
        List<Suggestion> suggestions = new ArrayList<Suggestion>();

        suggestions.add(TRIP_INFORMATION_OPTION.getSuggestedReply());
        suggestions.add(MENU_OPTION.getSuggestedReply());
        suggestions.add(SEATING_CHART_OPTION.getSuggestedReply());

        return suggestions;
    }

    /**
     * General message sent to help keep the conversation flowing.
     */
    private void sendEngagementPrompt() {
        Random random = new Random();
        int coinFlip = random.nextInt(2);

        // flip coin for follow-up message
        if(coinFlip == 0) {
            // send the original set of suggested replies
            sendDefaultFollowUp();
        }
        else {
            // send the client the map and calendar options
            sendMapAndCalendar();
        }
    }

    /**
     * Sends the default follow up message to the client. This is consists of a suggested
     * chip list of viewing trip information, menu options and seating chart.
     */
    private void sendDefaultFollowUp() {
        try {
            // send the original set of suggested replies
            List<Suggestion> suggestedActions = getDefaultSuggestionList();

            rbmApiHelper.sendTextMessage(
                    "Is there anything else I can help with?",
                    this.msisdn,
                    suggestedActions
            );
        } catch(IOException e) {
            logger.log(Level.SEVERE, EXCEPTION_WAS_THROWN, e);
        }
    }

    /**
     * Sends the client a message to let them know that we did not understand what they wanted.
     */
    private void sendBackupMessage() {
        try {
            List<Suggestion> suggestedActions = getDefaultSuggestionList();

            rbmApiHelper.sendTextMessage(
                    "I am sorry, but I did not understand your request. " +
                            "How may I assist you?",
                    this.msisdn,
                    suggestedActions
            );
        } catch(IOException e) {
            logger.log(Level.SEVERE, EXCEPTION_WAS_THROWN, e);
        }
    }

    /**
     * Sends a standalone card to the client with their ticket shown as media.
     */
    private void sendRailTicket() {
        try {
            rbmApiHelper.sendTextMessage(
                    "Here is your ticket for your upcoming trip.",
                    this.msisdn
            );

            logger.info("Creating standalone card for rail ticket");

            // create a standalone card for the rail ticket
            StandaloneCard standaloneCard = rbmApiHelper.createStandaloneCard(
                    null,
                    null,
                    RAIL_TICKET_URL,
                    MediaHeight.TALL,
                    CardOrientation.VERTICAL,
                    null
            );

            rbmApiHelper.sendStandaloneCard(standaloneCard, msisdn);
        } catch(IOException e) {
            logger.log(Level.SEVERE, EXCEPTION_WAS_THROWN, e);
        }
    }

    /**
     * Informs the client that they have changed their seat location.
     * @param seatChoice The open seat that they picked.
     */
    private void sendSeatChangedNotification(String seatChoice) {
        String seatAsString = "";

        // check to see what seat was chosen by the user
        if(seatChoice.equals(OPEN_SEAT_14.getPostbackData())) {
            seatAsString = "seat 14";
        }
        else if(seatChoice.equals(OPEN_SEAT_15.getPostbackData())) {
            seatAsString = "seat 15";
        }
        else if(seatChoice.equals(OPEN_SEAT_21.getPostbackData())) {
            seatAsString = "seat 21";
        }

        // combine the seat choice with the response text
        String responseText = "Your seat has been changed to " + seatAsString + ".";

        try {
            rbmApiHelper.sendTextMessage(responseText, msisdn);
        } catch(IOException e) {
            logger.log(Level.SEVERE, EXCEPTION_WAS_THROWN, e);
        }
    }

    /**
     * Sends the client a list of open seats that they can choose from.
     */
    private void sendSeatingChangeOptions() {
        // combine the open seat options into suggestions
        List<Suggestion> suggestions = new ArrayList<Suggestion>();

        suggestions.add(OPEN_SEAT_14.getSuggestedReply());
        suggestions.add(OPEN_SEAT_15.getSuggestedReply());
        suggestions.add(OPEN_SEAT_21.getSuggestedReply());

        // create a standalone card for the rail ticket
        StandaloneCard standaloneCard = rbmApiHelper.createStandaloneCard(
                "Choose an open seat from below:",
                null,
                SEATING_CHART_URL,
                MediaHeight.TALL,
                CardOrientation.VERTICAL,
                suggestions
        );

        try {
            rbmApiHelper.sendStandaloneCard(standaloneCard, msisdn);
        } catch(IOException e) {
            logger.log(Level.SEVERE, EXCEPTION_WAS_THROWN, e);
        }
    }

    /**
     * Sends a standalone card to the client with their seating chart shown as media.
     */
    private void sendSeatingChart() {
        logger.info("Creating standalone card for rail ticket");

        // list of suggestions to attach to the card
        List<Suggestion> suggestions = new ArrayList<Suggestion>();

        // creating a change seat suggested reply
        SuggestedReply reply = new SuggestedReply();
        reply.setText(CHANGE_SEAT_OPTION.getText());
        reply.setPostbackData(CHANGE_SEAT_OPTION.getPostbackData());

        // attaching reply to a suggestion
        Suggestion suggestion = new Suggestion();
        suggestion.setReply(reply);

        // adding suggesting the list of suggestions
        suggestions.add(suggestion);

        // creating a dial an agent suggested action
        DialAction dialAction = new DialAction();
        dialAction.setPhoneNumber("+12223334444");

        // creating a suggested action based on a dial action
        SuggestedAction suggestedAction = new SuggestedAction();
        suggestedAction.setText(TALK_TO_AGENT_OPTION.getText());
        suggestedAction.setPostbackData(TALK_TO_AGENT_OPTION.getPostbackData());
        suggestedAction.setDialAction(dialAction);

        // attaching action to a suggestion
        suggestion = new Suggestion();
        suggestion.setAction(suggestedAction);

        // adding suggesting the list of suggestions
        suggestions.add(suggestion);

        // create a standalone card for the rail ticket
        StandaloneCard standaloneCard = rbmApiHelper.createStandaloneCard(
                null,
                null,
                SEATING_CHART_URL,
                MediaHeight.TALL,
                CardOrientation.VERTICAL,
                suggestions
        );

        try {
            rbmApiHelper.sendStandaloneCard(standaloneCard, msisdn);
        } catch(IOException e) {
            logger.log(Level.SEVERE, EXCEPTION_WAS_THROWN, e);
        }
    }

    /**
     * Sends a confirmation that the order was received and follows up with the main menu.
     */
    private void sendFoodOrderFollowup() {
        String response = "Thank you for your order. Is there anything else I can help you with?";

        try {
            // send the original set of suggested replies
            List<Suggestion> suggestedActions = new ArrayList<Suggestion>();

            suggestedActions.add(TRIP_INFORMATION_OPTION.getSuggestedReply());
            suggestedActions.add(SEATING_CHART_OPTION.getSuggestedReply());

            rbmApiHelper.sendTextMessage(
                    response,
                    this.msisdn,
                    suggestedActions
            );
        } catch(IOException e) {
            logger.log(Level.SEVERE, EXCEPTION_WAS_THROWN, e);
        }
    }

    /**
     * Sends a carousel card to the client with the food menu options.
     */
    private void sendFoodMenu() {
        // list of card content for the menu carousel
        List<CardContent> cardContents = new ArrayList<CardContent>();

        // add order items as card content
        cardContents.add(HAM_AND_CHEESE_CARD.getCardContent());
        cardContents.add(CHICKEN_VEGGIE_WRAP_CARD.getCardContent());
        cardContents.add(CHEESE_PLATE_CARD.getCardContent());
        cardContents.add(APPLE_WALNUT_CARD.getCardContent());

        try {
            // send the message to the user
            rbmApiHelper.sendCarouselCards(cardContents, CardWidth.MEDIUM, msisdn);
        } catch(IOException e) {
            logger.log(Level.SEVERE, EXCEPTION_WAS_THROWN, e);
        }
    }

    /**
     * Creates actions for viewing a map of the trip and saving trip details to a calendar.
     */
    private void sendMapAndCalendar() {
        // create a list of suggestions for the calendar and map actions
        List<Suggestion> suggestedActions = new ArrayList<Suggestion>();

        // create a calendar object to create a sample start date and time for the calendar action
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.MONTH, 1);
        cal.set(Calendar.HOUR_OF_DAY, 9);
        cal.set(Calendar.MINUTE, 0);

        // initialize a start date for this fake start date and time
        Date startDate = cal.getTime();

        // add travel time to the existing calendar object
        cal.add(Calendar.HOUR, 14);
        cal.add(Calendar.MINUTE, 2);

        // initialize an end date for this fake arrival date and time
        Date endDate = cal.getTime();

        // needed for formatting the date/time for the API
        SimpleDateFormat format = new SimpleDateFormat(
                "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("UTC"));

        String startTime = format.format(startDate);
        String endTime = format.format(endDate);

        // create the calendar event action for the trip
        CreateCalendarEventAction calendarEventAction = new CreateCalendarEventAction();
        calendarEventAction.setTitle("Trip from London to Paris");
        calendarEventAction.setDescription("Upcoming trip on Bonjour Rail from London to Paris");
        calendarEventAction.setStartTime(startTime);
        calendarEventAction.setEndTime(endTime);

        // attach the calendar action to a suggested action
        SuggestedAction suggestedAction = new SuggestedAction();
        suggestedAction.setCreateCalendarEventAction(calendarEventAction);
        suggestedAction.setText(CALENDAR_OPTION.getText());
        suggestedAction.setPostbackData(CALENDAR_OPTION.getPostbackData());

        // attach the action to a suggestion object
        Suggestion suggestion = new Suggestion();
        suggestion.setAction(suggestedAction);

        // add the suggestion to the list of suggestions
        suggestedActions.add(suggestion);

        // create an open url action that will show a map of the trip
        OpenUrlAction openUrlAction = new OpenUrlAction();
        openUrlAction.setUrl("https://goo.gl/maps/ToMSdr4PYX62");

        // attach the open url action to a suggested action
        suggestedAction = new SuggestedAction();
        suggestedAction.setOpenUrlAction(openUrlAction);
        suggestedAction.setText(MAP_OPTION.getText());
        suggestedAction.setPostbackData(MAP_OPTION.getPostbackData());

        // attach the action to a suggestion object
        suggestion = new Suggestion();
        suggestion.setAction(suggestedAction);

        // add the suggestion to the list of suggestions
        suggestedActions.add(suggestion);

        try {
            // send the message with suggestion actions to the client
            rbmApiHelper.sendTextMessage(
                    "Is there anything else I can help you with?",
                    this.msisdn,
                    suggestedActions
            );
        } catch(IOException e) {
            logger.log(Level.SEVERE, EXCEPTION_WAS_THROWN, e);
        }
    }
}
// [END of the chat bot for Bonjour Rail]