import querystring from 'query-string'
import { WS } from './lib/websocket'
import { goto } from '@sapper/app'

class Bootstrap {
  constructor() {
    this.url = null

    window.notify = this.notify.bind(this)

    // Open Websocket
    const parseWebsocketOrigin = (string) => {
      if (/localhost/.test(string)) {
        return {
          host: 'localhost',
          protocol: 'ws://',
          port: 25706
        }
      } else {
        return {
          host: 'www.processor.' + string.match(/(?<=\/\/)(www.app.)?([^:]*)/)[2],
          protocol: 'wss://',
        }
      }

    }

    const ws = new WS(
      Object.assign(parseWebsocketOrigin(location.origin), {
        connection: 'node'
      })
    )

    ws.on('open', () => {
      this.notify({
        error: 'Connected to node',
        duration: 1000,
        prompt: false,
        duplicate: false
      })

      // authorization.getPersistantSession(ws)
    })

    ws.on('message', ({ data }) => {
      try {
        const message = JSON.parse(data)
        console.log(`incoming ${data}`)

        switch (message.o) {
          case 'p':
          case 'r':
            if (message.v === null) {
              message.v = undefined
            }

            const path = message.p
            const value = message.v

            if (_.isPlainObject(message.v)) {
              datastore.merge(path, value)
            } else {
              datastore.set(path, value)
            }

            const queued = datastore.get('/q' + message.p)
            if (_.isEqual(message.v, queued)) {
              const q_path = '/q' + message.p
              const q_steps = q_path.slice(1).split('/')
              datastore.delete(q_steps, { silent: true })
            }
            break
          case 'a':
            console.log(message)
            if (message.v === undefined) { return }
            console.log(message.c)

            switch (message.c) {
              case 0:
                const pointer = Pointer
                  .create(message.t)
                  .replace('/#', '/create')
                  .replace('state', 'action')

                datastore.write(pointer, message.v)
                datastore.destroy(pointer, message.v)

                if (!message.v) authorization.reset(message.t)
                break
              case 2:
                authorization.deriveSharedKey(message.t, message.v, ws)
                break
              case 4:
                if (authorization.verifySessionKey(message.t, message.v, ws) &&
                    /\/users/.test(message.t)) {
                  authorization.requestPersistantSession(message.v.u, ws)
                }
                break
              case 5:
                if (!message.v) { break }

                authorization.pickUpSession(message.t, message.v, ws)
                break
              case 6:
                if (!message.v) { break }

                authorization.setPersistantSession(message.v, ws)
                break
            }
            break
        }
      } catch (e) {
        console.error('Error parsing message from server.', e, data)
      }
    })

    ws.on('close', () => {
      this.notify({
        error: 'Disconnected from djin node',
        duration: 0,
        duplicate: false
      })
    })

    const subscriber = {}

    datastore.subscribe('q/setup/#', subscriber, (topic, pointer, value) => {
      if (_.isPlainObject(value)) {
        return
      }

      send(value, pointer.path)
    })

    datastore.subscribe('session/redirect', subscriber, (topic, pointer, value) => {
      if (_.isString(value)) {
        datastore.write('/session/redirect', null)
        goto(value)
      }
    })

    // Bind to url parameters

    datastore.subscribe('session/path/*', this, (topic, pointer, value) => {
      try {
        const { route, query, hash } = value

        let next = route || '/'

        localStorage.setItem('djinlist', JSON.stringify(value))

        const session_query = {}
        Object.keys(query || {})
          .sort()
          .forEach(key => {
            session_query[key] = query[key]
          })

        const session_hash = {}
        Object.keys(hash || {})
          .sort()
          .forEach(key => {
            session_hash[key] = hash[key]
          })

        next = next +
               (session_query && (session_query.length != 0) ? '?' + querystring.stringify(session_query) : '') +
               (session_hash && (session_hash.length != 0) ? '#' + querystring.stringify(session_hash) : '')

        if ((location.pathname || '' + location.query || '' + location.hash || '') === next) {
          return
        }

        if (!('standalone' in navigator && navigator.standalone === true)) {
          history.replaceState({}, '', next)
        }
      } catch (e) {
        return
      }
    })

    /* Query Parameters */

    if (!('standalone' in navigator && navigator.standalone === true)) {
      window.addEventListener('popstate', this.parse)
    }

    setTimeout(() => this.initialize(), 0)
  }

  parse() {
    const origin = location.origin
    const route = location.pathname
    const query = querystring.parse(location.search.slice(1))
    const hash = querystring.parse(location.hash.slice(1))

    let match = route.match(/^(\/\w+)*/)

    let evaluateRoute = ({ route, query, hash }) => {
      /* Setup */
      datastore.write('/session/path', {
        origin,
        route,
        query,
        hash
      })
    }

    if (match != null) {
      let [, route ] = match

      evaluateRoute({ route, query, hash })
    } else {
      const local = localStorage.getItem('djinlist')
      if (local == null) {
        evaluateRoute({ route: null, query, hash })
        return
      }

      const local_json = JSON.parse(local)
      evaluateRoute(_.merge(local_json, {query, hash}))
    }

    return
  }

  // Initialize and define parsing methods

  initialize() {
    this.parse()
  }

  notify(options) {
    let id = 1

    while (datastore.has(`/session/notifications/${id}`)) {
      id += 1
    }

    const notification = Object.assign(
      {},
      {
        id,
        time: Date.now(),
        duration: 2000,
        prompt: true,
        message: options.error.toString() || 'An error occurred',
        error: 'An error occurred',
        duplicate: true,
        overwrite: false
      },
      options
    )

    if (!notification.duplicate) {
      const notifications = datastore.read('/session/notifications/+')

      for (const n of Object.values(notifications)) {
        if (n.message === notification.message && !n.hidden && !n.overwrite) {
          return
        }
      }
    }

    datastore.write(`/session/notifications/${id}`, notification)
  }
}

export { Bootstrap }