serge-web/serge-web

View on GitHub
client/src/api/wargames_api/index.ts

Summary

Maintainability
D
1 day
Test Coverage
/* eslint-disable no-unused-vars */
import { ADJUDICATION_PHASE, allDbs, clearAll, COUNTER_MESSAGE, CUSTOM_MESSAGE, databasePath, FEEDBACK_MESSAGE, hiddenPrefix, INFO_MESSAGE, MSG_STORE, MSG_TYPE_STORE, PLANNING_PHASE, SERGE_INFO, serverPath, wargameSettings, dbDefaultSettings } from 'src/config'
import { deleteRoleAndParts, duplicateThisForce } from 'src/Helpers'
import _ from 'lodash'
import moment from 'moment'
import fetch, { Response } from 'node-fetch'
import uniqid from 'uniqid'
import deepCopy from '../../Helpers/copyStateHelper'
import * as messageTypesApi from '../../api/messageTypes_api'

import {
  setCurrentWargame, setLatestFeedbackMessage, setLatestWargameMessage
} from '../../ActionsAndReducers/playerUi/playerUi_ActionCreators'

import { ActivityLogsInterface, ChannelTypes, ForceData, GameTurnLength, Message, MessageChannel, MessageCustom, MessageDetailsFrom, MessageDetails, MessageFeedback, MessageInfoType, MessageStructure, ParticipantChat, ParticipantTypes, PlayerLogEntries, PlayerUiDispatch, Role, Wargame, WargameOverview, WargameRevision, TemplateData, MappingMessage, MappingMessageDelta, TypeOfCustomMessage } from 'src/custom-types'
import {
  ApiWargameDb, ApiWargameDbObject, ListenNewMessageType
} from './types.d'

import incrementGameTime from '../../Helpers/increment-game-time'
import DbProvider from '../db'

const wargameDbStore: ApiWargameDbObject[] = []

const rejectDefault = (err: string): any => {
  console.log(err)
  return err
}

// get db name from path
const getNameFromPath = (dbPath: string): string => {
  if (!dbPath) throw new Error('Wrong dbPath')
  const path: string = new URL(dbPath).pathname
  const index: number = path.lastIndexOf('/')
  if (index === -1) throw new Error('Wrong dbPath')
  return path.substring(index + 1)
}

// get database object by :name key
const getWargameDbByName = (name: string): ApiWargameDbObject => {
  name = name.replace(hiddenPrefix, '')
  const dbObject = wargameDbStore.find((item) => item.name === name)
  if (dbObject === undefined) throw new Error(`wargame database with '${name}' not found`)
  return dbObject
}

// get database object by database path
// const getWargameDbByDbPath = (dbPath: string): WargameDb => {
//   const name: string = getNameFromPath(dbPath)
//   return getWargameDbByName(name)
// }

// add a new wargame database
export const addWargameDbStore = (wargameDbObject: ApiWargameDbObject) => {
  wargameDbStore.unshift(wargameDbObject)
}

// remove wargame database
export const deleteWargame = (wargamePath: string): void => {
  const name: string = getNameFromPath(wargamePath)
  const wargame = getWargameDbByName(name)
  wargame.db.destroy()
  const index = wargameDbStore.findIndex((item) => item.name === name)
  wargameDbStore.splice(index, 1)
}

export const listenNewMessage = ({ db, dispatch }: ListenNewMessageType): void => {
  db.changes((msg) => {
    const doc = msg as Message
    if (doc === undefined) return
    if (doc.messageType === INFO_MESSAGE) {
      const infoM = doc as MessageInfoType
      dispatch(setCurrentWargame(doc as Wargame))
      const asAny = infoM as any
      const asMsg = asAny as MessageChannel
      dispatch(setLatestWargameMessage(asMsg))
      return
    }

    if (doc.messageType === FEEDBACK_MESSAGE) {
      const feedbackM = doc as MessageFeedback
      dispatch(setLatestFeedbackMessage(feedbackM))
    } else if (doc.messageType === COUNTER_MESSAGE) {
      // eslint-disable-next-line no-useless-return
      return
    } else {
      dispatch(setLatestWargameMessage(doc as MessageChannel))
    }
  })
}

export const listenForWargameChanges = (name: string, dispatch: PlayerUiDispatch): void => {
  const wargame = getWargameDbByName(name)
  const db = wargame.db
  listenNewMessage({ db, name, dispatch })
}

/** dual function method, to both check the server is still running, and to push
 * details of recent player activity
 * @param log a list of the most recent interactions
 * @param logAllActivity whether to store all events since last ping, or just the most recent one
 * @returns the server response
 */
export const pingServer2 = async (log: ActivityLogsInterface, logAllActivity: boolean): Promise<string> => { 
  const allItems = log.items
  
  // if we're not storing all activity, just store the latest item
  const items: PlayerLogEntries = logAllActivity ? allItems : allItems.length > 0 ? [allItems[allItems.length - 1]] : []

  // get the wargame to operate upon
  const { db } = getWargameDbByName(log.currentDbname)
 
  // In addition to pushing data to the server, we're also checking the server is still alive
  // So, even if the log is empty, we should push an empty list, since still we want to get a 
  // 'success' back from the server
  return db.bulkDocs(items).then(res => res.msg)
}
 
export const getPlayerActivityLogs = async (wargame: string, dbName: string, query: string): Promise<PlayerLogEntries> => {
  const { db } = getWargameDbByName(dbName)

  return await db.getPlayerLogs(wargame, query)
    .then(res => res)
    .catch(err => err)
}

export const populateWargameList = (): Promise<string | Wargame[]> => {
  return fetch(serverPath + allDbs).then((res: Response) => res.json()).then((res: { data: string[] }) => (res.data || []) as string[]).then((dbs: string[]) => {
    const wargameNames: string[] = wargameDbStore.map((db) => db.name)
    const toCreateDiff: string[] = _.difference(dbs, wargameNames)
    const toCreate: string[] = _.pull(toCreateDiff, MSG_STORE, MSG_TYPE_STORE, SERGE_INFO, '_replicator', '_users')
    toCreate.forEach(name => {
      const db = new DbProvider(databasePath + name)
      wargameDbStore.unshift({ name, db })
    })
    
    const promises: (Promise<Wargame>)[] = wargameDbStore.map(({ name, db }) => {
      return getLatestWargameRevision(name).then((res) => {
        return ({
          name: db.name,
          title: res.wargameTitle,
          initiated: res.wargameInitiated,
          shortName: res.name
        })
      }).catch((err) => {
        console.log(err)
        return err
      })
    })
    return Promise.all(promises)
  }).catch((err: string) => {
    console.log(err)
    return err
  })
}

export const clearWargames = (): void => {
  fetch(serverPath + clearAll, { method: 'DELETE' }).then(() => {
    window.location.reload()
  })
}

export const downloadAllWargames = (): void => {
  window.open(serverPath + 'downloadAll')
}

export const openFauxtonUI = ():void => {
  window.open(serverPath + 'db/_utils/')
}

// Note: when the download button is cicked, the SQLITE database be downloaded in a zip format
// This function downloads a wargame by sending a GET request to the server
// with the wargame's name in the URL. The server will respond with the file's contents.
// This function allows a user to download a wargame database in zip format using the given database path as input.
export const downloadWargame = (dbPath: string): void => {
  const dbName = getNameFromPath(dbPath)

  // Construct the URL for downloading the file
  // `serverPath` is a global variable that holds the base URL for the server
  // The URL will look something like this: `http://example.com/download/wargame.db`
  window.open(serverPath + 'download' + '/' + dbName)
}

export const getIpAddress = (): Promise<{ ip: string }> => {
  return fetch(serverPath + 'getIp').then<{ ip: string }>((res: Response) => res.json())
}

// TODO: Need to check component 'ImageDropzone' it returns file with Any type
export const saveIcon = (file: string) => {
  return fetch(serverPath + 'saveIcon', {
    method: 'POST',
    headers: {
      'Content-Type': 'image/png'
    },
    body: file
  }).then((res: Response) => res.json())
}

export const createWargame = async (): Promise<Wargame> => {
  const name = `wargame-${uniqid.time()}`
  const db = new DbProvider(databasePath + name)
  addWargameDbStore({ name, db })
  
  // get all temlete data
  const messages = await messageTypesApi.getAllMessagesFromDb()
  const templetes: TemplateData = {
    templates: messages
  }

  // include templetes whenever you start a new game
  dbDefaultSettings.data.templates = templetes
  const settings: Wargame = { 
    ...dbDefaultSettings, 
    name, 
    wargameTitle: name, 
    phase: ADJUDICATION_PHASE 
  }

  return new Promise((resolve, reject) => {
    // TODO: this method returns the inserted wargame.  I believe we could
    // return that, instead of `getLatestWargameRevisiion`
    db.put(settings)
      .then(() => {
        db.get(wargameSettings).then((res) => {
          // @ts-ignore
          resolve(res)
        }).catch((err) => {
          reject(err)
        })
      }).catch((err) => {
        console.log(err)
        reject(err)
      })
  })
}

export const checkIfWargameStarted = (dbName: string): Promise<boolean> => {
  return getAllMessages(dbName).then((messages) => {
    const latestWargame = messages.find((message) => (message.messageType === INFO_MESSAGE))
    return !!latestWargame
  })
}

// TODO: this gets all the messages from the server, then finds
// the newest wargame. I'm pretty sure that instead of that, we 
// should have a server-side end-point that returns latest wargame,
// then only one document goes over network.
export const getLatestWargameRevision = (dbName: string): Promise<Wargame> => {
  const { db } = getWargameDbByName(dbName)
  return db.lastWargame().then((message) => {
    if (message) return message
    // TODO: if we haven't got an INFO MESSAGE then the database hasn't been
    // TODO: created properly, and we should thrown an error
    return getWargameLocalFromName(dbName)
  }).catch(err => err)
}

export const editWargame = (dbPath: string): Promise<Wargame> => (
  getLatestWargameRevision(getNameFromPath(dbPath))
)

export const exportWargame = (dbPath: string): Promise<Wargame> => {
  const dbName = getNameFromPath(dbPath)
  return getAllMessages(dbName).then((messages) => {
    const nonInfoMessage = messages.filter((msg) => msg.messageType === INFO_MESSAGE) as Message[]
    return getLatestWargameRevision(dbName).then((game) => ({
      ...game, exportMessagelist: nonInfoMessage
    }))
  })
}

export const initiateGame = (dbName: string): Promise<MessageInfoType> => {
  const { db } = getWargameDbByName(dbName)
  return db.get(wargameSettings).then((res) => {
    const wargame = res as Wargame
    const initiatedWargame: Wargame = {
      ...wargame,
      phase: ADJUDICATION_PHASE,
      adjudicationStartTime: moment().format(),
      turnEndTime: moment().add(wargame.data.overview.realtimeTurnTime, 'ms').format(),
      wargameInitiated: true
    }
    return db.put(initiatedWargame).then(() => initiatedWargame)
  }).then((wargame) => {
    const messageInfoType: MessageInfoType = {
      ...wargame,
      _rev: undefined,
      _id: new Date().toISOString(),
      messageType: INFO_MESSAGE,
      gameTurn: 0
    }
    return db.put(messageInfoType).then(() => messageInfoType)
  }).catch((err) => {
    console.log(err)
    return err
  })
}

const updateWargame = (nextWargame: Wargame, dbName: string, revisionCheck = true): Promise<Wargame> => {
  const { db } = getWargameDbByName(dbName)
  return updateWargameByDb(nextWargame, dbName, revisionCheck, db)
}

const updateWargameByDb = (nextWargame: Wargame, dbName: string, revisionCheck = true, db: ApiWargameDb): Promise<Wargame> => {
  console.log('revisionCheck', revisionCheck)
  if (nextWargame.wargameInitiated) {
    // store with new id
    return createLatestWargameRevision(dbName, nextWargame)
  } else {
    // retain un-initiated status id
    // TODO: this put() method returns the inserted wargame.  I believe we could
    // return that, instead of `getLatestWargameRevisiion`
    return db.put({
      ...nextWargame,
      _id: wargameSettings
    }).then(() => {
      return db.get(wargameSettings) as Promise<Wargame>
    })
  }
}

export const updateWargameTitle = (dbName: string, title: string): Promise<Wargame> => {
  return getAllWargames().then((games) => {
    if (games.some((game) => game && game.title === title && getNameFromPath(game.name) !== dbName)) {
      throw new Error('Name already in use.')
    }
    return getLatestWargameRevision(dbName).then((doc) => {
      return updateWargame({ ...doc, wargameTitle: title }, dbName)
    })
  })
}

export const saveSettings = (dbName: string, data: WargameOverview): Promise<Wargame> => {
  return getLatestWargameRevision(dbName).then((res) => {
    const wargame: Wargame = deepCopy(res)
    wargame.data.overview = data
    return updateWargame(wargame, dbName)
  })
}

export const saveChannel = (dbName: string, newData: ChannelTypes): Promise<Wargame> => {
  return getLatestWargameRevision(dbName).then((res) => {
    const newDoc: Wargame = deepCopy(res)
    const updatedData = newDoc.data
    const channels = updatedData.channels.channels || []
    const channelNew = channels.every((channel: ChannelTypes) => channel.uniqid !== newData.uniqid)

    if (channelNew) {
      channels.unshift({ ...newData, name: newData.name })
    } else {
      const channelIndex = channels.findIndex((channel) => channel.uniqid === newData.uniqid)
      channels.splice(channelIndex, 1, { ...newData, name: newData.name })
    }

    updatedData.channels.channels = channels

    return updateWargame({ ...res, data: updatedData }, dbName)
  })
}

export const duplicateChannel = (dbName: string, channelUniqid: string): Promise<Wargame> => {
  return getLatestWargameRevision(dbName).then((res) => {
    const newDoc: Wargame = deepCopy(res)
    const updatedData = newDoc.data
    const channels = updatedData.channels.channels || []
    const channelIndex = channels.findIndex((channel) => channel.uniqid === channelUniqid)

    const duplicateChannel = deepCopy(channels[channelIndex])

    const uniq = uniqid.time()

    duplicateChannel.name = duplicateChannel.name + `-${uniq}`
    duplicateChannel.uniqid = `channel-${uniq}`

    channels.splice(channelIndex, 0, duplicateChannel)
    updatedData.channels.channels = channels
    updatedData.channels.selectedChannel = duplicateChannel
    return updateWargame({ ...res, data: updatedData }, dbName)
  })
}

export const deleteChannel = (dbName: string, channelUniqid: string): Promise<Wargame> => {
  return getLatestWargameRevision(dbName).then((res) => {
    const newDoc: Wargame = deepCopy(res)
    const updatedData = newDoc.data
    const channels = updatedData.channels.channels || []
    updatedData.channels.channels = channels.filter((channel: ChannelTypes) => channel.uniqid != channelUniqid)
    return updateWargame({ ...res, data: updatedData }, dbName)
  })
}

export const saveForces = (dbName: string, newData: ForceData[]) => {
  return getLatestWargameRevision(dbName).then((res) => {
    const newDoc: Wargame = deepCopy(res)
    const updatedData = newDoc.data
    updatedData.forces.forces = newData
    return updateWargame({ ...res, data: updatedData }, dbName)
  })
}

export const saveForce = (dbName: string, newData: ForceData) => {
  return getLatestWargameRevision(dbName).then((res) => {
    const newDoc: Wargame = deepCopy(res)
    const updatedData = newDoc.data
    const forces = updatedData.forces.forces
    const forceNew = forces.every((force) => force.uniqid !== newData.uniqid)

    if (forceNew) {
      forces.unshift({ ...newData, name: newData.name })
    } else {
      const forceIndex = forces.findIndex((force) => force.uniqid === newData.uniqid)
      // forces.forceName = newName;
      forces.splice(forceIndex, 1, { ...newData, name: newData.name })
    }

    updatedData.forces.forces = forces

    // remove default before calc

    const forceCheck: ForceData[] = deepCopy(forces)
    const umpireIndex = forceCheck.findIndex((force) => force.umpire)
    forceCheck.splice(umpireIndex, 1)

    return updateWargame({ ...res, data: updatedData }, dbName)
    // if (newDoc.wargameInitiated) {
    //   return createLatestWargameRevision(dbName, newDoc) // TODO: <<< check this part  `updatedData` saves only if wargame not Initiated
    // } else {
    //   return db.put({
    //     ...newDoc,
    //     _id: dbDefaultSettings._id,
    //     data: updatedData,  // TODO: <<< check this part  `updatedData` saves only if wargame not Initiated
    //     turnEndTime: moment().add(res.data.overview.realtimeTurnTime, 'ms').format(),
    //     // @ts-ignore
    //     wargameInitiated: res.wargameInitiated
    //   }).then<Wargame>(() => {
    //     return db.get(dbDefaultSettings._id)
    //   })
    // }
  })
}

export const deleteForce = (dbName: string, forceId: string): Promise<Wargame> => {
  return getLatestWargameRevision(dbName).then((res) => {
    const newDoc: Wargame = deepCopy(res)
    const updatedData = newDoc.data
    const forces = updatedData.forces.forces

    // remove the indicated force
    updatedData.forces.forces = forces.filter((force: ForceData) => force.uniqid !== forceId)

    // remove participations for this force
    updatedData.channels.channels.forEach((channel: ChannelTypes) => {
      // in the next time we're 'tricking' the compiler into accepting the
      // provided list.  We're not worried about the list being in the correct type
      // since all the entries came from that list
      const parts = channel.participants as ParticipantChat[]

      // drop participations for this force
      channel.participants = parts.filter((sub: ParticipantTypes) => sub.forceUniqid !== forceId)
    })

    // now delete channels with zero participations
    updatedData.channels.channels = updatedData.channels.channels.filter((channel: ChannelTypes) => channel.participants.length > 0)

    if (updatedData.forces.forces.length === 0) {
      updatedData.channels = {
        name: 'Channels',
        channels: [],
        selectedChannel: '',
        dirty: false
      }
    }
    return updateWargame({ ...res, data: updatedData }, dbName)
  })
}

export const duplicateForce = (dbName: string, currentForce: ForceData): Promise<Wargame> => {
  return getLatestWargameRevision(dbName).then((res) => {
    const newDoc: Wargame = deepCopy(res)
    const updatedData = newDoc.data
    const forces = updatedData.forces.forces || []
    const forceIndex = forces.findIndex((force) => force.uniqid === currentForce.uniqid)
    const duplicate = duplicateThisForce(forces[forceIndex])
    forces.splice(forceIndex, 0, duplicate)
    updatedData.forces.forces = forces
    updatedData.forces.selectedForce = duplicate

    return updateWargame({ ...res, data: updatedData }, dbName)
  })
}

export const deleteRolesParticipations = (dbName: string, roles: Role[], key: number): Promise<Wargame> => {
  return getLatestWargameRevision(dbName).then((res): any => {
    const processedData = deleteRoleAndParts(res.data, roles, key)
    if (_.isArray(processedData)) {
      updateWargame({ ...res, data: processedData[0] }, dbName)
      return processedData[1]
    }
    return updateWargame({ ...res, data: processedData }, dbName)
  })
}

export const cleanWargame = (dbPath: string): Promise<WargameRevision[]> => {
  const dbName = getNameFromPath(dbPath)
  const { db } = getWargameDbByName(dbName)
  const uniqId = uniqid.time()
  const newDbName = `wargame-${uniqId}`
  const newDb: ApiWargameDb = new DbProvider(databasePath + newDbName)
  return db.get(wargameSettings).then((res) => {
    const wargame = res as Wargame
    return updateWargameByDb({
      ...wargame,
      _rev: undefined,
      name: newDbName,
      wargameTitle: `${wargame.wargameTitle}-${uniqId}`,
      wargameInitiated: false
    }, newDbName, undefined, newDb).then(() => {
      addWargameDbStore({ name: newDbName, db: newDb })
      return getAllWargames()
    }).catch(rejectDefault)
  })
}

export const duplicateWargame = (dbPath: string): Promise<WargameRevision[]> => {
  const dbName = getNameFromPath(dbPath)
  const { db } = getWargameDbByName(dbName)
  const uniqId = uniqid.time()
  const newDbName = `wargame-${uniqId}`
  const newDb: ApiWargameDb = new DbProvider(databasePath + newDbName)
  // @ts-ignore
  return db.replicate(newDb).then((): Promise<Wargame> => {
    addWargameDbStore({ name: newDbName, db: newDb })
    // get default wargame
    return getWargameLocalFromName(dbName)
  }).then((res) => {
    const wargame = {
      ...res,
      _rev: undefined,
      _id: dbDefaultSettings._id,
      name: newDbName,
      wargameTitle: `${res.wargameTitle}-${uniqId}`
    }
    return newDb.put(wargame).then(() => {
      if (wargame.wargameInitiated) {
        // if wargameInitiated get last infoType and modify name
        return getLatestWargameRevision(newDbName).then((lastWargame: Wargame) => {
          return newDb.put({
            ...lastWargame,
            name: newDbName,
            wargameTitle: `${res.wargameTitle}-${uniqId}`
          })
        }).then(() => getAllWargames())
      }
      return getAllWargames()
    }).catch(rejectDefault)
  }).catch(rejectDefault)
}

export const updateWargameVisible = async (dbPath: string): Promise<Wargame> => {
  const dbName = getNameFromPath(dbPath)
  const { db } = getWargameDbByName(dbName)
  return getLatestWargameRevision(dbName).then(async (wargame: Wargame) => {
    wargame.name = wargame.name.startsWith(hiddenPrefix) ? wargame.name.substr(hiddenPrefix.length) : `${hiddenPrefix}${wargame.name}`
    return db.put(wargame).catch(rejectDefault)
  })
}

// TODO: suspect calls to this should be replaced by:
// TODO: calls to: getLatestWargameRevision
export const getWargameLocalFromName = (dbName: string): Promise<Wargame> => {
  const { db } = getWargameDbByName(dbName)
  // TODO: this should look for most recent wargame (INFO), it currently
  // looks for the un-initiated version of the wargame
  return db.get(wargameSettings).then((res) => res as Wargame)
}

export const getWargame = (gamePath: string): Promise<Wargame> => {
  return (async () => {
    const name = getNameFromPath(gamePath)
    return await getLatestWargameRevision(name)
  })()
}

export const createLatestWargameRevision = (dbName: string, wargame: Wargame): Promise<Wargame> => {
  const copiedData = deepCopy(wargame)
  const { db } = getWargameDbByName(dbName)

  // TODO: this put() method returns the inserted wargame.  I believe we could
  // return that, instead of `getLatestWargameRevisiion`
  return db.put({
    ...copiedData,
    _rev: undefined,
    _id: new Date().toISOString(),
    messageType: INFO_MESSAGE
  }).then(() => {
    return getLatestWargameRevision(dbName)
  }).catch(rejectDefault)
}

export const getAllWargameRevisions = (dbName: string) => {
  getAllMessages(dbName).then((messages) => {
    return messages.filter((message) => (message.messageType === INFO_MESSAGE))
  }).catch(rejectDefault)
}

export const nextGameTurn = (dbName: string): Promise<Wargame> => {
  return getLatestWargameRevision(dbName).then((res) => {
    switch (res.phase) {
      case PLANNING_PHASE:
        res.phase = ADJUDICATION_PHASE
        res.turnEndTime = '0'
        res.adjudicationStartTime = moment().format()
        break
      case ADJUDICATION_PHASE:
        res.phase = PLANNING_PHASE
        res.gameTurn += 1
        res.adjudicationStartTime = '0'
        // move the turn forward

        const gameDate: string = res.data.overview.gameDate
        const gameTurn: GameTurnLength = res.data.overview.gameTurnTime
        const newTime: number = incrementGameTime(gameDate, gameTurn)
        res.data.overview.gameDate = moment(newTime).format('YYYY-MM-DDTHH:mm')
        
        // calculate when the planning must finish
        res.turnEndTime = moment().add(res.data.overview.realtimeTurnTime, 'ms').format()
        break
    }
    return createLatestWargameRevision(dbName, res)
  })
    .catch(rejectDefault)
}

export const postFeedback = (dbName: string, fromDetails: MessageDetailsFrom, turnNumber: number, message: string, name: string): Promise<MessageFeedback> => {
  const { db } = getWargameDbByName(dbName)
  const feedback: MessageFeedback = {
    _id: new Date().toISOString(),
    details: {
      channel: 'Feedback',
      from: fromDetails,
      timestamp: new Date().toISOString(),
      turnNumber
    },
    message: {
      content: message
    },
    messageType: FEEDBACK_MESSAGE
  }

  if (name) feedback.name = name
  
  return db.put(feedback).catch(rejectDefault)
}

export const postMappingMessage = (dbName: string, message: MappingMessage | MappingMessageDelta): Promise<MappingMessage> => {
  const { db } = getWargameDbByName(dbName)
  return db.put(message).catch(rejectDefault)
}

const checkReference = async (message: MessageCustom, db: ApiWargameDb, details: MessageCustom['details']): Promise<MessageCustom> => {
  if (message.templateId !== 'Chat' && typeof message.message.Reference === 'string' && message.message.Reference.length === 0) {
    try {
      const counter = await db.lastCounter(details.from.force, details.timestamp)
      message.details.counter = counter
      message.message.Reference = [message.details.from.force, counter].join('-')
    } catch (err) {
      console.error(err)
    }
  }
  return message
}

export const postNewMessage = async (
  dbName: string, 
  details: MessageDetails,
  message: MessageStructure, 
  templateId: string, 
  messageType: TypeOfCustomMessage
): Promise<MessageCustom> => {
  const { db } = getWargameDbByName(dbName)
  const id = details.timestamp ? details.timestamp : new Date().toISOString()
  const customMessage: MessageCustom = {
    _id: id,
    // defined constat for messages, it's not same as message.templateId,
    // ex for all template based messages will be used CUSTOM_MESSAGE Type
    messageType: messageType || CUSTOM_MESSAGE,
    templateId,
    details,
    message
  }

  if (customMessage.message && typeof customMessage.message.Reference !== 'undefined') {
    await checkReference(customMessage, db, details)
  }
  
  // Save the message
  return db.put(customMessage).catch(rejectDefault)
}

/**
 * Populate a new wargame with bulk data
 * @returns Promise that resolves with the populated wargame
 */
export const populateWargame = (dbName: string, bulkData: Array<Message | Wargame>): Promise<Wargame> => {
  // Generate a unique name for the wargame by appending a timestamp to the end of the name
  const name = `${'wargame'}-${dbName}-${uniqid.time()}`

  // Create a new database provider instance for the new wargame
  const db = new DbProvider(databasePath + name)
  addWargameDbStore({ name, db })
  const customBulkMessage = bulkData

  // Return a new promise that will resolve with the populated wargame
  return new Promise((resolve, reject) => {
    // Call the bulkDocs() function of the new database instance, passing in the bulk data
    db.bulkDocs(customBulkMessage).then(() => {
      // Call getLatestWargameRevision() to retrieve the latest revision of the new wargame
      getLatestWargameRevision(name).then((res) => {
        // @ts-ignore
        return resolve(res)
      }).catch((err) => {
        reject(err)
      })
    }).catch((err) => {
      console.log(err)
      reject(err)
    })
  })
}

export const getAllMessages = (dbName: string): Promise<Message[]> => {
  const { db } = getWargameDbByName(dbName)
  return db.allDocs()
    // TODO: this should probably be a filter function
    .then((res): Array<Message> => {
      // drop counters
      const nonCounter = res.filter((message: Message) => message.messageType !== COUNTER_MESSAGE)
      // NOTE: SPECIAL CASE. It appears the docs are being sorted by _id before being returned.
      // This is putting the initial 'settings' doc at the end. It should be at the start. 
      // If it's at the end, move it to the start
      if (nonCounter.length > 0) {
        const lastDoc = nonCounter[nonCounter.length - 1] as any
        if (lastDoc._id === 'settings') {
          nonCounter.pop()
          nonCounter.unshift(lastDoc)
        }
      }
      return nonCounter
    }
    )
    .catch(() => {
      throw new Error('Serge disconnected')
    })
}

export const getAllWargames = (): Promise<WargameRevision[]> => {
  const promises = wargameDbStore.map<Promise<WargameRevision>>((game) => {
    return getLatestWargameRevision(game.name)
      .then(({ wargameTitle, wargameInitiated, name }) => {
        return {
          name: game.db.name,
          title: wargameTitle,
          initiated: wargameInitiated,
          shortName: name
        }
      }).catch(rejectDefault)
  })
  return Promise.all<WargameRevision>(promises)
}