/********************************************************************
 *  Nomes: Gabriel Capella                       Números USP: 8962078 
 *         João Herique Luciano                               8535957
 * 
 *  Tarefa:    RedCore - EP2 MAC0463
 *  Arquivo:   GameScene.cpp
 *  Descrição: Implementação do cenário e do mecanismo de jogo
 ********************************************************************/

#include "GameScene.h"
#include "SimpleAudioEngine.h"
#include "GameScene/BlocksLayer.h"
#include "GameScene/Ball.h"
#include "GameScene/Paddle.h"
#include "params.h"

USING_NS_CC;

Scene* GameScene::createScene(int level) {
    auto scene = Scene::createWithPhysics();
    Size visibleSize = Director::getInstance()->getVisibleSize();
    
#if DEBUG
    scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
#endif
    
    GameScene* layer = GameScene::create();
    scene->addChild(layer);
    
    // parede da tela
    auto body = PhysicsBody::createEdgeBox(visibleSize, PhysicsMaterial(0.0f, 1.0f, 0.0f));
    auto edgeNode = Node::create();
    edgeNode->setPosition(Point(visibleSize.width/2,visibleSize.height/2));
    body->setDynamic(false);
    edgeNode->setPhysicsBody(body);
    scene->addChild(edgeNode);
    
    // parede de baixo
    auto bottom_body = PhysicsBody::createEdgeBox(Size(visibleSize.width, 0));
    auto bottomNode = Node::create();
    bottomNode->setPosition(Point(visibleSize.width/2, 0));
    bottom_body->setDynamic(false);
    bottomNode->setTag(BOTTOM_TAG);
    bottom_body->setContactTestBitmask(0xFFFFFFFF);
    bottomNode->setPhysicsBody(bottom_body);
    layer->addChild(bottomNode);
    
    auto blocks = BlocksLayer::create();
    blocks->setLevel(level);
    blocks->setPosition(visibleSize.width/2, visibleSize.height);
    layer->addChild(blocks, 20);
    
    layer->setLevel(level);
    
    // inicializa os callbacks dos power ups/downs
    auto delay_save_level = DelayTime::create(10.0);
    CallFunc *runCallback_save_level = CallFunc::create(CC_CALLBACK_0(GameScene::saveLevel, layer));
    auto seq_save_level = Sequence::create(delay_save_level, runCallback_save_level, nullptr);
    
    CallFunc *runCallback_triple_balls = CallFunc::create(CC_CALLBACK_0(GameScene::tripleBallsAppearance, layer));
    CallFunc *runCallback_paddle_ball = CallFunc::create(CC_CALLBACK_0(GameScene::paddleBallAppearance, layer));
    CallFunc *runCallback_superball = CallFunc::create(CC_CALLBACK_0(GameScene::superBallAppearance, layer));
    
    layer->runAction(seq_save_level);
    layer->runAction(runCallback_triple_balls);
    layer->runAction(runCallback_paddle_ball);
    layer->runAction(runCallback_superball);
    
    layer->addAndThrowBall();
    
    return scene;
}

bool GameScene::init() {
    
    // 1. super init first
    if ( !Layer::init() ) {
        return false;
    }
    
    over = false;
    last_touch = time(NULL);
    
    FileUtils::getInstance()->addSearchPath("res");
    auto audio = CocosDenshion::SimpleAudioEngine::getInstance();
    audio->preloadEffect("pipe.wav");
    audio->preloadEffect("metal.wav");
    audio->preloadEffect("bomb.wav");
    audio->preloadEffect("win.wav");
    
    auto visibleSize = Director::getInstance()->getVisibleSize();
    width = visibleSize.width;
    height = visibleSize.height;
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    
    // Adiciona o fundo
    auto bg = LayerColor::create(COLOR_back);
    addChild(bg);
    
    paddle = Paddle::create();
    paddle->setPositionX(width/2);
    paddle->listen(width);
    addChild(paddle);
    
    // Registra evento de contato de objetos
    auto contactListener = EventListenerPhysicsContact::create();
    contactListener->onContactBegin = CC_CALLBACK_1(GameScene::onContactBegin, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);
    
    balls = Node::create();
    addChild(balls, 21);
    
    power_ups = Node::create();
    addChild(power_ups, 20);
    
    return true;
}

void GameScene::addAndThrowBall() {
    Ball* ball = Ball::create();
    while (paddle->getPositionX() == 0.0);
    ball->setPosition(paddle->getPositionX(), PADDLE_ALTURA+BALL_SIZE+0.1);
    ball->throwBall();
    balls->addChild(ball, 21);
}

bool GameScene::onContactBegin(PhysicsContact& contact) {
    auto nodeA = contact.getShapeA()->getBody()->getNode();
    auto nodeB = contact.getShapeB()->getBody()->getNode();
    
    if (nodeA && nodeB && nodeA->getTag() != nodeB->getTag() && !over) {
        
        if (nodeB->getTag() > nodeA->getTag()) {
            auto tmp = nodeB;
            nodeB = nodeA;
            nodeA = tmp;
        }
        
        if (nodeA->getTag() == BALL_TAG) {
            last_touch = time(NULL);
        }
        
        // sempre B < A
        if (nodeB->getTag() == BLOCK_TAG) {
            auto audio = CocosDenshion::SimpleAudioEngine::getInstance();
            audio->playEffect("pipe.wav");
            nodeB->removeFromParentAndCleanup(true);
        } else if (nodeB->getTag() == INDESTRUCTIBLE_BLOCK_TAG) {
            if (nodeA->getTag() == BALL_TAG) {
                Ball* ball = (Ball*) nodeA;
                if (ball->superball) {
                    nodeB->removeFromParentAndCleanup(true);
                    ball->resetNormalball();
                }
            }
            auto audio = CocosDenshion::SimpleAudioEngine::getInstance();
            audio->playEffect("metal.wav");
        } else if (nodeB->getTag() == BOTTOM_TAG && nodeA->getTag() == BALL_TAG) {
            caseBallCollision(nodeA);
        }  else if (nodeB->getTag() == CORE_TAG && nodeA->getTag() == BALL_TAG) {
            caseBallCore(nodeB, nodeA);
        }  else if (nodeB->getTag() == BALL_TAG && nodeA->getTag() == CORE_TAG) {
            caseBallCore(nodeA, nodeB);
        }  else if (nodeB->getTag() == SAVE_TAG && nodeA->getTag() == PADDLE_TAG) {
            caseSaveLevel(nodeB);
        } else if (nodeB->getTag() == THREE_BALLS_TAG && nodeA->getTag() == PADDLE_TAG) {
            caseTripleBalls(nodeB);
        } else if (nodeB->getTag() == SUPERBALL_TAG && nodeA->getTag() == PADDLE_TAG) {
            caseSuperBall(nodeB);
        } else if (nodeB->getTag() == PADDLE_BALL_TAG && nodeA->getTag() == PADDLE_TAG) {
            caseRaqueteBall(nodeB);
        } else if (nodeB->getTag() == SAVE_TAG && nodeA->getTag() == BOTTOM_TAG) {
            nodeB->removeFromParentAndCleanup(true);
        } else if (nodeB->getTag() == THREE_BALLS_TAG && nodeA->getTag() == BOTTOM_TAG) {
            nodeB->removeFromParentAndCleanup(true);
        } else if (nodeB->getTag() == PADDLE_BALL_TAG && nodeA->getTag() == BOTTOM_TAG) {
            nodeB->removeFromParentAndCleanup(true);
        } else if (nodeB->getTag() == SUPERBALL_TAG && nodeA->getTag() == BOTTOM_TAG) {
            nodeB->removeFromParentAndCleanup(true);
        }
        
    }
    return true;
}

void GameScene::caseBallCollision (Node *ball) {
    
    auto audio = CocosDenshion::SimpleAudioEngine::getInstance();
    audio->playEffect("bomb.wav");
    ParticleSun* m_emitter = ParticleSun::create();
    m_emitter->setPosition(ball->getPosition());
    m_emitter->setDuration(1);
    addChild(m_emitter);
    ball->removeFromParentAndCleanup(true);
    
    if (balls->getChildrenCount() == 0) {
        over = true;
        removeBallsAndPowersUP ();
        auto text = Label::createWithTTF(MSG_OVER, FONT, 40);
        text->setPosition(width/2, height/2);
        addChild(text);
        auto menu_item_start = MenuItemFont::create(MSG_RESTART, CC_CALLBACK_0(GameScene::nextLevel, this));
        menu_item_start->setFontNameObj(FONT);
        menu_item_start->setPosition(text->getPosition());
        menu_item_start->setPositionY(menu_item_start->getPositionY()-50);
        auto *menu = Menu::create(menu_item_start, NULL);
        menu->setPosition(Point(0, 0));
        addChild(menu, 30);
    }
}

void GameScene::caseBallCore (Node *core, Node *ball) {
    auto scaleBy = ScaleBy::create(1.0f, 20.0f);
    core->getPhysicsBody()->setEnabled(false);
    core->runAction(scaleBy);
    auto audio = CocosDenshion::SimpleAudioEngine::getInstance();
    audio->playEffect("win.wav");
    
    auto callbackRotate = CallFunc::create([=](){
        level = level + 1;
        auto menu_item_start = MenuItemFont::create(MSG_NEXT_LEVEL, CC_CALLBACK_0(GameScene::nextLevel, this));
        menu_item_start->setFontNameObj(FONT);
        menu_item_start->setPosition(Point(width / 2, (height / 2)));
        auto *menu = Menu::create(menu_item_start, NULL);
        menu->setPosition(Point(0, 0));
        addChild(menu, 30);
    });
    
    // cria uma sequencia com as ações e callbacks
    auto seq = Sequence::create(scaleBy, callbackRotate, nullptr);
    core->runAction(seq);
    removeBallsAndPowersUP();
}

void GameScene::caseSaveLevel(Node *powerup_ball) {
    UserDefault *userdata = UserDefault::getInstance();
    userdata->setIntegerForKey("level", level);
    userdata->setDoubleForKey("time", (double) time(NULL));
    userdata->flush();
    alert(MSG_LEVEL_SAVED);
    powerup_ball->removeFromParentAndCleanup(true);
}

void GameScene::nextLevel() {
    auto scene = GameScene::createScene(level);
    Director::getInstance()->replaceScene(scene);
}

void GameScene::setLevel(int level) {
    this->level = level;
    char level_text[256];
    sprintf(level_text,"Level %d", level);
    auto text = Label::createWithTTF(level_text, FONT, 30);
    text->setAnchorPoint(Vec2());
    text->setPosition(10, 10);
    addChild(text);
}

void GameScene::saveLevel() {
    if (over) return;
    
    if (rand_0_1() < DROP_LEVEL_SAVE)
        createPowerUpBody(COLOR_green, SAVE_TAG);

    delayCallback(CC_CALLBACK_0(GameScene::saveLevel, this));
}

void GameScene::tripleBallsAppearance() {
    if (over) return;
    double delta = (time(NULL)-last_touch);
    
    if (rand_0_1() < delta*DROP_TRIPLE_BALL ){
        last_touch = time(NULL);
        if (balls->getChildrenCount() < 3)
            createPowerUpBody(COLOR_pink, THREE_BALLS_TAG);
    }
    
    delayCallback(CC_CALLBACK_0(GameScene::tripleBallsAppearance, this));
}

void GameScene::caseTripleBalls(Node *powerup_ball) {
    alert(MSG_TRIPE_BALLS);
    while (!over && balls->getChildrenCount() < 3)
        addAndThrowBall();
    powerup_ball->removeFromParentAndCleanup(true);
}

void GameScene::paddleBallAppearance() {
    if (over) return;
    
    if (rand_0_1() < DROP_PADDLE_BALL && over == false)
        createPowerUpBody(COLOR_blue, PADDLE_BALL_TAG);
    
    delayCallback(CC_CALLBACK_0(GameScene::paddleBallAppearance, this));
}

void GameScene::casePaddleBall(Node *powerup_ball) {
    if (!over) {
        if (rand_0_1() < 0.5) {
            alert(MSG_DOUBLE_PADDLE);
            paddle->doubleSize();
        } else {
            alert(MSG_HALF_PADDLE);
            paddle->halfSize();
        }
    }
    powerup_ball->removeFromParentAndCleanup(true);
}

void GameScene::alert(std::string text) {
    if (!over) {
        auto display_label = Label::createWithTTF(text, FONT, 40);
        display_label->setPosition(width/2, height/2);
        addChild(display_label);
        display_label->runAction(FadeOut::create(3));
    }
}

void GameScene::superBallAppearance() {
    if (over) return;
    
    if (rand_0_1() < DROP_SUPER_BALL)
        createPowerUpBody(COLOR_orange, SUPERBALL_TAG);
    
    delayCallback(CC_CALLBACK_0(GameScene::superBallAppearance, this));
}

void GameScene::caseSuperBall(Node *powerup_ball) {
    if (over) return;
    alert(MSG_SUPER_BALL);
    Vector<Node *>  balls_vector = balls->getChildren();
    Ball* ball = (Ball*) balls_vector.at(0);
    ball->setSuperball();
    powerup_ball->removeFromParentAndCleanup(true);
}

void GameScene::removeBallsAndPowersUP () {
    balls->removeAllChildrenWithCleanup(true);
    power_ups->removeAllChildrenWithCleanup(true);
    paddle->removeFromParentAndCleanup(true);
}

void GameScene::createPowerUpBody(Color4B color, int tag) {
    auto ball_draw = DrawNode::create();
    ball_draw->drawDot(Vec2(0, 0), BALL_SIZE/3.0, Color4F(color));
    
    auto material = PhysicsMaterial(0.0f, 1.0f, 0.0f);
    auto physicsBody = PhysicsBody::createCircle(BALL_SIZE/3.0, material);
    physicsBody->setGravityEnable(true);
    physicsBody->setVelocity(Vec2(0,0));
    physicsBody->setLinearDamping(0.0);
    physicsBody->setMass(1.0f);
    physicsBody->setContactTestBitmask(0xFFFFFFFF);
    physicsBody->setGroup(-1);
    ball_draw->addComponent(physicsBody);
    ball_draw->setTag(tag);
    
    ball_draw->setPosition(width * rand_0_1(), height);
    power_ups->addChild(ball_draw, 20);
}

void GameScene::delayCallback(const std::function<void ()> &func) {
    auto delta = POWER_UP_INTERVAL/4.0 * rand_minus1_1();
    auto delay = DelayTime::create(POWER_UP_INTERVAL+delta);
    CallFunc *runCallback = CallFunc::create(func);
    auto seq = Sequence::create(delay, runCallback, nullptr);
    runAction(seq);
}