/*
 * Copyright 2019. Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.apps.playgames.simpleengine.game;

import com.google.android.apps.playgames.simpleengine.Renderer;
import java.util.ArrayList;
import java.util.Arrays;

// this class is final for performance reasons
public final class GameObject {

    private World mWorld;

    // position, velocity, acceleration
    public float x = 0.0f;
    public float y = 0.0f;
    public float velX = 0.0f;
    public float velY = 0.0f;
    public float accX = 0.0f;
    public float accY = 0.0f;

    // collides?
    public boolean collides = false;

    // if it collides, width and height of collider box (centered on x,y)
    public float collBoxWidth = 0.0f;
    public float collBoxHeight = 0.0f;

    // type -- the meaning of this is up to the game developer
    public int type = 0;

    // for developer use
    public int ivar[] = new int[16];
    public float fvar[] = new float[16];
    public boolean bvar[] = new boolean[16];

    // flag that indicates that this GameObject should be deleted asap
    public boolean dead = false;

    // countdown to this object's death
    public float timeToLive = Float.POSITIVE_INFINITY;

    // sprites
    public ArrayList<Renderer.Sprite> mSprites = new ArrayList<Renderer.Sprite>();

    // texture that shows collider size (for debug purposes)
    private int mColliderSpriteIdx = -1;

    public final void update(float deltaT) {
        if (dead) {
            return;
        }
        if ((timeToLive -= deltaT) <= 0.0f) {
            dead = true;
            return;
        }

        velX += accX * deltaT;
        velY += accY * deltaT;
        displaceBy(velX * deltaT, velY * deltaT);

        if (mColliderSpriteIdx >= 0) {
            mSprites.get(mColliderSpriteIdx).enabled = (System.currentTimeMillis() % 200) < 100;
        }
    }

    // only created by the World (if you want a GameObject, ask the World for one)
    GameObject(World world) {
        mWorld = world;
        clear();
    }

    // only called by the World
    void clear() {
        deleteSprites();
        dead = false;
        timeToLive = Float.POSITIVE_INFINITY;
        x = y = velX = velY = accX = accY = 0.0f;
        collides = false;
        collBoxHeight = collBoxWidth = 0.0f;
        type = 0;
        Arrays.fill(ivar, 0);
        Arrays.fill(fvar, 0.0f);
        Arrays.fill(bvar, false);
    }

    public void displaceBy(float dx, float dy) {
        x += dx;
        y += dy;

        // displace sprites
        int i;
        for (i = 0; i < mSprites.size(); i++) {
            mSprites.get(i).x += dx;
            mSprites.get(i).y += dy;
        }
    }

    public void displaceTo(float toX, float toY) {
        displaceBy(toX - x, toY - y);
    }

    public void displaceTowards(float targetX, float targetY, float maxDisplacement) {
        if (distanceTo(targetX, targetY) <= maxDisplacement) {
            displaceTo(targetX, targetY);
        } else {
            float velX = targetX - x;
            float velY = targetY - y;
            float modulus = (float) Math.sqrt(velX * velX + velY * velY);
            displaceBy(velX * maxDisplacement / modulus, velY * maxDisplacement / modulus);
        }
    }

    public float distanceTo(float x, float y) {
        return (float) Math.sqrt((this.x - x) * (this.x - x) + (this.y - y) * (this.y - y));
    }

    public int addSprite() {
        // create renderer sprite
        Renderer.Sprite s = mWorld.getRenderer().createSprite();
        s.x = x;
        s.y = y;
        mSprites.add(s);
        return mSprites.size() - 1;
    }

    public Renderer.Sprite getSprite(int i) {
        return i >= 0 && i < mSprites.size() ? mSprites.get(i) : null;
    }

    public int getSpriteCount() {
        return mSprites.size();
    }

    public void deleteSprites() {
        int i;
        for (i = 0; i < mSprites.size(); i++) {
            mWorld.getRenderer().deleteSprite(mSprites.get(i));
        }
        mSprites.clear();
    }

    public void setBoxCollider(float width, float height) {
        collBoxHeight = height;
        collBoxWidth = width;
        collides = true;
    }

    public void debugShowCollider() {
        Renderer.Sprite s = getSprite(mColliderSpriteIdx = addSprite());
        s.x = x;
        s.y = y;
        s.width = collBoxWidth;
        s.height = collBoxHeight;
        s.color = 0xffff0000;
        s.tintFactor = 1.0f;
        s.texIndex = -1;
    }

    public void hide() {
        show(false);
    }

    public void show() {
        show(true);
    }

    public void show(boolean show) {
        int i;
        for (i = 0; i < mSprites.size(); i++) {
            Renderer.Sprite s = mSprites.get(i);
            s.enabled = show;
        }
    }

    public void bringToFront() {
        int i;
        for (i = 0; i < mSprites.size(); i++) {
            mWorld.getRenderer().bringToFront(mSprites.get(i));
        }
    }

    public void sendToBack() {
        int i;
        for (i = 0; i < mSprites.size(); i++) {
            mWorld.getRenderer().sendToBack(mSprites.get(i));
        }
    }
}
