/* ----------------------------------------------------------------------------
Question Server
 GitLab: <https://gitlab.com/MrFry/question-node-server>

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program. If not, see <https://www.gnu.org/licenses/>.

 ------------------------------------------------------------------------- */

module.exports = {
  ProcessIncomingRequest: ProcessIncomingRequest,
  CheckData: CheckData,
  NLoad: NLoad,
  LoadJSON: LoadJSON,
  ProcessQA: ProcessQA
}

const staticFile = './public/data/static'
const dataFile = './public/data.json'
const recDataFile = './stats/recdata'
const versionFile = './public/version'
const motdFile = './public/motd'
const qaFile = './public/qa'

var logger = require('../utils/logger.js')
var utils = require('../utils/utils.js')

class Question {
  constructor (q, a, i) {
    this.Q = q
    this.A = a
    this.I = i
  }
  toString () {
    var r = '?' + this.Q + '\n!' + this.A
    if (this.I) { r += '\n>' + this.I }
    return r
  }
  HasQuestion () {
    return this.Q !== undefined
  }
  HasAnswer () {
    return this.A !== undefined
  }
  HasImage () {
    return this.I !== undefined
  }
  IsComplete () {
    return this.HasQuestion() && this.HasAnswer()
  }
  Compare (q2, i) {
    if (typeof q2 === 'string') {
      var qmatchpercent = Question.CompareString(this.Q, q2)

      if (i == undefined || i.length == 0) { return qmatchpercent } else {
        if (this.HasImage()) {
          const imatchpercent = this.HasImage() ? Question.CompareString(this.I.join(' '), i.join(' '))
            : 0
          return (qmatchpercent + imatchpercent) / 2
        } else {
          qmatchpercent -= 30
          if (qmatchpercent < 0) { return 0 } else { return qmatchpercent }
        }
      }
    } else {
      const qmatchpercent = Question.CompareString(this.Q, q2.Q)
      const amatchpercent = Question.CompareString(this.A, q2.A)
      if (this.I !== undefined) {
        const imatchpercent = this.I === undefined ? Question.CompareString(this.I.join(' '), q2.I.join(
          ' ')) : 0
        return (qmatchpercent + amatchpercent + imatchpercent) / 3
      } else {
        return (qmatchpercent + amatchpercent) / 2
      }
    }
  }

  // static CompareString (s1, s2) {
  //   // if (s1 == undefined || s2 == undefined)
  //   //   return 0;
  //   s1 = SimplifyStringForComparison(s1).split(' ')
  //   s2 = SimplifyStringForComparison(s2).split(' ')
  //   var match = 0
  //   for (var i = 0; i < s1.length; i++) {
  //     if (s2.includes(s1[i])) { match++ }
  //   }
  //   var percent = Math.round(((match / s1.length) * 100).toFixed(2)) // matched words percent
  //   var lengthDifference = Math.abs(s2.length - s1.length)
  //   percent -= lengthDifference * 3
  //   if (percent < 0) { percent = 0 }
  //   return percent
  // }
}

class Subject {
  constructor (n) {
    this.Name = n
    this.Questions = []
  }
  get length () {
    return this.Questions.length
  }
  AddQuestion (q) {
    this.Questions.push(q)
  }
  Search (q, img) {
    const minMatchAmmount = 90
    var r = []
    for (let i = 0; i < this.length; i++) {
      let percent = this.Questions[i].Compare(q, img)
      if (percent > minMatchAmmount) {
        r.push({
          q: this.Questions[i],
          match: percent
        })
      }
    }

    for (let i = 0; i < r.length; i++) {
      for (var j = i; j < r.length; j++) {
        if (r[i].match < r[j].match) {
          var tmp = r[i]
          r[i] = r[j]
          r[j] = tmp
        }
      }
    }

    return r
  }
  toString () {
    var r = []
    for (var i = 0; i < this.Questions.length; i++) { r.push(this.Questions[i].toString()) }
    return '+' + this.Name + '\n' + r.join('\n')
  }
}

class QuestionDB {
  constructor () {
    this.Subjects = []
  }
  get length () {
    return this.Subjects.length
  }
  // get activeIndexes () {
  //   var r = []
  //   for (var i = 0; i < this.length; i++) {
  //     if (GM_getValue('Is' + i + 'Active')) {
  //       r.push(i)
  //     }
  //   }
  //   return r
  // }
  // GetIfActive (ind) {
  //   return GM_getValue('Is' + ind + 'Active')
  // }
  // ChangeActive (i, value) {
  //   GM_setValue('Is' + i + 'Active', !!value)
  // }
  AddQuestion (subj, q) {
    var i = 0
    while (i < this.Subjects.length && this.Subjects[i].Name !== subj) { i++ }
    if (i < this.Subjects.length) { this.Subjects[i].AddQuestion(q) } else {
      const n = new Subject(subj)
      n.AddQuestion(q)
      this.Subjects.push(n)
    }
  }
  Search (q, img) {
    var r = []
    for (let i = 0; i < this.length; i++) {
      if (this.GetIfActive(i)) { r = r.concat(this.Subjects[i].Search(q, img)) }
    }

    for (let i = 0; i < r.length; i++) {
      for (var j = i; j < r.length; j++) {
        if (r[i].match < r[j].match) {
          var tmp = r[i]
          r[i] = r[j]
          r[j] = tmp
        }
      }
    }

    return r
  }
  AddSubject (subj) {
    var i = 0
    while (i < this.length && subj.Name !== this.Subjects[i].Name) { i++ }

    if (i < this.length) {
      this.Subjects.concat(subj.Questions)
    } else {
      this.Subjects.push(subj)
    }
  }
  toString () {
    var r = []
    for (var i = 0; i < this.Subjects.length; i++) { r.push(this.Subjects[i].toString()) }
    return r.join('\n\n')
  }
}

function Process (d, file) {
  try {
    logger.Log('File: ' + file)
    if (d.data.split('\n').length > 1) {
      var oldFile = utils.ReadFile(file)
      var newFile = oldFile + '\n'
      if (d.data[0] === '+') { newFile += d.data } else { newFile += '+' + d.data }

      var newRes = CheckData(newFile)
      var oldRes = CheckData(oldFile)

      if (oldRes.count > 0) { logger.Log('\t\told public result: ' + oldRes.count, logger.GetColor('blue')) } else { logger.Log('\t\told public NLOD error, ' + oldRes.log, logger.GetColor('redbg'), true) }

      if (newRes.count > 0) { logger.Log('\t\tnew file result: ' + newRes.count, logger.GetColor('blue')) } else { logger.Log('\t\tnew file NLOD error, ' + newRes.log, logger.GetColor('redbg'), true) }

      utils.WriteFile(newFile, file)
      logger.Log('\t\tNew data written to: ' + file)

      return newRes.count - oldRes.count
    } else { logger.Log('\t\tNo new data') }
  } catch (e) {
    logger.Log('\tError at processing data! File: ' + file, logger.GetColor('redbg'))
    logger.Log(e.toString(), logger.GetColor('redbg'))
  }
  return -1
}

function ProcessIncomingRequest (data) {
  if (data === undefined) {
    logger.Log('\tRecieved data is undefined!', logger.GetColor('redbg'))
    return
  }

  try {
    let towrite = logger.GetDateString() + '\n'
    towrite += '------------------------------------------------------------------------------\n'
    towrite += data
    towrite += '\n------------------------------------------------------------------------------\n'
    utils.AppendToFile(towrite, recDataFile)
  } catch (e) {
    logger.log('Error writing recieved data.')
  }

  try {
    var d = JSON.parse(data)
    var dfile = utils.ReadFile(dataFile)
    data = LoadJSON(dfile)
    var allQuestions = []
    for (let i = 0; i < d.allData.length; i++) {
      allQuestions.push(new Question(d.allData[i].Q, d.allData[i].A, d.allData[i].I))
    }
    var questions = []
    for (let i = 0; i < d.data.length; i++) {
      let q = new Question(d.data[i].Q, d.data[i].A, d.data[i].I)
      questions.push(q)
      data.AddQuestion(d.subj, q)
    }

    logger.Log('\t' + d.subj)
    var msg = 'All / new count: ' + allQuestions.length + ' / ' + questions.length
    if (d.version !== undefined) { msg += '. Version: ' + d.version }
    var color = logger.GetColor('green')

    try {
      data.version = utils.ReadFile(versionFile)
      data.motd = utils.ReadFile(motdFile)
    } catch (e) {
      logger.Log('MOTD/Version writing/reading error!')
    }

    if (data !== undefined && d.data.length > 0) {
      utils.WriteBackup()
      utils.WriteFile(JSON.stringify(data), dataFile)
      msg += ' - Data file written!'
      color = logger.GetColor('blue')
    }
    logger.Log('\t' + msg, color)
  } catch (e) {
    logger.Log('Couldnt parse JSON data, trying old format...', logger.GetColor('redbg'))
    d = SetupData(data)
    var qcount = -1
    try {
      var splitted = d.alldata.split('\n')
      var count = 0
      for (var i = 0; i < splitted.length; i++) {
        if (splitted[i][0] === '?') { count++ }
      }
      qcount = count
    } catch (e) { console.log('Error :c'); console.log(e) }

    logger.Log('\tProcessing data: ' + d.subj + ' (' + d.type + '), count: ' + qcount, logger.GetColor('green'))
    if (d.subj === undefined) {
      logger.Log(JSON.stringify(d), logger.GetColor('red'))
      return
    }

    var newStatItems = Process(d, staticFile)

    PrintNewCount(d, newStatItems, staticFile)
  }
}

function PrintNewCount (d, newItems, file) {
  if (newItems > 0) {
    var count = 0
    var splitted = d.alldata.split('\n')
    for (var i = 0; i < splitted.length; i++) {
      if (splitted[i].startsWith('?')) { count++ }
    }
    logger.Log('\t' + file + ' All / New: ' + count + ' / ' + newItems, logger.GetColor('cyan'))
  }
}

function SetupData (data) {
  var pdata = data.split('<#>')
  if (pdata.length <= 0) {
    logger.Log('Data length is zero !', logger.GetColor('redbg'))
    throw 'No data recieved!'
  }

  var d = {} // parsed data
  for (var i = 0; i < pdata.length; i++) {
    var td = pdata[i].split('<=>')
    if (td.length === 2) { d[td[0]] = td[1] } else {
      logger.Log('Invalid parameter!', logger.GetColor('redbg'))
      throw 'Invalid parameter recieved!'
    }
  }
  return d
}

function CheckData (data) {
  try {
    var presult = NLoad(data)
    return presult
  } catch (e) {
    logger.Log('Load error, ' + e.toString(), logger.GetColor('redbg'), true)
    return {
      count: -1,
      log: [e.toString()]
    }
  }
}

function NLoad (resource) {
  var resultLog = []
  var allCount = 0
  if (resource === undefined) { throw 'A megadott adat undefined!' }
  resource = resource.split('\n') // splitting by enters
  if (resource.length === 1) { throw 'A megadott adat nem sorokból áll!' }
  var data = [] // initializing data declared at the begining

  function AddNewSubj (name) {
    data.push({
      'questions': []
    }) // ads aa new object, with an empty array in it
    GetCurrSubj().name = name // sets the name for it
    GetCurrSubj().count = 0 // setting count to default
    // setting if its active only if its undefined, otherwise previous user setting shouldt be overwritten
  }

  function AddItem () // adds an item to the last subjects questions
  {
    GetCurrSubj().count++
    allCount++ // incrementing all count
    GetCurrSubj().questions.push({}) // adding a new empty object to the last item in the data
  }

  function GetLastItem () // returns the last item of the last item of data
  {
    var q = GetCurrSubj().questions.length // questions length
    return GetCurrSubj().questions[q - 1]
  }

  function GetCurrSubj () {
    return data[data.length - 1]
  }

  // ? : question
  // ! : answer
  // > : image JSON data
  // + : subject name

  // checking for name
  for (var j = 0; j < resource.length; j++) // goes through resources
  {
    if (resource[j][0] == '+') // if there is a name identifier
    {
      break // breaks, couse there will be a name
    }
    if (resource[j][0] == '?' || resource[j][0] == '!' || resource[j][0] == '>') // if it begins with another identifier:
    {
      AddNewSubj('NONAME') // there is no name (for the first question at least), so setting it noname.
      break
    }
    // else it does nothing, continues to check
  }
  var jumped = 0 // the amount of lines it processed
  for (var i = 0; i < resource.length; i += jumped) // gouing through the resource
  {
    jumped = 0 // resetting it to 0
    var currRawDataQ = resource[i] // current question
    var currRawDataA = resource[i + 1] // current answer
    var currRawDataI = resource[i + 2] // current image
    if (currRawDataQ != undefined && currRawDataQ[0] == '?' && currRawDataA != undefined &&
			currRawDataA[0] == '!') // if the current line is ? and the next is ! its a data
    {
      AddItem()
      GetLastItem().q = currRawDataQ.substr(1)
      GetLastItem().a = currRawDataA.substr(1)
      jumped += 2
      if (currRawDataI != undefined && currRawDataI[0] == '>') {
        GetLastItem().i = currRawDataI.substr(1)
        jumped++
      }
    } else if (currRawDataQ[0] == '+') // if its a new subject
    {
      AddNewSubj(currRawDataQ.substr(1))
      jumped++
    } else {
      // this should be invalid question order
      resultLog.push('Warning @ line ' + i + ':' + currRawDataQ + ' ' + currRawDataA + ' ' +
				currRawDataI)
      jumped++
    }
  } // end of parsing all data
  return {
    count: allCount,
    log: resultLog
  }
}

// loading stuff
function LoadJSON (resource) {
  var count = -1
  try {
    var	d = JSON.parse(resource)
    var r = new QuestionDB()
    var rt = []
    var allCount = -1

    for (var i = 0; i < d.Subjects.length; i++) {
      let s = new Subject(d.Subjects[i].Name)
      var j = 0
      for (j = 0; j < d.Subjects[i].Questions.length; j++) {
        var currQ = d.Subjects[i].Questions[j]
        s.AddQuestion(new Question(currQ.Q, currQ.A, currQ.I))
      }
      rt.push({
        name: d.Subjects[i].Name,
        count: j
      })
      allCount += j
      r.AddSubject(s)
    }
    return r
  } catch (e) {
    logger.Log('Error loading sutff', logger.GetColor('redbg'), true)
  }
}

function ProcessQA () {
  if (!utils.FileExists(qaFile)) { utils.WriteFile('', qaFile) }

  let a = utils.ReadFile(qaFile).split('\n')
  let r = []
  let ind = 0
  for (let i = 0; i < a.length; i++) {
    if (a[i] == '#') { ind++ } else {
      if (r[ind] == undefined) { r[ind] = {} }

      if (r[ind].q == undefined) {
        r[ind].q = a[i]
      } else {
        if (r[ind].a == undefined) { r[ind].a = [] }

        r[ind].a.push(a[i])
      }
    }
  }

  return r
}