#include "MainGamePanel.hpp"
#include "../uiElements/ImagePanel.hpp"
#include "../GameController.hpp"

#include <map>

#include <chrono>
#include <thread>


MainGamePanel::MainGamePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(960, 680)) {
}

void MainGamePanel::buildGameState(game_state* gameState, player* me) {

    // remove any existing UI
    this->DestroyChildren();

    wxColor green = wxColor(50, 168, 54);
    this->SetBackgroundColour(green);

    std::vector<player*> players = gameState->get_players();
    int numberOfPlayers = players.size();

    // find our own player object in the list of players
    int myPosition = -1;
    std::vector<player*>::iterator it = std::find_if(players.begin(), players.end(), [me](const player* x) {
        return x->get_id() == me->get_id();
    });
    if (it < players.end()) {
        me = *it;
        myPosition = it - players.begin();
    } else {
        GameController::showError("Game state error", "Could not find this player among players of server game.");
        return;
    }

    double anglePerPlayer = MainGamePanel::twoPi / (double) numberOfPlayers;

    // show all other players
    for(int i = 1; i < numberOfPlayers; i++) {
        // get player at i-th position after myself
        player* otherPlayer = players.at((myPosition + i) % numberOfPlayers);

        double playerAngle = (double) i * anglePerPlayer;
        int side = (2 * i) - numberOfPlayers; // side < 0 => right, side == 0 => center, side > 0 => left

        this->buildOthers(gameState, otherPlayer, playerAngle, side);
    }

    bool am_broke = me->get_money() + me->get_bet_size() < gameState->_min_bet;
    if (!am_broke) {
      // show our own player
      this->buildThisPlayer(gameState, me);
    } else {
      this->buildThisPlayerBroke();
    }

    // show the number of rounds
    this->buildRoundCounter(gameState);

    // show cards of dealer
    this->buildDealer(gameState);

    // update layout
    this->Layout();
}




// Places labels and images of all opposed players onto the GUI.
void MainGamePanel::buildOthers(game_state* gameState, player* otherPlayer, double playerAngle, int side) {

    std::string err;
    long textAlignment = wxALIGN_CENTER;
    int labelOffsetX = 0;
    int betOffsetX = 0;

    if (side < 0) { // right side
        textAlignment = wxALIGN_LEFT;
        labelOffsetX = 70;
        betOffsetX = 20;
    } else if(side > 0) { // left side
        textAlignment = wxALIGN_RIGHT;
        labelOffsetX = -10;
        betOffsetX = 40;
    }


    // define the ellipse which represents the virtual player circle
    double verticalRadius_hand = MainGamePanel::otherPlayerHandDistanceFromCenter;
    double verticalRadius_label = MainGamePanel::otherPlayerLabelDistanceFromCenter;
    double verticalRadius_bet = MainGamePanel::otherPlayerBetDistanceFromCenter;

    // get player's position on that ellipse
    wxPoint handPosition = MainGamePanel::tableCenter;
    wxPoint labelPosition = MainGamePanel::tableCenter;
    wxPoint betPosition = MainGamePanel::tableCenter;

    handPosition  +=    this->getPointOnEllipse(verticalRadius_hand*1.3,  verticalRadius_hand,  playerAngle);
    labelPosition +=    this->getPointOnEllipse(verticalRadius_label*1.4, verticalRadius_label, playerAngle);
    betPosition   +=    this->getPointOnEllipse(verticalRadius_bet*1.9,   verticalRadius_bet,   playerAngle);
    labelPosition += wxSize(labelOffsetX, 0);


    // LABEL(name)
    this->buildStaticText(
            otherPlayer->get_player_name(),
            labelPosition + wxSize(-100, -36),
            wxSize(200, 18),
            textAlignment,
            true
    );

    // LABEL(status)
    bool player_is_broke = otherPlayer->get_money()+otherPlayer->get_bet_size() < gameState->_min_bet;
    if(!gameState->is_started()) {
        
        this->buildStaticText(
                "waiting for game to start...",
                labelPosition + wxSize(-100, -18),
                wxSize(200, 18),
                textAlignment
        );
    }
    else if(player_is_broke){
        this->buildStaticText(
                "Status: LOST GAME",
                labelPosition + wxSize(-100, -18),
                wxSize(200, 18),
                textAlignment
        );
    }
    else{
        std::string status_message = "Status: ";

        if(otherPlayer->get_hand()->is_over_21(err)) 
            status_message += "LOST ROUND" ;
        else if(gameState->everyone_finished()){
            std::string err;
            int pp = otherPlayer->get_hand()->get_points(err);
            int dp = gameState->get_dealers_hand()->get_points(err);
            if(dp>21 || pp > dp )   status_message += "WON ROUND";
            else if(pp < dp)             status_message += "LOST ROUND";
            else                status_message += "DRAW WITH DEALER";

        }
        else if(gameState->get_current_player() == otherPlayer && !gameState->everyone_finished()) 
            status_message += "PLAYING";
        else
            status_message += "WAITING";
        
        this->buildStaticText(
                    status_message,
                    labelPosition + wxSize(-100, -18),
                    wxSize(200, 18),
                    textAlignment
            );


        // LABEL(money)
        this->buildStaticText(
                "Money:" + std::to_string(otherPlayer->get_money()) + "$",
                labelPosition + wxSize(-100, 0),
                wxSize(200, 18),
                textAlignment
        );
        // LABEL(bet)
        this->buildStaticText(
                "Bet:" + std::to_string(otherPlayer->get_bet_size()) + "$",
                labelPosition + wxSize(-100, 18),
                wxSize(200, 18),
                textAlignment
        );


        //HAND(cards) IMAGE
        int numberOfCards = otherPlayer->get_hand()->get_nof_cards();
        std::string cardImage;
        wxSize weirdSize(76, 90);
        double cAngle = playerAngle + MainGamePanel::twoPi/4;
        int cDist = MainGamePanel::otherPlayerHandSize;
        wxSize card_dist((int)(sin(cAngle)*cDist), (int)(cos(cAngle) * cDist));
        handPosition -= 0.5*card_dist + 1.5*wxSize(betOffsetX, 0);

        for(int i = 0; i<numberOfCards;++i){
            card *handCard = otherPlayer->get_hand()->get_cards().at(i);
            cardImage = getPngFileName(handCard->get_value(), handCard->get_suit());
            new ImagePanel(this, cardImage, wxBITMAP_TYPE_ANY, handPosition, weirdSize, playerAngle);
            handPosition += card_dist*0.65;
        }

        //BET(chips) IMAGE
        int bet=otherPlayer->get_bet_size();
        wxSize chipSize(25, 25);
        wxSize bet_dist = 0.15*card_dist;
        betPosition -= 4* bet_dist + wxSize(betOffsetX, 0);

        while(bet>=100){
            new ImagePanel(this, "assets/png-chips/gold.png", wxBITMAP_TYPE_ANY, betPosition, chipSize, playerAngle);
            betPosition += bet_dist;
            bet -= 100;
        }
        while(bet>=25){
            new ImagePanel(this, "assets/png-chips/green.png", wxBITMAP_TYPE_ANY, betPosition, chipSize, playerAngle);
            betPosition += bet_dist;
            bet -= 25;
        }
        while(bet>=10){
            new ImagePanel(this, "assets/png-chips/black.png", wxBITMAP_TYPE_ANY, betPosition, chipSize, playerAngle);
            betPosition += bet_dist;
            bet -= 10;
        }
        while(bet>=5){
            new ImagePanel(this, "assets/png-chips/red.png", wxBITMAP_TYPE_ANY, betPosition, chipSize, playerAngle);
            betPosition += bet_dist;
            bet -= 5;
        }
        while(bet>=1){
            new ImagePanel(this, "assets/png-chips/blue.png", wxBITMAP_TYPE_ANY, betPosition, chipSize, playerAngle);
            betPosition += bet_dist;
            bet -= 1;
        }
    }
}



void MainGamePanel::buildThisPlayer(game_state* gameState, player* me) {

    // Setup two nested box sizers, in order to align our player's UI to the bottom center
    wxBoxSizer* outerLayout = new wxBoxSizer(wxHORIZONTAL);
    this->SetSizer(outerLayout);
    wxBoxSizer* innerLayout = new wxBoxSizer(wxVERTICAL);
    outerLayout->Add(innerLayout, 1, wxALIGN_BOTTOM);

    // Show the label with our player name
    wxStaticText* playerName = buildStaticText(
            me->get_player_name(),
            wxDefaultPosition,
            wxSize(200, 18),
            wxALIGN_CENTER,
            true
    );
    innerLayout->Add(playerName, 0, wxALIGN_CENTER);

    // if the game has not yet started we say so
    if(!gameState->is_started()) {

        wxStaticText* playerPoints = buildStaticText(
                "waiting for game to start...",
                wxDefaultPosition,
                wxSize(200, 18),
                wxALIGN_CENTER
        );
        innerLayout->Add(playerPoints, 0, wxALIGN_CENTER | wxBOTTOM, 8);

        // show button that allows our player to start the game
        wxButton* startGameButton = new wxButton(this, wxID_ANY, "Start Game!", wxDefaultPosition, wxSize(160, 64));
        startGameButton->Bind(wxEVT_BUTTON, [](wxCommandEvent& event) {
            GameController::startGame();
        });
        innerLayout->Add(startGameButton, 0, wxALIGN_CENTER | wxBOTTOM, 8);

    } else {

        wxStaticText *playerMoney = buildStaticText(
                 "You currently have " + std::to_string(me->get_money()) + "$",
                 wxDefaultPosition,
                 wxSize(200, 18),
                 wxALIGN_CENTER
                );

        wxStaticText *playerBet;
        if(gameState->everyone_finished()) {
            playerBet = buildStaticText(
                 "Your bet was " + std::to_string(me->get_bet_size()) + "$",
                 wxDefaultPosition,
                 wxSize(200, 18),
                 wxALIGN_CENTER
                );
        } else {
            playerBet = buildStaticText(
                 "Your current bet is " + std::to_string(me->get_bet_size()) + "$",
                 wxDefaultPosition,
                 wxSize(200, 18),
                 wxALIGN_CENTER
                );
        }

        innerLayout->Add(playerMoney, 0, wxALIGN_CENTER | wxBOTTOM, 8);
        innerLayout->Add(playerBet, 0, wxALIGN_CENTER | wxBOTTOM, 8);


        //Display chip according to the betsize of the player
        int bet=me->get_bet_size();
        wxSize chipSize(25, 25);

        wxPoint chipPosition = MainGamePanel::tableCenter + MainGamePanel::chipOffset;
        const wxPoint chipdispl = wxPoint(20,0);

        while(bet>=100){
            new ImagePanel(this, "assets/png-chips/gold.png", wxBITMAP_TYPE_ANY, chipPosition, chipSize, wxALIGN_CENTER);
            chipPosition += chipdispl;
            bet -= 100;
        }
        while(bet>=25){
            new ImagePanel(this, "assets/png-chips/green.png", wxBITMAP_TYPE_ANY, chipPosition, chipSize, wxALIGN_CENTER);
            chipPosition += chipdispl;
            bet -= 25;
        }
        while(bet>=10){
            new ImagePanel(this, "assets/png-chips/black.png", wxBITMAP_TYPE_ANY, chipPosition, chipSize, wxALIGN_CENTER);
            chipPosition += chipdispl;
            bet -= 10;
        }
        while(bet>=5){
            new ImagePanel(this, "assets/png-chips/red.png", wxBITMAP_TYPE_ANY, chipPosition, chipSize, wxALIGN_CENTER);
            chipPosition += chipdispl;
            bet -= 5;
        }
        while(bet>=1){
            new ImagePanel(this, "assets/png-chips/blue.png", wxBITMAP_TYPE_ANY, chipPosition, chipSize, wxALIGN_CENTER);
            chipPosition += chipdispl;
            bet -= 1;
        }

        // if our player already played, we display that as status
        // TODO from has_folded make has_played

        if(gameState->everyone_finished()){
            std::string err;
            std::string status_message;
            int pp = me->get_hand()->get_points(err);
            int dp = gameState->get_dealers_hand()->get_points(err);

            if(me->get_hand()->is_over_21(err))
                                            status_message += "YOU LOST THIS ROUND";
            else if(dp>21 || pp > dp)       status_message += "YOU WON THIS ROUND";
            else if(pp < dp)                status_message += "YOU LOST THIS ROUND";
            else                            status_message += "YOU TIED WITH THE DEALER";

        
            wxStaticText *playerStatus = buildStaticText(
                    status_message,
                    wxDefaultPosition,
                    wxSize(200, 32),
                    wxALIGN_CENTER
            );
            innerLayout->Add(playerStatus, 0, wxALIGN_CENTER | wxBOTTOM, 8);

        }
        else if (me->has_finished_turn()) {
            wxStaticText *playerStatus = buildStaticText(
                    "You finished your turn",
                    wxDefaultPosition,
                    wxSize(200, 32),
                    wxALIGN_CENTER
            );
            innerLayout->Add(playerStatus, 0, wxALIGN_CENTER | wxBOTTOM, 8);
        }

        // if it's our turn, display Hit and Stand button
        if (gameState->get_current_player() == me && !gameState->everyone_finished()) {
            wxButton *hitButton = new wxButton(this, wxID_ANY, "Hit", wxDefaultPosition, wxSize(80, 32));
            hitButton->Bind(wxEVT_BUTTON, [](wxCommandEvent& event) {
                GameController::hit();
            });
            innerLayout->Add(hitButton, 0, wxALIGN_CENTER | wxBOTTOM, 8);

            wxButton *standButton = new wxButton(this, wxID_ANY, "Stand", wxDefaultPosition, wxSize(80, 32));
            standButton->Bind(wxEVT_BUTTON, [](wxCommandEvent& event) {
                GameController::stand();
            });
            innerLayout->Add(standButton, 0, wxALIGN_CENTER | wxBOTTOM, 8);
        // if it's not our turn, display "waiting..."
        } else {
            wxStaticText *playerStatus = buildStaticText(
                    "waiting...",
                    wxDefaultPosition,
                    wxSize(200, 32),
                    wxALIGN_CENTER
            );
            innerLayout->Add(playerStatus, 0, wxALIGN_CENTER | wxBOTTOM, 8);
        }

        // display our player's hand, if we have cards
        int numberOfCards = me->get_hand()->get_nof_cards();
        if (numberOfCards > 0) {

            // create horizontal layout for the individual hand cards of our player
            wxBoxSizer *handLayout = new wxBoxSizer(wxHORIZONTAL);
            innerLayout->Add(handLayout, 0, wxALIGN_CENTER);

            wxSize scaledCardSize = MainGamePanel::cardSize;

            // Adjust card size (if the number of cards does not fit on the screen)
            if (numberOfCards * (MainGamePanel::cardSize.GetWidth() + 8) >
                MainGamePanel::panelSize.GetWidth()) { // 8 -> 4 pixel padding on both sides
                int scaledCardWidth = (MainGamePanel::panelSize.GetWidth() / numberOfCards) - 8;
                double cardAspectRatio =
                        (double) MainGamePanel::cardSize.GetHeight() / (double) MainGamePanel::cardSize.GetWidth();
                int scaledCardHeight = (int) ((double) scaledCardWidth * cardAspectRatio);
                scaledCardSize = wxSize(scaledCardWidth, scaledCardHeight);
            }

            // Show all cards of this player
            for (int i = 0; i < me->get_hand()->get_cards().size(); i++) {

                card *handCard = me->get_hand()->get_cards().at(i);
                std::string cardFile = getPngFileName(handCard->get_value(), handCard->get_suit());
                ImagePanel *cardPanel = new ImagePanel(this, cardFile, wxBITMAP_TYPE_ANY, wxDefaultPosition, scaledCardSize);
                handLayout->Add(cardPanel, 0, wxLEFT | wxRIGHT, 4);
            }
        }
    }
}

void MainGamePanel::buildThisPlayerBroke(){
    wxBoxSizer* outerLayout = new wxBoxSizer(wxHORIZONTAL);
    this->SetSizer(outerLayout);
    //Display text to tell player he has lost
    wxPoint Offset(-100, 200);
    wxPoint textPosition = MainGamePanel::tableCenter + Offset;
    wxStaticText *playerName =
        buildStaticText("You are broke :(", textPosition, wxSize(400, 36),
                        wxALIGN_CENTER, true);
}


void MainGamePanel::buildRoundCounter(game_state* gameState){
  if(gameState->is_started() && gameState->get_current_player() != nullptr) {

      std::string roundindicator = "We are in round: " + std::to_string(gameState->get_round_number());

      //wxPoint turnIndicatorPosition = MainGamePanel::tableCenter + MainGamePanel::turnIndicatorOffset;
      wxPoint roundIndicatorPosition = MainGamePanel::tableCenter + MainGamePanel::roundIndicatorOffset;

      this->buildStaticText(
              roundindicator,
              roundIndicatorPosition,
              wxSize(200, 18),
              wxALIGN_CENTER,
              true
      );
  }
}


void MainGamePanel::buildDealer(game_state* gameState){
    if(gameState->is_started()) {
   
        wxBoxSizer* handLayout = new wxBoxSizer(wxVERTICAL);

        wxPoint offset(80, 0);

        hand* dealers_hand = gameState->get_dealers_hand();
        std::vector<card*> dealers_cards = dealers_hand->get_cards();

        std::vector<wxPoint> offsets(dealers_cards.size());
        for(unsigned i = 0; i < dealers_cards.size(); ++i){
            offsets[i] = i * offset;
        }
        std::string backside = "assets/png-cards/backside.png";
        std::string left_frontside = getPngFileName(dealers_cards.at(0)->get_value(), dealers_cards.at(0)->get_suit());

        std::vector<std::string> dealer_cards_file_names(dealers_cards.size());
        std::transform(dealers_cards.begin(), dealers_cards.end(),
                        dealer_cards_file_names.begin(), [this](card* c) -> std::string {
                        return this->getPngFileName(c->get_value(), c->get_suit());
                        });

        // display DEALER LABEL
        std::string dealerindicator = "Dealer";
        wxPoint dealerIndicatorPosition = MainGamePanel::tableCenter + MainGamePanel::leftDealerCardOffset + MainGamePanel::dealerIndicatorOffset;
        this->buildStaticText(
                dealerindicator,
                dealerIndicatorPosition,
                wxSize(200, 18),
                wxALIGN_CENTER,
                true
        );

        // display DEALER CARDS
        // A round has two parts: in the first part the dealer has two cards, one of which is flipped, and in the second part 
        // he gets more cards according to the hard coded rules
        bool first_part = !gameState->everyone_finished();
        wxPoint leftCardPosition = MainGamePanel::tableCenter + MainGamePanel::leftDealerCardOffset;
        // during round
        if(first_part){
            wxPoint rightCardPosition = MainGamePanel::tableCenter + MainGamePanel::rightDealerCardOffset;
            ImagePanel* rightDealerCard = new ImagePanel(this, backside, wxBITMAP_TYPE_ANY, rightCardPosition, MainGamePanel::cardSize);
            ImagePanel* leftDealerCard = new ImagePanel(this, getPngFileName(dealers_cards[0]->get_value(), dealers_cards[0]->get_suit()), wxBITMAP_TYPE_ANY, leftCardPosition, MainGamePanel::cardSize);
        }
        // end of round
        else {
            for(unsigned i = 0; i < dealers_cards.size(); ++i){
                ImagePanel *image_panel = new ImagePanel(this, dealer_cards_file_names[i], wxBITMAP_TYPE_ANY, leftCardPosition + offsets[i], MainGamePanel::cardSize);
                handLayout->Add(image_panel, 0, wxLEFT | wxRIGHT, 4);
            }
            // halt game till displayed continue button is pressed
            wxButton* continueButton = new wxButton(this, wxID_ANY, "Continue", wxDefaultPosition, wxSize(100, 40));
            continueButton->Bind(wxEVT_BUTTON, [](wxCommandEvent& event) {
                GameController::continue_to_bet_panel();
            });
            handLayout->Add(continueButton, 0, wxALIGN_RIGHT | wxALL, 10);
        }
    }
}

wxStaticText* MainGamePanel::buildStaticText(std::string content, wxPoint position, wxSize size, long textAlignment, bool bold) {
    wxStaticText* staticText = new wxStaticText(this, wxID_ANY, content, position, size, textAlignment);
    if(bold) {
        wxFont font = staticText->GetFont();
        font.SetWeight(wxFONTWEIGHT_BOLD);
        staticText->SetFont(font);
    }
    return staticText;
}


wxSize MainGamePanel::getBoundsOfRotatedSquare(double edgeLength, double rotationAngle) {
    double newEdgeLength = this->getEdgeLengthOfRotatedSquare(edgeLength, rotationAngle);
    return wxSize(newEdgeLength, newEdgeLength);
}


double MainGamePanel::getEdgeLengthOfRotatedSquare(double originalEdgeLength, double rotationAngle) {
    return originalEdgeLength * (abs(sin(rotationAngle)) + abs(cos(rotationAngle)));
}


wxPoint MainGamePanel::getPointOnEllipse(double horizontalRadius, double verticalRadius, double angle) {
    return wxPoint((int) (sin(angle) * horizontalRadius), (int) (cos(angle) * verticalRadius));
}

// Returns file path to the correct corresponding card
std::string MainGamePanel::getPngFileName(int value, int suit){

    std::map<int, std::string> value_map {{11, "jack"}, {12, "queen"}, {1, "ace"}, {13, "king"}};
    std::map<int, std::string> suit_map{
        {0, "spades"}, {1, "clubs"}, {2, "diamonds"}, {3, "hearts"}};

    std::string path = "assets/png-cards/";

    if (value > 1 && value <= 10) {
      return path + std::to_string(value) + "_of_" + suit_map[suit] + ".png";
    }
    else{
      return path + value_map[value] + "_of_" + suit_map[suit] + ".png";
    }
}