// https://codeburst.io/dynamic-modules-with-vuex-and-vue-b9c481ca792
// https://www.brophy.org/post/instance-aware-vuex-modules-1/

// import qs from 'querystring'
// import { REST } from 'api/rest-axios'
import { GRAPHQL } from 'api/graphql-axios'
import Query from 'graphql-query-builder'
import { VIMEO } from 'api/vimeo-axios'
import qs from 'querystring'

import * as THREE from 'three'
import { ThreeBSP } from 'three-js-csg-es6'

export default {
  namespaced: true,

  // initial state
  state: {
    debug: true,
    obj3d: null,
    size: { x: 0, y: 0, z: 0 },
    position: { x: 0, y: 0, z: 0 },
    walls3dObj: null,
    wallsPos: null,
    top3dObj: null,
    topPos: null,
    topColor: null,
    floor3dObj: null,
    floorPos: null,
    florrColor: null,
    levels3dObj: null,
    levelsPos: null,
    wall: {
      wallW: 0.001,
      // dig windows on face and back
      winW: 2 + Math.random() * 2,
      // winH: 4 + Math.random() * 4,
      winH: 0,
      margin: 2,
      nbrWinX: 0,
      paddingX: 0,
      nbrWinY: 0,
      paddingY: 0,
      nbrWinZ: 0,
      paddingZ: 0
    },
    contents: {},
    contents_size_factor: 1, // factor to get the contents (grid) size proportional to windows
    contentTypes: ['visible', 'context', 'process', 'concept'],
    grids: null,
    gridCel: {
      width: 6,
      height: 4.5
    },
    gridsContentPlaced: {},
    // gridBebug3dObj: null,
    activeLevel: 'visibles'
  },

  // getters
  getters: {
    position: (state) => {
      return state.position
    },
    size: (state) => {
      return state.size
    },
    createGradientCanvas: (state) => (c1, c2) => {
      var ctx = document.createElement('canvas').getContext('2d')
      ctx.canvas.width = 1024
      ctx.canvas.height = 1024
      var lingrad = ctx.createLinearGradient(0, 0, 0, 1024)
      lingrad.addColorStop(0, c1)
      lingrad.addColorStop(1, c2)
      ctx.fillStyle = lingrad
      ctx.fillRect(0, 0, 1024, 1024)
      return ctx.canvas
    }
  },

  // mutations
  mutations: {
    setSize: (state, size) => {
      state.size = size
    },
    setPosition: (state, pos) => {
      state.position = pos
    },
    setWalls3dObj: (state, obj) => { state.walls3dObj = obj },
    setWallsPos: (state, pos) => { state.wallsPos = pos },
    setTop3dObj: (state, obj) => { state.top3dObj = obj },
    setTopPos: (state, pos) => { state.topPos = pos },
    setFloor3dObj: (state, obj) => { state.floor3dObj = obj },
    setFloorPos: (state, pos) => { state.floorPos = pos },
    setTopColor: (state, col) => { state.topColor = col },
    setFloorColor: (state, col) => { state.floorColor = col },
    setLevels3dObj: (state, obj) => { state.levels3dObj = obj },
    setLevelsPos: (state, pos) => { state.levelsPos = pos },
    setContents: (state, contents) => { state.contents = contents },
    setGrids: (state, grids) => { state.grids = grids },
    // setDebugGrids3dObj: (state, obj) => { state.gridBebug3dObj = obj },
    shiftGrid: (state, c) => {
      let side
      switch (c.face) {
        case 'back':
        case 'front':
          side = 'x'
          break
        case 'left':
        case 'right':
          side = 'z'
          break
      }
      // console.log(`shiftGrid side: ${side}, face: ${c.face}, type: ${c.type}, id: ${c.id}`)
      let p = state.grids[side][c.face][c.type].pop()
      if (!state.gridsContentPlaced[side]) {
        state.gridsContentPlaced[side] = {}
      }
      if (!state.gridsContentPlaced[side][c.face]) {
        state.gridsContentPlaced[side][c.face] = {}
      }
      if (!state.gridsContentPlaced[side][c.face][c.type]) {
        state.gridsContentPlaced[side][c.face][c.type] = {}
      }
      state.gridsContentPlaced[side][c.face][c.type][c.id] = p
    },
    setActiveLevel: (state, l) => {
      state.activeLevel = l
    }
  },

  // actions
  actions: {
    init ({ dispatch, commit, state, rootGetters }) {
      console.log(`Init Project module ${state.id}`, state)
      dispatch('sizingBuilding')
      dispatch('build3dObjs')
    },
    sizingBuilding ({ dispatch, commit, state, rootGetters }) {
      console.log('sizingBuilding, state', state)
      let totalW = rootGetters['Projects/totalW']
      // console.log('totalW', totalW)
      let margin = rootGetters['Projects/marginBetweenBuildings']
      // console.log('margin', margin)

      // positioning buildings on x regarding the widths
      // & setting up the window sizing
      // & setting up the content grid
      // let wall, a
      // let grid

      // X POS
      let x
      if (state.index === 0) {
        // if it's the first
        x = -1 * totalW / 2 + state.size.x / 2
      } else {
        // else get the precedent pos
        let prevProjID = rootGetters['Projects/projectID'](state.index - 1)
        // console.log('prevProjID', prevProjID)
        let prevProjPos = rootGetters[`project:${prevProjID}/position`]
        // console.log(`project:${prevProjID}/position.x`, prevProjPos.x)
        let prevProjSize = rootGetters[`project:${prevProjID}/size`]
        // console.log(`project:${prevProjID}/size.x`, prevProjSize.x)
        // console.log('state.size.x', state.size.x)
        //  prev X        + alf of prev size x + margin + half of current size x
        x = prevProjPos.x + prevProjSize.x / 2 + margin + state.size.x / 2
      }
      // console.log('x', x)
      commit('setPosition', {
        x: x,
        // y: -1 * state.size.y / 2 + 10 + Math.random() * 30, // -10 + Math.random() * this.size.y / 2
        y: -state.size.y / 4,
        z: 0// -10 + Math.random() * 10
      })

      // WINDOWS
      let a = 0
      state.wall.nbrWinX = Math.floor((state.size.x - 2 * state.wall.margin) / state.wall.winW)
      // removing windows on X until padding is enough
      while (state.wall.paddingX < 0.4) {
        state.wall.nbrWinX -= a
        state.wall.paddingX = (state.size.x - 2 * state.wall.margin - state.wall.winW * state.wall.nbrWinX) / (state.wall.nbrWinX - 1)
        a++
      }
      a = 0
      // state.wall.nbrWinY = Math.floor((state.size.y - 2 * state.wall.margin) / state.wall.winH)
      state.wall.nbrWinY = 4 * 10
      state.wall.winH = (state.size.y - 2 * state.wall.margin) / state.wall.nbrWinY
      // removing windows on Y until padding is enough
      while (state.wall.paddingY < 0.4) {
        state.wall.nbrWinY -= a
        state.wall.paddingY = (state.size.y - 2 * state.wall.margin - state.wall.winH * state.wall.nbrWinY) / (state.wall.nbrWinY - 1)
        a += 4
      }
      a = 0
      state.wall.nbrWinZ = Math.floor((state.size.z - 2 * state.wall.margin) / state.wall.winW)
      while (state.wall.paddingZ < 0.4) {
        state.wall.nbrWinZ -= a
        state.wall.paddingZ = (state.size.z - 2 * state.wall.margin - state.wall.winW * state.wall.nbrWinZ) / (state.wall.nbrWinZ - 1)
        a++
      }

      // CONTENTS GRID
      // create grids for right/left & face/back
      let grids = {
        x: { front: {}, back: {} },
        z: { left: {}, right: {} }
      }
      // console.log('grids', grids)
      let grid, rows, cols, t, cel, paddingH
      // console.log('Object.keys(grids)', Object.keys(grids))
      // Object.keys(grids).map(function (side) {
      Object.keys(grids).forEach(side => {
        // console.log('# GRID side:', side)
        // create one grid for each face x & z
        // will then clone both for each face front/back or left/right)
        grid = {}
        // calculate cols nbr regarding the face orientation
        switch (side) {
          case 'z':
            // cols = state.wall.nbrWinZ / state.contents_size_factor
            cols = Math.floor(state.size.z / state.gridCel.width)
            // compute padding to centering the grid horizontaly
            paddingH = (state.size.z - cols * state.gridCel.width) / 2
            break
          case 'x':
            // cols = state.wall.nbrWinX / state.contents_size_factor
            cols = Math.floor(state.size.x / state.gridCel.width)
            // compute padding to centering the grid horizontaly
            paddingH = (state.size.x - cols * state.gridCel.width) / 2
            break
        }
        // calculate rows
        // rows = state.wall.nbrWinY / state.contents_size_factor
        rows = Math.floor(state.size.y / state.gridCel.height)

        // dispatch grid rows on to 4 levels (t)
        for (var m = rows - 1; m > 0; m--) { // rows
          if (m > rows / 4 * 3 + 1) {
            t = state.contentTypes[0]
          } else if (m > rows / 4 * 2 + 1) {
            t = state.contentTypes[1]
          } else if (m > rows / 4 + 1) {
            t = state.contentTypes[2]
          } else {
            t = state.contentTypes[3]
          }
          // create level if not exists
          if (!grid[t]) {
            grid[t] = []
            // skip the first level's row as we will display wall title
            continue
          }
          // create cols for each rows
          for (var l = cols - 1; l >= 0; l--) { // cols
            // cel = { y: margin + state.wall.winH * state.contents_size_factor * m }
            cel = { y: state.position.y - state.size.y / 2 + state.gridCel.height * m }
            switch (side) {
              case 'z':
                // cel.z = margin + state.wall.winW * state.contents_size_factor * l
                cel.z = state.position.z - state.size.z / 2 + paddingH + state.gridCel.width * l
                break
              case 'x':
                // cel.x = margin + state.wall.winW * state.contents_size_factor * l
                cel.x = state.position.x - state.size.x / 2 + paddingH + state.gridCel.width * l
                break
            }
            grid[t].push(cel)
          }
        }
        // console.log('grid', grid)

        // apply grid for each face of each side
        // Object.keys(grids[side]).map(function (face) {
        Object.keys(grids[side]).forEach(face => {
          // console.log('## GRID face:', face)
          // // shuffling the grid
          // for (var i = 0; i < state.contentTypes.length; i++) {
          //   for (let n = grid[state.contentTypes[i]].length - 1; n > 0; n--) {
          //     const o = Math.floor(Math.random() * n)
          //     const temp = grid[state.contentTypes[i]][n]
          //     grid[state.contentTypes[i]][n] = grid[state.contentTypes[i]][o]
          //     grid[state.contentTypes[i]][o] = temp
          //   }
          // }
          // // console.log('shuffeld grid', grid)

          // cloning the grid for each face
          // grids[side][face] = { ...grid } this is not working as it is not deep copy
          grids[side][face] = JSON.parse(JSON.stringify(grid))

          // comput common values
          let x, z, ry
          switch (face) {
            case 'front': // side x
              z = state.position.z + state.size.z / 2 - 0.1
              ry = 180
              break
            case 'back': // side x
              z = state.position.z - state.size.z / 2 + 0.1
              ry = 0
              break
            case 'left': // side z
              x = state.position.x - state.size.x / 2 + 0.1
              ry = 90
              break
            case 'right': // side z
              x = state.position.x + state.size.x / 2 - 0.1
              ry = -90
              break
          }

          // loop through levels
          // apply common values
          // Object.keys(grids[side][face]).map(function (level) {
          Object.keys(grids[side][face]).forEach(level => {
            // console.log(`${side} ${face} ${level} x: ${x}, z: ${z}, ry: ${ry}`)
            // loop through all cels of the level
            for (var i = 0; i < grids[side][face][level].length; i++) {
              grids[side][face][level][i].ry = ry
              switch (side) {
                case 'z':
                  grids[side][face][level][i].x = x
                  break
                case 'x':
                  grids[side][face][level][i].z = z
                  break
              }
            }
          })
        })
      })
      commit('setGrids', grids)
      console.log('state.grids', state.grids)
    },
    build3dObjs ({ dispatch, commit, state, getters, rootGetters }) {
      console.log('build3dObjs')
      // http://learningthreejs.com/blog/2011/12/10/constructive-solid-geometry-with-csg-js/

      let frontGeom = new THREE.BoxGeometry(state.size.x, state.size.y, state.wall.wallW)
      let frontMesh = new THREE.Mesh(frontGeom)
      frontMesh.position.z = 0.5 * state.size.z
      let frontBSP = new ThreeBSP(frontMesh)

      // https://medium.com/techtrument/multithreading-javascript-46156179cf9a
      let winGeom = new THREE.BoxGeometry(state.wall.winW, state.wall.winH, state.wall.wallW)
      let winMesh = new THREE.Mesh(winGeom)
      let windowsGeom = new THREE.Geometry()

      for (var i = 0; i < state.wall.nbrWinX; i++) {
        for (var j = 0; j < state.wall.nbrWinY; j++) {
          winMesh.position.z = 0.5 * state.size.z
          winMesh.position.x = -0.5 * state.size.x + state.wall.margin + state.wall.winW * 0.5 + i * (state.wall.winW + state.wall.paddingX)
          winMesh.position.y = 0.5 * state.size.y - state.wall.margin - state.wall.winH * 0.5 - j * (state.wall.winH + state.wall.paddingY)
          // winMesh.updateMatrix()
          windowsGeom.mergeMesh(winMesh)
        }
      }
      let windowsBSP = new ThreeBSP(windowsGeom)
      let frontWindowedBSP = frontBSP.subtract(windowsBSP)

      // console.log('state.Faces_opaques', state.Faces_opaques)
      let backWindowedMesh
      if (state.Faces_opaques === 3) {
        backWindowedMesh = frontMesh.clone()
      } else {
        let frontWindowedMesh = frontWindowedBSP.toMesh()
        backWindowedMesh = frontWindowedMesh.clone()
      }
      backWindowedMesh.position.z = -0.5 * state.size.z
      let backWindowedBSP = new ThreeBSP(backWindowedMesh)

      let rightGeom = new THREE.BoxGeometry(state.wall.wallW, state.size.y, state.size.z)
      let rightMesh = new THREE.Mesh(rightGeom)
      rightMesh.position.x = 0.5 * state.size.x
      // rightMesh.position.z = 0.5 * state.size.z
      let rightBSP = new ThreeBSP(rightMesh)

      let leftMesh = rightMesh.clone()
      leftMesh.position.x = -0.5 * state.size.x
      // leftMesh.position.z = 0.5 * state.size.z
      let leftBSP = new ThreeBSP(leftMesh)

      let buildingBSP = frontWindowedBSP.union(rightBSP)
      buildingBSP = buildingBSP.union(backWindowedBSP)
      buildingBSP = buildingBSP.union(leftBSP)

      // convert back to three.js mesh
      let building = buildingBSP.toMesh()

      // create a classical material for building
      // let topColor = `hsla(201, 100%, 95%, 1)`
      let hTop = Math.round(190 + Math.random() * 20)
      let sTop = Math.round(20 + Math.random() * 60)
      let lTop = Math.round(75)
      // let hFloor = Math.round(205 + Math.random() * 10)
      let hFloor = hTop
      // let sFloor = Math.round(30 + Math.random() * 40)
      let sFloor = sTop
      // let lFloor = Math.round(10 + Math.random() * 20)
      let lFloor = Math.round(10)
      let topColor = `hsla(${hTop}, ${sTop}%, ${lTop}%, 1)`
      commit('setTopColor', topColor)
      let floorColor = `hsla(${hFloor}, ${sFloor}%, ${lFloor}%, 1)`
      commit('setFloorColor', floorColor)
      let gradientTexture = new THREE.CanvasTexture(getters.createGradientCanvas(topColor, floorColor))
      let materialOpts = {
        color: 0xffffff,
        side: THREE.DoubleSide,
        shininess: 30,
        map: gradientTexture
      }
      building.material = new THREE.MeshPhongMaterial(materialOpts)
      // commiting walls
      commit('setWalls3dObj', building)
      let buildingPos = { ...state.position, ...{ z: state.position.z + 0.5 * state.size.z } }
      commit('setWallsPos', buildingPos)

      // TOP & FLOOR
      let topGeom = new THREE.BoxGeometry(state.size.x, state.wall.wallW, state.size.z)
      let topOpts = {
        color: new THREE.Color(`hsl(${hTop}, ${sTop}%, ${lTop}%)`),
        shininess: 30
      }
      let topMat = new THREE.MeshPhongMaterial(topOpts)
      let topMesh = new THREE.Mesh(topGeom, topMat)
      commit('setTop3dObj', topMesh)
      let topPosition = { ...state.position, ...{ y: state.position.y + 0.5 * state.size.y } }
      commit('setTopPos', topPosition)

      let floorOpts = {
        color: new THREE.Color(`hsl(${hFloor}, ${sFloor}%, ${lFloor}%)`),
        shininess: 10
      }
      let floorMat = new THREE.MeshPhongMaterial(floorOpts)
      let floorMesh = new THREE.Mesh(topGeom, floorMat)
      commit('setFloor3dObj', floorMesh)
      let floorPosition = { ...state.position, ...{ y: state.position.y - 0.5 * state.size.y } }
      commit('setFloorPos', floorPosition)

      // LEVELS
      let levelGeom = new THREE.BoxGeometry(state.size.x - state.wall.wallW * 2, 0.1, state.size.z - state.wall.wallW * 2)
      let levelMesh = new THREE.Mesh(levelGeom)
      let levelBSP = new ThreeBSP(levelMesh)
      let levelHoleGeom = new THREE.BoxGeometry(state.size.x - state.wall.wallW * 2 - 3, 0.1, state.size.z - state.wall.wallW * 2 - 3)
      let levelHoleMesh = new THREE.Mesh(levelHoleGeom)
      let levelHoleBSP = new ThreeBSP(levelHoleMesh)
      levelBSP = levelBSP.subtract(levelHoleBSP)
      levelMesh = levelBSP.toMesh()
      let levelsGeom = new THREE.Geometry()
      for (var k = -1; k < 2; k++) {
        levelMesh.position.y = k * state.size.y * 0.25
        levelsGeom.mergeMesh(levelMesh)
      }
      let levelsOpts = {
        color: new THREE.Color(`hsl(${hTop}, ${sTop}%, ${lTop}%)`),
        shininess: 10
      }
      let levelsMat = new THREE.MeshPhongMaterial(levelsOpts)
      let levelsMesh = new THREE.Mesh(levelsGeom, levelsMat)
      commit('setLevels3dObj', levelsMesh)
      let levelsPos = { ...state.position }
      commit('setLevelsPos', levelsPos)

      // // DEBUG GRID
      // if (state.debug) {
      //   let celDebugGeom = new THREE.BoxGeometry(1, 1, 0.01)
      //   let celDebugMesh = new THREE.Mesh(celDebugGeom)
      //   let gridsDebugGeom = new THREE.Geometry()
      //   Object.keys(state.grids).map(function (side) {
      //     Object.keys(state.grids[side]).map(function (face) {
      //       Object.keys(state.grids[side][face]).map(function (level) {
      //         state.grids[side][face][level].forEach((item, i) => {
      //           // console.log(`grid ${side} ${face} ${level} ${i}`, item)
      //           celDebugMesh.position.y = state.grids[side][face][level][i].y
      //           celDebugMesh.position.x = state.grids[side][face][level][i].x
      //           celDebugMesh.position.z = state.grids[side][face][level][i].z
      //           celDebugMesh.rotation.y = state.grids[side][face][level][i].ry
      //           gridsDebugGeom.mergeMesh(celDebugMesh)
      //         })
      //       })
      //     })
      //   })
      //   let gridsDebugOpts = {
      //     color: 0xff0000
      //   }
      //   let gridsDebugMat = new THREE.MeshPhongMaterial(gridsDebugOpts)
      //   let gridsDebugMesh = new THREE.Mesh(gridsDebugGeom, gridsDebugMat)
      //   commit('setDebugGrids3dObj', gridsDebugMesh)
      // }
    },
    getContents ({ dispatch, commit, state }) {
      return new Promise((resolve, reject) => {
        // get the list of corpuses (aka authors)
        dispatch('loadContents')
          .then(({ data: { data: { project } = null } }) => {
            console.log('graphql contents', project)
            dispatch('computeContents', project)
              .then((contents) => {
                console.log('computed contents', contents)
                commit('setContents', project)
              })
          })
          .catch((error) => {
            console.warn('Issue with getContents', error)
            Promise.reject(error)
          })
      })
    },
    loadContents ({ dispatch, commit, state }) {
      console.log('loadContents')
      let contentsQuery = new Query('project', { id: state.id })
      let visiblesQuery = new Query('visibles')
      visiblesQuery.filter({ Published: true })
      visiblesQuery.find(['id', 'Name', 'Text2', 'Vimeo', 'Url', 'categories'])
      contentsQuery.find([visiblesQuery])
      console.log('contentsQuery', `${contentsQuery}`)

      return GRAPHQL.post('', { query: `query {
          project(id: "${state.id}") {
            visibles(where: { Published: "true" }){
              id
              Name
              Media {
                url
                size
              }
              Text2
              Vimeo
              Url
              categories
              country{
                id
                Name
              }
            }
            contexts(where: { Published: "true" }){
              id
              Name
              Images {
                url
                size
              }
              Text2
              Vimeo
              Url
              country{
                id
                Name
              }
            }
            processes(where: { Published: "true" }){
              id
              Name
              Media {
                url
                size
              }
              Text2
              Vimeo
              Url
              country{
                id
                Name
              }
            }
            concepts(where: { Published: "true" }){
              id
              Name
              Images {
                url
                size
              }
              Text2
              Vimeo
            }
          }
        }` })
    },
    computeContents ({ dispatch, commit, state }, contents) {
      console.log('computeContents')
      return Promise.all(Object.keys(contents).map(function (key) {
        return Promise.all(contents[key].map(function (content) {
          if (content.Vimeo) {
            // console.log('Vimeo', content.Vimeo)
            // GET https://vimeo.com/api/oembed.json?url=https%3A//vimeo.com/286898202&width=480&height=360
            let params = {
              url: content.Vimeo,
              width: 1280,
              height: 720,
              responsive: true
            }
            let q = qs.stringify(params)
            return VIMEO.get('?' + q, {})
              .then(({ data }) => {
                // console.log('Vimeo data', data)
                content.Vimeo = data// thumbnail_url_with_play_button
                return content
              })
              .catch((error) => {
                console.warn('Issue with vimeo', error)
              })
          } else {
            return content
          }
        })).then((cts) => {
          // return an object with the right key (visible, etc, ...)
          // console.log(`${key} cts`, cts)
          let o = {}
          o[key] = cts
          return o
        })
      })).then((a) => {
        // return an object with the right keys (visible, etc, ...)
        console.log('a', a)
        let o = {}
        for (var i = 0; i < a.length; i++) {
          o = { ...o, ...a[i] }
        }
        return o
      })
    }
    // getGridPos ({ state, commit }) {
    //   let p = state.grid[0]
    //   commit('shiftGrid')
    //   Promise.resolve(p)
    // }
  }
}
