//
//  Created by Thijs Wenker on 3/31/2017
//  Copyright 2017 High Fidelity, Inc.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

(function() {
    var _this;

    var entityJSONCache = {};

    var SANITIZE_PROPERTIES = ['childEntities', 'parentID', 'id'];
    var ZERO_UUID = '{00000000-0000-0000-0000-000000000000}';

    function entityListToTree(entitiesList) {
        function entityListToTreeRecursive(properties) {
            properties.childEntities = [];
            entitiesList.forEach(function(entityProperties) {
                if (properties.id === entityProperties.parentID) {
                    properties.childEntities.push(entityListToTreeRecursive(entityProperties));
                }
            });
            return properties;
        }
        var entityTree = [];
        entitiesList.forEach(function(entityProperties) {
            if (entityProperties.parentID === undefined || entityProperties.parentID === ZERO_UUID) {
                entityTree.push(entityListToTreeRecursive(entityProperties));
            }
        });
        return entityTree;
    }

    function importEntitiesJSON(importLink, parentProperties, overrideProperties) {
        if (parentProperties === undefined) {
            parentProperties = {};
        }
        if (overrideProperties !== undefined) {
            parentProperties.overrideProperties = overrideProperties;
        }
        try {
            var entityJSONImport = Script.require(importLink).Entities;
            parentProperties.childEntities = entityListToTree(entityJSONImport);
            return parentProperties;
        } catch (e) {
            print('Failed importing entities JSON because: ' + JSON.stringify(e));
        }
        return null;
    }

    function fixLocalPath(link) {
        if (link.indexOf('://') === -1) {
            return Script.resolvePath(link);
        }
        return link;
    }

    // Creates an entity and returns a mixed object of the creation properties and the assigned entityID
    var createEntity = function(entityProperties, parent, overrideProperties) {
        // JSON.stringify -> JSON.parse trick to create a fresh copy of JSON data
        var newEntityProperties = JSON.parse(JSON.stringify(entityProperties));
        if (overrideProperties !== undefined) {
            Object.keys(overrideProperties).forEach(function(key) {
                newEntityProperties[key] = overrideProperties[key];
            });
        }
        if (parent.rotation !== undefined) {
            if (newEntityProperties.rotation !== undefined) {
                newEntityProperties.rotation = Quat.multiply(parent.rotation, newEntityProperties.rotation);
            } else {
                newEntityProperties.rotation = parent.rotation;
            }
        }
        if (parent.position !== undefined) {
            var localPosition = (parent.rotation !== undefined) ?
                Vec3.multiplyQbyV(parent.rotation, newEntityProperties.position) : newEntityProperties.position;
            newEntityProperties.position = Vec3.sum(localPosition, parent.position);
        }
        if (parent.id !== undefined) {
            newEntityProperties.parentID = parent.id;
        }
        // Fix up paths
        if (entityProperties.modelURL !== undefined) {
            newEntityProperties.modelURL = fixLocalPath(newEntityProperties.modelURL);
        }
        if (entityProperties.script !== undefined) {
            newEntityProperties.script = fixLocalPath(newEntityProperties.script);
        }
        newEntityProperties.id = Entities.addEntity(newEntityProperties);
        return newEntityProperties;
    };

    var createEntitiesFromTree = function(entityTree, parent, overrideProperties) {
        if (parent === undefined) {
            parent = {};
        }
        if (parent.overrideProperties !== undefined) {
            overrideProperties = parent.overrideProperties;
        }
        var createdTree = [];
        entityTree.forEach(function(entityProperties) {
            var sanitizedProperties = {};
            Object.keys(entityProperties).forEach(function(propertyKey) {
                if (!entityProperties.hasOwnProperty(propertyKey) || SANITIZE_PROPERTIES.indexOf(propertyKey) !== -1) {
                    return true;
                }
                sanitizedProperties[propertyKey] = entityProperties[propertyKey];
            });

            // Allow for non-entity parent objects, this allows us to offset groups of entities to a specific position/rotation
            var parentProperties = sanitizedProperties;
            if (entityProperties.type !== undefined) {
                parentProperties = createEntity(sanitizedProperties, parent, overrideProperties);
            }
            if (entityProperties.childEntities !== undefined) {
                parentProperties.childEntities =
                    createEntitiesFromTree(entityProperties.childEntities, parentProperties, overrideProperties);
            }
            createdTree.push(parentProperties);
        });
        return createdTree;
    };

    // listens for a release message from entities with the snap to grid script
    // checks for the nearest snap point and sends a message back to the entity
    function ImportGamePiece(url, spawnLocation, spawnRotation) {
        var fullURL = Script.resolvePath(url);
        print('CREATE PastedItem FROM SPAWNER: ' + fullURL);
        var _created = [];

        function create() {
            var entitiesTree;
            if (entityJSONCache[fullURL]) {
                entitiesTree = entityJSONCache[fullURL];
            } else {
                entitiesTree = importEntitiesJSON(fullURL);
                entityJSONCache[fullURL] = entitiesTree;
            }

            entitiesTree.position = spawnLocation;
            entitiesTree.rotation = spawnRotation;
            // print('entityTree: ' + JSON.stringify(entitiesTree));

            // var entities = importEntitiesJSON(fullURL);
            // var success = Clipboard.importEntities(fullURL);
            // var dimensions = Clipboard.getContentsDimensions();
            // we want the bottom of any piece to actually be on the board, so we add half of the height of the piece to the location when we paste it,
            // spawnLocation.y += (0.5 * dimensions.y);
            // if (success === true) {
                // created = Clipboard.pasteEntities(spawnLocation);
            //    this.created = created;
            //    print('created ' + created);
            // }
            _created = createEntitiesFromTree([entitiesTree]);
        }

        function cleanup() {
            _created.forEach(function(obj) {
                print('removing: ' + JSON.stringify(obj));
                Entities.deleteEntity(obj.id);
            });
        }

        create();

        this.cleanup = cleanup;

    }

    function Tile(rowIndex, columnIndex) {
        var side = _this.tableSideSize / _this.game.startingArrangement.length;
        var rightAmount = rowIndex * side;
        rightAmount += (0.5 * side);
        var forwardAmount = columnIndex * side;
        forwardAmount += (0.5 * side);
        var localPosition = {
            x: rightAmount - (_this.matDimensions.x * 0.5),
            y: 0,
            z: forwardAmount - (_this.matDimensions.y * 0.5)
        };
        this.startingPosition = _this.matCenter;

        this.middle = Vec3.sum(this.startingPosition, Vec3.multiplyQbyV(_this.tableRotation, localPosition));

        var splitURL = _this.game.startingArrangement[rowIndex][columnIndex].split(":");
        if (splitURL[0] === '1') {
            this.url = Script.resolvePath(_this.game.pieces[0][splitURL[1]]);
        }
        if (splitURL[0] === '2') {
            this.url = Script.resolvePath(_this.game.pieces[1][splitURL[1]]);
        }
        if (splitURL[0] === 'empty') {
            this.url = 'empty';
        }
    }

    function EntitySpawner() {
        _this = this;
    }

    EntitySpawner.prototype = {
        matDimensions: null,
        matCenter: null,
        matCorner: null,
        tableRotation: null,
        items: [],
        toCleanup: [],
        preload: function(id) {
            print('JBP preload entity spawner');
            _this.entityID = id;
        },
        createSingleEntity: function(url, spawnLocation, spawnRotation) {
            if (url === 'empty') {
                return null;
            }
            var item = new ImportGamePiece(url, spawnLocation, spawnRotation);
            _this.items.push(item);
            return item;
        },
        changeMatPicture: function(mat) {
            var fullURL = Script.resolvePath(_this.game.matURL);
            print('changing mat: ' + fullURL);
            Entities.editEntity(mat, {
                textures: JSON.stringify({
                    Picture: fullURL
                })
            });
        },
        spawnEntities: function(id, params) {
            this.items = [];
            var matEntity = params[1];
            var matProperties = Entities.getEntityProperties(matEntity, ['dimensions', 'position', 'rotation',
                'parentID']);
            var dimensions = Entities.getEntityProperties(matEntity, 'dimensions').dimensions;
            _this.game = JSON.parse(params[0]);
            _this.matDimensions = matProperties.dimensions;
            _this.matCenter = matProperties.position;
            _this.matCorner = {
                x: _this.matCenter.x - (dimensions.x * 0.5),
                y: _this.matCenter.y,
                z: _this.matCenter.z + (dimensions.y * 0.5)
            };
            _this.matRotation = matProperties.rotation;
            var tableID = matProperties.parentID;
            _this.tableRotation = Entities.getEntityProperties(tableID, 'rotation').rotation;
            _this.tableSideSize = dimensions.x;
            _this.changeMatPicture(matEntity);
            if (_this.game.spawnStyle === 'pile') {
                _this.spawnByPile();
            } else if (_this.game.spawnStyle === 'arranged') {
                _this.spawnByArranged();
            }
        },
        spawnByPile: function() {
            var position = Entities.getEntityProperties(_this.entityID, 'position').position;
            for (var i = 0; i < _this.game.howMany; i++) {
                var spawnLocation = {
                    x: position.x,
                    y: position.y - 0.25,
                    z: position.z
                };
                var url;
                if (_this.game.identicalPieces === false) {
                    url = Script.resolvePath(_this.game.pieces[i]);
                } else {
                    url = Script.resolvePath(_this.game.pieces[0]);
                }
                _this.createSingleEntity(url, spawnLocation, _this.tableRotation);
            }
        },
        spawnByArranged: function() {
            // make sure to set userData.gameTable.attachedTo appropriately
            _this.setupGrid();
        },
        createDebugEntity: function(position) {
            return Entities.addEntity({
                type: 'Sphere',
                position: {
                    x: position.x,
                    y: position.y + 0.1,
                    z: position.z
                },
                color: {
                    red: 0,
                    green: 0,
                    blue: 255
                },
                dimensions: {
                    x: 0.1,
                    y: 0.1,
                    z: 0.1
                },
                collisionless: true
            });
        },
        setupGrid: function() {
            _this.tiles = [];

            for (var i = 0; i < _this.game.startingArrangement.length; i++) {
                for (var j = 0; j < _this.game.startingArrangement[i].length; j++) {
                    // print('jbp there is a tile at:: ' + i + "::" + j)
                    var tile = new Tile(i, j);
                    var item = _this.createSingleEntity(tile.url, tile.middle, _this.tableRotation);
                    if (_this.game.hasOwnProperty('snapToGrid') && _this.game.snapToGrid === true) {
                        var anchor = _this.createAnchorEntityAtPoint(tile.middle);
                        if (item !== null) {
                            Entities.editEntity(item, {
                                userData: JSON.stringify({
                                    gameTable: {
                                        attachedTo: anchor
                                    }
                                })
                            });
                        }
                    }

                    _this.tiles.push(tile);
                }
            }
        },
        findMidpoint: function(start, end) {
            var xy = Vec3.sum(start, end);
            return Vec3.multiply(0.5, xy);
        },
        createAnchorEntityAtPoint: function(position) {
            var properties = {
                type: 'Zone',
                name:'Game Table Snap To Grid Anchor',
                description: 'hifi:gameTable:anchor',
                visible: false,
                collisionless: true,
                dimensions: {
                    x: 0.075,
                    y: 0.075,
                    z: 0.075
                },
                parentID: _this.entityID,
                position: position,
                userData: 'available'
            };
            return Entities.addEntity(properties);
        },
        setCurrentUserData: function(data) {
            var userData = _this.getCurrentUserData();
            userData.gameTableData = data;
            Entities.editEntity(_this.entityID, {
                userData: userData
            });
        },
        getCurrentUserData: function() {
            var userData = Entities.getEntityProperties(_this.entityID).userData;
            try {
                return JSON.parse(userData);
            } catch (e) {
                // e
            }
            return null;
        },
        cleanupEntitiesList: function() {
            _this.items.forEach(function(item) {
                item.cleanup();
            });
        },
        unload: function() {
            _this.toCleanup.forEach(function(item) {
                Entities.deleteEntity(item);
            });
        }
    };
    return new EntitySpawner();
});