<?php

namespace bedwars\game;

use pocketmine\Server;
use pocketmine\Player;
use pocketmine\utils\Config;

use pocketmine\entity\Entity;
use pocketmine\entity\Human;
use pocketmine\entity\FallingSand;

use pocketmine\level\particle\FlameParticle;
use pocketmine\level\particle\HeartParticle;
use pocketmine\level\particle\PortalParticle;
use pocketmine\level\particle\RedstoneParticle;
use pocketmine\math\Vector3;
use pocketmine\level\Position;

use pocketmine\level\Level;
use pocketmine\item\Item;
use pocketmine\block\Block;
use pocketmine\network\protocol\AddPlayerPacket;
use pocketmine\network\protocol\AddEntityPacket;
use pocketmine\network\protocol\AddItemEntityPacket;
use pocketmine\network\protocol\SetEntityDataPacket;

use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\DoubleTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\FloatTag;

use pocketmine\utils\Color;

use pocketmine\utils\UUID;
use ZipArchive;
use pocketmine\event\entity\EntityDamageEvent;

use bedwars\BedWars;
use bedwars\util\Bed;
use bedwars\util\Team;
use bedwars\util\Generetor;
use bedwars\entity\FireBall;
use bedwars\entity\Shop;
use bedwars\entity\upgrade;
use bedwars\entity\VillagerNPC;
use bedwars\entity\BedText;
use bedwars\entity\GeneretorText;
use bedwars\entity\IconBlock;

class BasicGame {
    
    const MODE_COUNT = array(
        "solo" => 1, 
        "duo" => 2, 
        "squad" => 4
    );
    
    const error = array(
        0 => "§cSorry but this game has already started!", 
        1 => "§cSorry but this game is full!",
        2 => "§cYou have joined the party"
    );
    
    /** @var int */
    public $index;
    
    /** @var string */
    public $name;
    
    /** @var string */
    public $mode;
    
    /** @var string */
    public $world;
    
    /** @var array */
    public $hub;
    
    /** @var array */
    public $players;
    
    /** @var array */
    public $espects;
    
    /** @var array */
    public $armors;
    
    /** @var array */
    public $respawn;
    
    /** @var array */
    public $upgrade;
    
    /** @var array */
    public $placedBlocks;
    
    /** @var array */
    public $teams;
    
    /** @var array */
    public $team;
    
    /** @var array */
    public $tools;
    
    /** @var string */
    public $message;
    
    /** @var Player */
    public $player;
    
    /** @var int */
    public $time;
    
    /** @var int */
    public $returnTime;
    
    /** @var array */
    public $teamsPath;
    
    /** @var array */
    public $defaultTeams;
    
    /** @var array */
    public $generetors;
    
    private $json;
    
    public $bed;
    public $generetor;
    public $level;

    private $plugin;
    
    public function reset() : bool {
        $this->json = new Config(BedWars::$saves . $this->name . "/" . $this->name . ".js", Config::JSON, array("name" => $this->name, "world" => $this->world, "mode" => $this->mode, "teams" => $this->defaultTeams, "generetors" => $this->generetors, "hub" => $this->hub));
        
        $this->level = Server::getInstance()->getLevelByName($this->world);

        $this->index = 0;
        $this->time = 10;
        $this->players = array();
        $this->espects = array();
        $this->teams = array();
        $this->placedBlocks = array();
        $this->upgrade = array();
        $this->respawn = array();
        $this->tools = array();
        $this->armors = array();
        $this->returnTime = 10;
        $this->message = null;
        $this->player = null;
        $this->team = null;
        
        if ($this->bed instanceof Bed) {
            $this->bed->close();
        }

        if ($this->generetor instanceof Generetor) {
            $this->generetor->close();
        }

        //restos
        $this->clearTrash();

        $this->bed = new Bed($this);
        $this->generetor = new Generetor($this);
        
        if ($this->index > 3) 
            return false;
        return true;
    }
    
    public function getLevel() {
        if (Server::getInstance()->isLevelLoaded($this->world) and !($this->level instanceof Level)) {
            $this->level = Server::getInstance()->getLevelByName($this->world);
        }
        return $this->level;
    }

    public function clearTrash() {
        if ($this->level instanceof Level) {
            foreach ($this->level->getEntities() as $entity) {
                if ($entity instanceof BedText or $entity instanceof GeneretorText or $entity instanceof IconBlock) {
                    $entity->close();
                }
            }
        }
    }

    public function backupMap(bool $copy = false) {
        $world_directory = BedWars::$saves . $this->name . "/" . $this->world . ".zip";
        $path = BedWars::$worlds . "/" . $this->world;
        
        $server = Server::getInstance();
        
        $backup = true;
        
        if ($copy) {
            @mkdir (BedWars::$saves . $this->name, 0755);
            
            if ($backup) {
                $zip = new ZipArchive;
                $zip->open ($world_directory, $zip::CREATE | $zip::OVERWRITE);
                $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator ($path), \RecursiveIteratorIterator::LEAVES_ONLY);
            
                foreach($files as $file) {
                    if (is_file($file)) {
                        $zip->addFile($file, str_replace("\\", "/", ltrim(substr($file, strlen($path)), "/\\")));
                    }
                }
            }
        } else {
            if (!is_dir($path)) {
                @mkdir($path);
            }
            
            if ($server->isLevelLoaded($this->world)) {
                if ($this->getLevel() instanceof Level) {
                    foreach ($this->getLevel()->getPlayers() as $player) {
                        $this->quit($player, true);
                    }
                }
                
                $server->unloadLevel($this->level);
                
                if ($backup) {
                    $zip = new ZipArchive;
                    if ($zip->open($world_directory) === true) {
                        $zip->extractTo($path);
                    }
                    $zip->close();
                }
                
                $server->loadLevel($this->world);
            } else {
                if ($backup) {
                    $zip = new ZipArchive;
                    if ($zip->open($world_directory) === true) {
                        $zip->extractTo($path);
                    }
                    $zip->close();
                }
                
                $server->loadLevel($this->world);
            }
        }
    }
    
    /**
     * @param string $message
     * @param int $type
     */
    public function print(string $message, int $type = 0, bool $break = true) {
        foreach ($this->getPlayers() as $name => $player) {
            $name = $player->getName();
            if (($player == null) or !(isset($this->players[$name])))
                continue;
            
            switch ($type) {
                case 0:
                $player->sendMessage($message);
                break;
            
                case 1:
                $player->sendTip($message);
                break;
                
                case 2:
                $player->sendPopup($message);
                break; 
                
                case 3:
                $lines = "";
                if ($break)
                    $lines = str_repeat("§r\n", 5);
                $player->sendTip($message . $lines);
                break;
                
                case 4:
                break;
            }
        }
    }
    
    /**
    * @param Player
    */
    public function quit(Player $player, $reset = false) {
        if ($reset == false) { 
            $team = $this->getTeam($player->getName());
        
            if ($team instanceof Team) {
                if ($team->removePlayer($player)) {
                    $this->teams[$team->getName()] = $team;
                    $this->print("§r§d" . $this->getNameTag($player) . "§c quit the party");
                }
            }
        }

        unset($this->players[$player->getName()]);
        
        $default = $player->getServer()->getDefaultLevel()->getSafeSpawn();
        
        $player->teleport($default);
        $player->setHealth($player->getMaxHealth());
        $player->setFood(20);
        $player->removeAllEffects();
        $player->spawnToAll();
        $player->sendTip('');
        $player->sendPopup('');
        
    }
    
    /**
     * @return array
     */
    public function getPlayers() : array {
        $hub = $this->getHub();
        if (!($this->level instanceof Level) or !($hub instanceof Level))
            return array();
        
        $players = $this->level->getPlayers();
        foreach ($hub->getPlayers() as $key => $value)
            $players[$key] = $value;
        return $players;
    }
    
    /**
    * @param string $team
    * @param string $type
    */  
    public function getUpgradeLevel(string $team, string $type) : int {
        if (isset($this->upgrade[$team])) {
            if (isset($this->upgrade[$team][$type])) {
                return (int) $this->upgrade[$team][$type];
            }
        }
        return 0;
    }
    
    /**
    * @param string $team
    * @param string $type
    */  
    public function setUpgradeLevel(string $team, string $type, int $value) {
        $this->upgrade[$team][$type] = $value;
    }
    
    /**
    * @param Player $player
    */
    public function addParticle(Player $player) {
        $center = new Vector3($player->getX(), $player->getY() + 3, $player->getZ());
        $radius = 1;
        $particles = array(new FlameParticle($center), new HeartParticle($center), new RedstoneParticle($center), new PortalParticle($center));
        $rand = $particles[array_rand($particles)];
        $particle = $rand;

        for($a = 0; $a < 100; $a++){
            $pitch = (mt_rand() / mt_getrandmax() - 0.5) * M_PI;
            $yaw = mt_rand() / mt_getrandmax() * 2 * M_PI;
            $yi = -sin($pitch);
            $delta = cos($pitch);
            $xi = -sin($yaw) * $delta;
            $zi = cos($yaw) * $delta;
            $vector = new Vector3($xi, $yi, $zi);
            $pi = $center->add($vector->normalize()->multiply($radius));
            $particle->setComponents($pi->x, $pi->y + 0.3, $pi->z);
            $player->getLevel()->addParticle($particle, $player->getLevel()->getPlayers());
        }

        self::addFires($player);
    }

    /**
    * @param Player $player
    */
    public function addFires(Player $player) {
        $x = $player->x;
        $y = $player->y + 5;
        $z = $player->z;

        if (rand(0,1) === 1) {
            $x += 1.3;
        } else {
            $x -= 1.3;
        }

        if (rand(0,1) === 1) {
            $z += 1.3;
        } else {
            $z -= 1.3;
        }

        $nbt = new CompoundTag("", [
            "Pos" => new ListTag("Pos", [
                new DoubleTag(0, $x),
                new DoubleTag(1, $y),
                new DoubleTag(2, $z)
            ]),
            "Motion" => new ListTag("Motion", [
                new DoubleTag(0, 0),
                new DoubleTag(1, 0),
                new DoubleTag(2, 0)
            ]),
            "Rotation" => new ListTag("Rotation", [
                new FloatTag(0, $player->yaw),
                new FloatTag(1, $player->pitch)
            ])
        ]);

        $f = 1.5;
        $projectile = new FireBall($player->chunk, $nbt, $player);
        $projectile->setExplodeTime(15);
        $projectile->setDamage(0);
        $projectile->setBlocks([]);
        $projectile->spawnToAll();
    }
    
    /**
     * @return string
     */
    public function getColor(string $team) : string {
        if (isset($this->defaultTeams[$team])) {
            return "§" . $this->defaultTeams[$team]["color"];
        } else {
            return '';
        }
    }
    
    /**
     * @return string
     */
    public function getNameTag(Player $player) : string {
        $team = $this->getTeam($player->getName());
        
        if ($team === null) 
            return $player->getNameTag();
        return $this->getColor($team->getName()) . "[" . ucfirst($team->getName(){0}) . "]§r " . $player->getNameTag();
    }
    
    /**
     * @return int|string
     */
    public function getDamage(string $team) {
        $list = array(
            "1" => 11,
            "2" => 13,
            "3" => 9,
            "4" => 14,
            "5" => 10,
            "6" => 1,
            "7" => 8,
            "8" => 7,
            "9" => 11,
            "0" => 15,
            "a" => 13,
            "b" => 5,
            "c" => 14,
            "d" => 6,
            "e" => 4,
            "f" => 0
        );
        if (isset($this->defaultTeams[$team])) {
            $color = $this->defaultTeams[$team]["color"];
            if (isset($list[$color])) {
                return $list[$color];
            }
        }
        return "f";
    }
    
    /**
     * @return int|string
     */
    public function getDyeColor(string $team) {
        $list = array(
            "1" => 4,
            "2" => 2,
            "3" => 6,
            "4" => 1,
            "5" => 10,
            "6" => 14,
            "7" => 8,
            "8" => 7,
            "9" => 12,
            "0" => 0,
            "a" => 2,
            "b" => 9,
            "c" => 1,
            "d" => 6,
            "e" => 11,
            "f" => 15
        );
        if (isset($this->defaultTeams[$team])) {
            $color = $this->defaultTeams[$team]["color"];
            if (isset($list[$color])) {
                return Color::getDyeColor($list[$color]);
            }
        }
        return Color::getDyeColor(15);
    }
    
    /**
     * @return Level|null
     */
    public function getHub() {
        if (count($this->hub) < 3)
            return null;
        
        $level = Server::getInstance()->getLevelByName($this->hub[3]);
        
        if ($level instanceof Level) {
            return $level;
        }
        return null;
    }
    
    /**
     * @param string $name
     */
    public function getTeam(string $name) {
        if (isset($this->players[$name])) {
            $team = $this->players[$name];

            if (isset($this->teams[$team])) {
                $teamData = $this->teams[$team];

                if ($teamData instanceof Team) {
                    return $teamData;
                }
            }
        }
        return null;
    }
    
    /**
     * @return Bed
     */
    public function getBed() {
        return $this->bed;
    }
        
    /**
     * @return Generetor
    */
    public function getGeneretor() {
        return $this->generetors;
    }
    
    /**
     * @param string $name
     */
    public function setName(string $name) {
        $this->name = (string) $name;
    }
    
    /** 
     * @param string $mode
     */
    public function setMode(string $mode) {
        $this->mode = (string) $mode;
    }
    
    /**
     * @param string $worldName
     */
    public function setWorld(string $worldName) {
        $this->world = (string) $worldName;
    }
    
    /**
     * @param array|null $args
    */
    public function setHub(array $args) {
        if (count($args) >= 3) {
            $this->hub = $args;

            if ($this->json != null) {
                $this->json->set("hub", $args);
                $this->json->save(true);
            }
        } 
    }
    
    /**
     * @param array $defaultTeams
     */
    public function setDefaultTeams(array $defaultTeams) {
        $this->defaultTeams = (array) $defaultTeams;
    }
    
    /**
     * @param array $args
     */
    public function setGeneretors(array $args) {
        $this->generetors = (array) $args; 
    }
    
    /**
     * @return string
     */
    public function getName() : string {
        return $this->name;
    }
    
    /** 
     * @return string
     */
    public function getMode() : string {
        return $this->mode;
    }
    
    /**
     * @return string
     */
    public function getWorld() : string {
        return $this->world;
    }
    
    /**
     * @return array
     */
    public function getDefaultTeams() : array {
        return $this->defaultTeams;
    }
    
    /**
     * @param string $team
     * @param string $type
     * @param string $position
     */
    public function setPosition(string $team, string $type, string $position, $pos = null) {
        if ($pos !== null) {
            $generetors = $this->json->get("generetors");
            $generetors[$team][$pos] = $position;
            
            $this->generetors = $generetors;
            $this->json->set("generetors", $generetors);
            $this->json->save(true);
        } else {
            $this->defaultTeams[$team][$type] = $position;
            
            $this->json->set("teams", $this->defaultTeams);
            $this->json->save(true);
        }
    
    }
    
    /**
     * @return int
     */
    public function getSlot(bool $value = false) {
        $count = count($this->players);

        if ($value == false) {
            $count = (int) count($this->defaultTeams);
        
            if (isset(self::MODE_COUNT[$this->mode])) {
                $count *= self::MODE_COUNT[$this->mode];
            }
        }

        return $count;
    }
    
}
?>
