import * as _ from 'lodash';

import { GameSortField, UserSortField, SortDirection } from 'types';

import { SortUtil } from '../common';

import { RoomsState } from './rooms.interfaces'
import { MAX_ROOM_MESSAGES, Types } from './rooms.types';

const initialState: RoomsState = {
  rooms: {},
  games: {},
  joinedRoomIds: {},
  joinedGameIds: {},
  messages: {},
  sortGamesBy: {
    field: GameSortField.START_TIME,
    order: SortDirection.DESC
  },
  sortUsersBy: {
    field: UserSortField.NAME,
    order: SortDirection.ASC
  }
};

export const roomsReducer = (state = initialState, action: any) => {
  switch (action.type) {
    case Types.CLEAR_STORE: {
      return {
        ...initialState
      };
    }

    case Types.UPDATE_ROOMS: {
      const rooms = {
        ...state.rooms
      };

      // Server does not send everything on updates
      _.each(action.rooms, (room, order) => {
        const { roomId } = room;
        const existing = rooms[roomId] || {};

        const update = { ...room };
        delete update.gameList;
        delete update.gametypeList;
        delete update.userList;

        rooms[roomId] = {
          ...existing,
          ...update,
          order
        };
      });

      return { ...state, rooms };
    }

    case Types.JOIN_ROOM: {
      const { roomInfo } = action;
      const { joinedRoomIds, rooms, sortGamesBy, sortUsersBy } = state;

      const { roomId } = roomInfo;

      const gameList = [
        ...roomInfo.gameList
      ];

      const userList = [
        ...roomInfo.userList
      ];

      SortUtil.sortByField(gameList, sortGamesBy);
      SortUtil.sortUsersByField(userList, sortUsersBy);

      return {
        ...state,

        rooms: {
          ...rooms,
          [roomId]: {
            ...roomInfo,
            gameList,
            userList
          }
        },

        joinedRoomIds: {
          ...joinedRoomIds,
          [roomId]: true
        },
      }
    }

    case Types.LEAVE_ROOM: {
      const { roomId } = action;
      const { joinedRoomIds, messages } = state;

      const _joined = {
        ...joinedRoomIds
      };

      const _messages = {
        ...messages
      };

      delete _joined[roomId];
      delete _messages[roomId];

      return {
        ...state,

        joinedRoomIds: _joined,
        messages: _messages,
      }
    }

    case Types.ADD_MESSAGE: {
      const { roomId, message } = action;
      const { messages } = state;

      let roomMessages = [...(messages[roomId] || [])];

      if (roomMessages.length === MAX_ROOM_MESSAGES) {
        roomMessages.shift();
      }

      message.timeReceived = new Date().getTime();
      roomMessages.push(message);

      return {
        ...state,
        messages: {
          ...messages,

          [roomId]: [
            ...roomMessages
          ]
        }
      }
    }
    // @TODO improve this reducer, likely by improving the store model

    case Types.UPDATE_GAMES: {
      const { roomId, games } = action;
      const { rooms, sortGamesBy } = state;
      const room = rooms[roomId];

      if (!room) {
        return { ...state };
      }

      // Create map of games with update objects
      const toUpdate = games.reduce((map, game) => {
        map[game.gameId] = game;
        return map;
      }, {});

      const gameUpdates = room.gameList
      // filter out closed games and remove from update map
        .filter(game => {
          const gameUpdate = toUpdate[game.gameId];
          const closedGame = gameUpdate && gameUpdate.closed;

          if (closedGame) {
            delete toUpdate[game.gameId];
          }

          return !closedGame;
        })
        .map(game => {
          const gameUpdate = toUpdate[game.gameId];

          if (gameUpdate) {
            delete toUpdate[game.gameId];

            return {
              ...game,
              ...gameUpdate
            };
          }

          return game;
        });

      // Push new games to end of list
      if (_.size(toUpdate)) {
        _.each(toUpdate, game => gameUpdates.push(game));
      }

      const gameList = [...gameUpdates];

      SortUtil.sortByField(gameList, sortGamesBy);

      return {
        ...state,
        rooms: {
          ...rooms,
          [roomId]: {
            ...room,
            gameList
          }
        }
      }
    }

    case Types.USER_JOINED: {
      const { roomId, user } = action;
      const { rooms, sortUsersBy } = state;

      const room = { ...rooms[roomId] };

      const userList = [
        ...room.userList,
        user
      ];

      SortUtil.sortUsersByField(userList, sortUsersBy);

      return {
        ...state,
        rooms: {
          ...rooms,
          [roomId]: {
            ...room,
            userList
          }
        }
      };
    }

    case Types.USER_LEFT: {
      const { roomId, name } = action;
      const { rooms } = state;

      const room = { ...rooms[roomId] };
      const userList = room.userList.filter(user => user.name !== name);

      return {
        ...state,
        rooms: {
          ...rooms,
          [roomId]: {
            ...room,
            userList
          }
        }
      };
    }

    case Types.SORT_GAMES: {
      const { field, order, roomId } = action;
      const { rooms } = state;

      const gameList = [...rooms[roomId].gameList];

      const sortGamesBy = {
        field, order
      };

      SortUtil.sortByField(gameList, sortGamesBy);

      return {
        ...state,

        rooms: {
          ...rooms,
          [roomId]: {
            ...rooms[roomId],
            gameList
          }
        },

        sortGamesBy
      }
    }

    case Types.REMOVE_MESSAGES: {
      const { name, amount, roomId } = action;
      const { messages } = state;
      let amountRemoved = 0;

      return {
        ...state,
        messages: {
          ...messages,
          [roomId]: messages[roomId]
            .reverse()
            .filter(({ message }) => {
              if (amount === amountRemoved) {
                return true;
              }

              const keep = message.indexOf(`${name}:`) !== 0;

              if (!keep) {
                amountRemoved++;
              }

              return keep;
            })
            .reverse()
        }
      }
    }

    case Types.JOINED_GAME: {
      const { gameId, roomId } = action;
      const { joinedGameIds } = state;

      return {
        ...state,
        joinedGameIds: {
          ...joinedGameIds,
          [roomId]: {
            ...joinedGameIds[roomId],
            [gameId]: true,
          }
        }
      }
    }

    default:
      return state;
  }
}
