models/stepModel.js

const BaseModel = require('./baseModel.js')
const ReasoningService = require('../services/reasoning.js')

const N3 = require('n3')
const { DataFactory } = N3
const { namedNode, literal, defaultGraph, quad } = DataFactory

// unlink temp files
const { unlink } = require('fs')
/**
* The stepModel contains code specifically for the step model
* Handles all steps related data
* @extends BaseModel
*/
class StepModel extends BaseModel {
  constructor (req) {
    super(req)
    this.extendedFolder = 'steps'
    this.fileName = 'data.ttl'
    this.inputPatternFile = 'reasoning/interim/input/input_patterns.n3'
    this.queryPatternFile = 'reasoning/interim/show/query-patterns.n3'
  }

  /**
    * Gets the input patterns as n3string.
    *
    * @async
    * @returns {string} n3string of all input patterns
    */
  async getAllInputPatternsAsString () {
    return this.fileService.fileToString(this.inputPatternFile)
  }

  /**
    * Gets all query patterns as n3string
    * @async
    * @returns {string} n3string of all query patterns
    */
  async getAllQueryPatternsAsString () {
    return this.fileService.fileToString(this.queryPatternFile)
  }

  /**
    * Get input pattern of the provided step
    * @async
    * @param {string} stepName - name of the step are interested in
    * @returns {Object|null} - object with the following properties:
    *
    *   - triples: n3string of the expected triples
    *   - replaceVar: n3string of the variables to replace
    *   - prefixes: all the prefixes for a step as a string
    *   - step: name of the step
    *
    * - returns 'null' if no match can be found for either the triples or if there are no variables to replace
    */
  async getInputPatternByStep (stepName) {
    // first get all input patterns as a string
    const inputPatterns = await this.getAllInputPatternsAsString()

    // get prefixes
    const prefixes = this.fileService.prefixesFromStringAsString(inputPatterns)
    // unused?:const prefixesJSON = this.fileService.prefixesFromString(inputPatterns);

    // create the expressions that will find our pattern and variable to replace
    const exprPattern = new RegExp(stepName + '.*{(.*)}', '')
    /* eslint-disable-next-line */
    const exprReplaceVar = new RegExp('.*' + stepName + '.*variablesToReplace.*\.', '')

    // get the pattern triples using the RegExp
    const matchedTriples = inputPatterns.match(exprPattern)
    if (matchedTriples === null) {
      return null
    }
    // Get captured group
    const patternTriples = matchedTriples[1]

    const matchedReplaceVar = inputPatterns.match(exprReplaceVar)
    if (matchedReplaceVar === null) {
      return null
    }
    const patternReplaceVar = matchedReplaceVar[0]
    // var patternReplaceVar = inputPatterns.match(exprReplaceVar)[0];

    // store everything as JSON object to return
    const patternFull = {
      triples: patternTriples,
      replaceVar: patternReplaceVar,
      prefixes: prefixes,
      step: stepName
    }
    // console.log("getInputPatternByStep:", patternFull);
    // beam me up Scotty
    return patternFull
  }

  /**
     * Transforms the provided steps into a temporary N3 file which gets fed to the reasoner.
     *
     * Which in turn returns the inferred N3 steps that we need to add to the users profile.
     * SHOULD NEVER BE USED WITH SOLID (only possible to change internal data)
     * @async
     * @param {Object.<String,String|String[]>} steps - Stepname - stepvalue pairs.
     *                                                  Stepvalue can be a string or an array of strings for steps that take multiple values
     * @returns {String|null} - N3 strings inferred from the steps or null if no steps were provided.
     *
     */
  async getTriplesFromSteps (steps) {
    if (Object.keys(steps).length === 0) {
      console.log('No steps provided')
      return null
    }

    const prefixes = {
      step: this.prefixes.step,
      // step : this.url + '/steps#',
      ' ': 'http://example.org#'
    }

    // loop the steps and create triples from the json request
    // e.g. step:provideTelephoneNumber :value "123456"
    const quads = []

    for (const step in steps) {
      // Always transform to array for simplicity
      const values = Array.isArray(steps[step]) ? steps[step] : [steps[step]]
      for (const val of values) {
        quads.push(quad(
          namedNode(prefixes.step + step),
          namedNode(prefixes[' '] + 'value'),
          literal(val),
          defaultGraph()))
      }
    }

    const tempFile = await this.fileService.quadsToTempFile(quads, prefixes)
    // convert the quad to a string to be used by reasoner (or write to file first if reasoner can't use string)
    const inference = {
      query: 'reasoning/external-input/CreateInputTriple.n3',
      data: [tempFile, 'reasoning/help-functions/aux2.n3', 'reasoning/profile/knowledge.n3', 'reasoning/profile/personalInfo.n3', 'reasoning/profile/profileInfo.n3', 'reasoning/interim/steps/component-level-steps.n3', 'reasoning/external-input/replaceValue.n3']
    }
    // Here comes the DAG invocation
    const output = await ReasoningService.eyePromise(inference)

    unlink(tempFile, (err) => {
      if (err) { console.error('Temporary file: ', tempFile, ' Could not be removed', err) } else { console.log('Deleted temporary file: ', tempFile) }
    })
    return output
  }
}

module.exports = StepModel

// Old code kept for tracking bugs
/*
    * Turns a JSON request for adding a value to specific steps into triples the reasoner can use,
    * the reasoner then generates all of the triples that are required for finishing the step
    */
/*
    getStepTriplesByJSON(body, next)
    {
        return next(Boom.badRequest('Refactored into getTriplesFromSteps'), null);
        // check if the request actually contains input, if not -> errorhandler
        if (!body.hasOwnProperty('input'))
            return next(Boom.badRequest('Invalid format for request'), null);

        // if no steps were provided then return null
        if (!Object.keys(body.input).length)
            return next(null, null);

        var prefixes = {
            step : this.prefixes.step,
            //step : this.url + '/steps#',
            ' ': 'http://example.org#'
        };

        // loop the steps and create triples from the json request
        // e.g. step:provideTelephoneNumber :value "123456"
        var quads = [];
        for(var step in body.input){
            const myQuad = quad(
                namedNode(prefixes.step + step),
                namedNode(prefixes[' '] + 'value'),
                literal(body.input[step]),
                defaultGraph(),
            );
            quads.push(myQuad);
        }

        // convert the quad to a string to be used by reasoner (or write to file first if reasoner can't use string)
        this.fileService.quadsToTempFile(quads, prefixes, function(error, file) {
            if (error) return next(error, null);

            var query = 'reasoning/external-input/CreateInputTriple.n3';

            var meta = {
                inference: {
                    query: query,
                    data: [file, 'reasoning/help-functions/aux2.n3', 'reasoning/profile/knowledge.n3', 'reasoning/profile/personalInfo.n3', 'reasoning/profile/profileInfo.n3', 'reasoning/interim/steps/component-level-steps.n3', 'reasoning/external-input/replaceValue.n3' ]
                }
            }

            //Here comes the DAG invocation
            Promise.resolve(ReasoningService.eyePromise(meta.inference))
            .then(function (body) {
                // TODO: convert turtle result to json-ld
                //console.log(body)
                next(null, body);
                //renderSupportedContentypes(context, targetContentType, body, res);
            })
            .catch(function (error) {
                //renderError(res, error);
                next(error, null);
            });
        });
    }
*/