import { Plugin, BaseApp, SpeechBuilder,  Log} from '../../jovo-core';
import { Config } from '../../jovo-platform-dialogflow/dist/src/DialogflowCore';
import { Dialogflow , DialogflowConfig} from '../../jovo-platform-dialogflow';
import { DialogflowAgent} from '../../jovo-platform-dialogflow/dist/src/DialogflowAgent';
import { DialogflowRequest } from  '../../jovo-platform-dialogflow/dist/src/core/DialogflowRequest';
import { DialogflowResponse } from  '../../jovo-platform-dialogflow/dist/src/core/DialogflowResponse';
import { EntityOverrideMode, SessionEntity, SessionEntityType } from '../../jovo-platform-dialogflow';
import _get = require('lodash.get');
import _set = require('lodash.set');
const _sample = require('lodash.sample');
import _unionWith = require('lodash.unionwith');
import { TelephonyActions } from './Interfaces';
import { AltervoiceAsteriskCallbotUser } from './altervoice-asterisk-callbot-user';

type reprompt = string | SpeechBuilder;
//const idPlatform = 'telephony';
const idPlatform = 'phone_gateway';
const idSource = 'phone_gateway';

declare module '../../jovo-platform-dialogflow/dist/src/DialogflowAgent' {
 export interface DialogflowAgent {
  isCallbot(): boolean;
	addActions(action : TelephonyActions) : void;
	getDtmf(): string;
	getResult(): any;
  addOutputContext(name: string, parameters: { [key: string]: any }, lifespanCount:number): void;
  getOutputContext(name: string): void;
  addSessionEntityTypes(sessionEntityTypes: SessionEntityType[] ) : this;
  addSessionEntityType(sessionEntityTypes: SessionEntityType ) : this;
  addSessionEntity(name: string,value: string,synonyms: string[], entityOverrideMode?: EntityOverrideMode): this;
  actions : (TelephonyActions| any)[];
  $request : DialogflowRequest;
  }
}

export * from '../../jovo-platform-dialogflow';

export class AltervoiceAsteriskCallbot implements Plugin {
	config = {
		enabled: true,
	};


    constructor(config?: Config) {  }

    install(dialogFlow: Dialogflow): void {
        dialogFlow.middleware('$output')!.use(this.output.bind(this));
        dialogFlow.middleware('$type')!.use(this.type.bind(this));

		DialogflowAgent.prototype.isCallbot = function () {
			return _get(this.$request, 'originalDetectIntentRequest.payload.source') === idSource;
		};

		DialogflowAgent.prototype.addActions = function (action : TelephonyActions) {
			action.platform = idPlatform;
      if(action.telephonyMaxCall && action.telephonyMaxCall.prompt)
        action.telephonyMaxCall.prompt = action.telephonyMaxCall.prompt.toString();
      if(action.telephonyInact && action.telephonyInact.prompt)
        action.telephonyInact.prompt = action.telephonyInact.prompt.toString();
			if(!this.actions)
				this.actions = [];
      this.actions!.push(action);
		};

		DialogflowAgent.prototype.ask = function(
			speech: string | SpeechBuilder | string[],
			reprompt: string | SpeechBuilder | string[],
			...reprompts: reprompt[]
			) {
			delete this.$output.tell;

			if (Array.isArray(speech)) {
				speech = _sample(speech);
			}

			if (Array.isArray(reprompt)) {
				reprompt = _sample(reprompt);
			}

			if (!reprompt) {
				reprompt = [];
			}

			this.$output.ask = {
				speech: speech.toString(),
				reprompt: [reprompt.toString()],
			};

			if (reprompts) {
				this.$output.ask.reprompt = [reprompt.toString()];
				reprompts.forEach((repr: string | SpeechBuilder) => {
				(this.$output.ask!.reprompt as string[]).push(repr.toString());
				});
			}
			return this;
		}

		DialogflowAgent.prototype.getDtmf = function () {
      let reg =  /\/telephony_dtmf$/g;
      let outputContext = _get(this.$request, 'queryResult.outputContexts');
      let data = outputContext ? outputContext.find((x :any) => reg.exec(x.name)) : {};
      return data.parameters;
		};

		DialogflowAgent.prototype.getResult = function () {
      let reg =  /\/telephony_result$/g;
      let outputContext = _get(this.$request, 'queryResult.outputContexts');
      let data = outputContext ? outputContext.find((x :any) => reg.exec(x.name)) : {};
      return data.parameters;
	  };

    DialogflowAgent.prototype.addOutputContext = function(name: string, parameters: { [key: string]: any }, lifespanCount = 1) {
      let output = _get(this.$output, "Dialogflow.OutputContexts") || [];
      output.push({name,parameters,lifespanCount});
      _set(this.$output, 'Dialogflow.OutputContexts',output);
    }

    DialogflowAgent.prototype.getOutputContext = function(name: string) {
      return _get(this.$request, 'queryResult.outputContexts', []).find((context: any) => {
        return context.name.indexOf(`/contexts/${name}`) > -1;
      });
    }

    DialogflowAgent.prototype.addSessionEntityTypes = function ( sessionEntityTypes: SessionEntityType[]  ) {
      if (!  _get(this.$output,'Dialogflow.SessionEntityTypes')) {
        _set(this.$output, 'Dialogflow.SessionEntityTypes',[]);
      }

      sessionEntityTypes.forEach((el: SessionEntityType) => {
        // Place session id in front of session entity name to accomodate to proper format
        const sessionId = this.$request!.getSessionId();
        const entityName = el.name;
        el.name = `${sessionId}/entityTypes/${entityName}`;

        // Set default override mode
        if (!el.entityOverrideMode) {
          el.entityOverrideMode = 'ENTITY_OVERRIDE_MODE_SUPPLEMENT';
        }
      });

      // Merge existing entities with new ones provided as arguments with _.unionWith.
        _set(this.$output, 'Dialogflow.SessionEntityTypes', _unionWith(
          _get( this.$output, 'Dialogflow.SessionEntityTypes'),
        sessionEntityTypes,
        (newEntry: SessionEntityType, original: SessionEntityType) => {
          // If the new session entity does not yet exist by its name, just add it to the new array.
          if (newEntry.name !== original.name) {
            return false;
          }

          // If the session entity already exists by its name, check if its entity values already exist.
          const entities = _unionWith(
            newEntry.entities,
            original.entities,
            (n: SessionEntity, o: SessionEntity) => {
              // If the current value is not yet present, just add it with a new entry.
              if (n.value !== o.value) {
                return false;
              }

              // Else merge the respective synonyms and unify them.
              o.synonyms = _unionWith(o.synonyms, n.synonyms);
              return true;
            }
          );

          // Replace old entries with new, merged ones.
          original.entities = entities;
          return true;
        })
      );
      return this;
    };

    DialogflowAgent.prototype.addSessionEntityType = function (sessionEntityType: SessionEntityType) {
      return this.addSessionEntityTypes([sessionEntityType]);
    };

  }

    uninstall(app: BaseApp) : void { }


	type(dialogflowAgent: DialogflowAgent) {
		if (dialogflowAgent.isCallbot()) {
			dialogflowAgent.$user = new AltervoiceAsteriskCallbotUser(dialogflowAgent);
      if (!dialogflowAgent.$response)
          dialogflowAgent.$response = new DialogflowResponse();
		}
	}


  output(dialogflowAgent: DialogflowAgent) : void  {
		if (dialogflowAgent.isCallbot()) {
            const output = dialogflowAgent.$output;
            if (!dialogflowAgent.$response) {
                dialogflowAgent.$response = new DialogflowResponse();
            }
      const response = dialogflowAgent.$response as DialogflowResponse;

	    if(dialogflowAgent.actions === undefined)
        dialogflowAgent.actions = [];

      if( dialogflowAgent.$output.Dialogflow){
        response.sessionEntityTypes = _get(dialogflowAgent.$output.Dialogflow, 'SessionEntityTypes') ;
      }

  		if( dialogflowAgent.$output.ask ){
//        _set(response, 'fulfillmentText', dialogflowAgent.$output.ask.speech);
				dialogflowAgent.actions.push({ simpleResponse:{ssml: dialogflowAgent.$output.ask.speech}} );
				if((dialogflowAgent.$output.ask!.reprompt as string[]).length)
					_set(response, 'payload.'+idPlatform+'.noInputPrompts',  (dialogflowAgent.$output.ask!.reprompt  as string[]).map((x: string | SpeechBuilder)  => {return {ssml: x};} ));
				_set(response, 'payload.'+idPlatform+'.expectUserResponse',true);
        response.end_interaction = false;
//	      delete dialogflowAgent.$output.ask;

			}

			else if( dialogflowAgent.$output.tell ){
//        _set(response, 'fulfillmentText', dialogflowAgent.$output.tell.speech);
				dialogflowAgent.actions.push({simpleResponse:{ssml: dialogflowAgent.$output.tell.speech}} );
				_set(response, 'payload.'+idPlatform+'.expectUserResponse',false);
        response.end_interaction = true;
//        delete dialogflowAgent.$output.tell;

			}

      if (response.fulfillmentText && SpeechBuilder.isSSML(response.fulfillmentText)) {
          const ssml = response.fulfillmentText;
          if (!response.fulfillmentMessages) {
              response.fulfillmentMessages = [];
          }
          response.fulfillmentMessages.push({
              platform: idSource,
              telephonySynthesizeSpeech: {
                  ssml,
              },
          });
          response.fulfillmentText = SpeechBuilder.removeSSML(response.fulfillmentText);
      }
      else {
          Log.debug('Response does not contain SSML');
      }

			if(dialogflowAgent.actions){
				_set(response, 'payload.'+idPlatform+'.richResponse.items',dialogflowAgent.actions );
				dialogflowAgent.actions = [];
			}
/*
      let user = _get(dialogflowAgent.$request, 'originalDetectIntentRequest.payload.user');
      console.log('user=',user);
	     _set(response, 'payload.'+idPlatform+'.user.',user);
*/
//      _set(response, 'userStorage.user', user);

//      console.log("response", Object.keys(response));

		}
  }
}
