/* Copyright © 2017 Apple Inc. All rights reserved.
 *
 * Use of this source code is governed by a BSD-3-clause license that can
 * be found in the LICENSE.txt file or at https://opensource.org/licenses/BSD-3-Clause
 */
#ifndef TURI_FAST_MULTINOMIAL_HPP
#define TURI_FAST_MULTINOMIAL_HPP

#include <vector>
#include <algorithm>

#include <boost/integer.hpp>
#include <boost/random.hpp>

#include <core/parallel/pthread_tools.hpp>
#include <core/parallel/atomic.hpp>

#include <core/generics/float_selector.hpp>

namespace turi {

  /// \ingroup util_internal
  class fast_multinomial {
    // system word length float
    typedef float_selector<sizeof(size_t)>::float_type float_t;

    //! First leaf index
    size_t first_leaf_index;

    //! The number of assignments to the multinomial
    size_t num_asg;

    //! The tree datastructure
    std::vector<float_t> tree;

    //! the number of positive probability elements
    atomic<size_t> num_support;

    // Helper routines
    // ========================================================>

    //! Compute the next power of 2
    //     size_t next_powerof2(size_t val) {
    //       size_t powof2 = 1;
    //       while(powof2 < val) powof2 = powof2 * 2;
    //       return powof2;
    //     }

    //! Clever next power of two bit magic
    uint64_t next_powerof2(uint64_t val) {
      --val;
      val = val | (val >> 1);
      val = val | (val >> 2);
      val = val | (val >> 4);
      val = val | (val >> 8);
      val = val | (val >> 16);
      val = val | (val >> 32);
      return val + 1;
    }

    //! Returns the index of the left child of the supplied index.
    size_t left_child(size_t i) const { return 2 * i + 1; }

    //! Returns the index of the right child of the supplied index.
    size_t right_child(size_t i) const { return 2 * i + 2; }

    //! Returns the index of the parent of the supplied index.
    size_t parent(size_t i) const { return (i-1) / 2; }

    //! returns the sibling of
    size_t sibling(size_t i) const {
      // the binary here is equivalent to (+1) if leaf is odd and (-1) if
      // leaf is even
      return i + (i & 1)*2 - 1;
    }

    //! get the tree location of the assignment
    size_t tree_loc_from_asg(size_t asg) const {
      size_t loc = asg + first_leaf_index;
      assert(loc < tree.size());
      assert(is_leaf(loc));
      return loc;
    }

    //! determine the assignment from the location in the tree
    size_t asg_from_tree_loc(size_t i) const {
      assert(is_leaf(i));
      size_t asg = i - first_leaf_index;
      assert(asg < num_asg);
      return asg;
    }

    //! Returns true if the index corresponds to a leaf
    bool is_leaf(size_t i) const {
      return i >= first_leaf_index;
      //      return left_child(i) > tree.size();
    }

    //! Returns true if the location is the root
    bool is_root(size_t i) const { return i == 0; }

    /// Returns the index of a leaf sampled proportionate to its
    /// priority.  Returns false on failure
    bool try_sample(size_t& asg, size_t cpuid) {
      size_t loc = 0;
      while ( !is_leaf(loc) ) {
        // get the left and right priorities
        float_t left_p = tree.at(left_child(loc));
        float_t right_p = tree.at(right_child(loc));
        // if both are zero, the sample has failed. Return
        if (left_p + right_p == 0) return false;
        else if (right_p == 0) loc = left_child(loc);
        else if (left_p == 0)  loc = right_child(loc);
        else {
          // pick from a bernoulli trial
          float_t childsum = left_p + right_p;
          float_t rndnumber = turi::random::uniform<float_t>(0,1);
          if((childsum * rndnumber)  < left_p)
            loc = left_child(loc);
          else
            loc = right_child(loc);
        }
      }
      assert(is_leaf(loc));
      asg = asg_from_tree_loc(loc);
      assert(asg < num_asg);
      return true;
    } // end of sample index

    /// Propagates a cumulative sum update up the tree.
    void propagate_change(size_t loc) {
      // Loop while the location is not the root each time moving up
      // the tree
      for( ; !is_root(loc); loc = parent(loc) ) {
        // Get the sibbling of this location
        size_t sibling_loc = sibling(loc);
        assert(sibling_loc < tree.size());
        // Get the parent
        size_t parent_loc = parent(loc);
        assert(parent_loc < tree.size());
        // Assert that the sibling is infact the sibling
        assert(parent_loc == parent(sibling_loc));
        // Get the priority of this location and the sibling
        // and the parent
        volatile float_t* sibling1 = &(tree[loc]);
        volatile float_t* sibling2 = &(tree[sibling_loc]);
        volatile float_t* parent = &(tree[parent_loc]);

        // write to the parent. Use a concurrent write mechanism
        // size_t spin_count = 0;
        //float_t old_value = *parent;
        //        float_t new_value = *sibling1 + *sibling2;
        //        while(!atomic_compare_and_swap(tree[parent_loc], old_value, new_value)) {
        //          old_value = *parent;
        //          new_value = *sibling1 + *sibling2;
        // if(++spin_count % 10 == 0) {
        //   std::cerr << "Propagate_change: " << spin_count << std::endl;
        // }
        //      }
        while(true) {
          float_t sum = (*sibling1) + (*sibling2);
          (*parent) = sum;
          __asm("mfence");
          float_t sum2 = (*sibling1) + (*sibling2);
          float_t parentval = (*parent);
          if (sum2 == parentval) break;
        }
        // If the update was successful accomplished by anothe thread
        // than return
        //if(old_value == new_value) return;
      } // end of for loop
    } // end of propagate change

  public:

    /** initialize a fast multinomail */
    fast_multinomial(size_t num_asg,
                     size_t ncpus) :
      first_leaf_index(0),
      num_asg(num_asg) {
      // // initialize the generators
      // for(size_t i = 0; i < rngs.size(); ++i) {
      //   rngs[i].seed(rand());
      //   distributions.push_back(distribution_type(rngs[i]));
      // }
      // Determine the size of the tree
      first_leaf_index = next_powerof2(num_asg) - 1;
      size_t tree_size = first_leaf_index + next_powerof2(num_asg);
      tree.resize(tree_size, 0.0);
    }

    void zero(size_t asg) {
      assert(asg < num_asg);
      size_t loc = tree_loc_from_asg(asg);
      // Use CAS
      float_t old_value = tree[loc];
      float_t new_value = 0;
      while(!atomic_compare_and_swap(tree[loc], old_value, new_value)){
        old_value = tree[loc];
      }
      propagate_change(loc);
      if(old_value > 0) {
        num_support.dec();
      }
    }

    //! Set a leaf value
    void set(size_t asg, float_t value) {
      assert(asg < num_asg);
      assert(value >= 0);
      size_t loc = tree_loc_from_asg(asg);
      // Use atomic compare and swap to update the value
      float_t old_value = tree[loc];
      float_t new_value = value;
      while(!atomic_compare_and_swap(tree[loc], old_value, new_value)){
        old_value = tree[loc];
      }
      if(old_value == 0 && new_value > 0) {
        num_support.inc();
      }
      propagate_change(loc);
      // Update support count
      if(old_value > 0 && new_value == 0) {
        num_support.dec();
      }
    } // end of set

    //! Set a leaf value
    void add(size_t asg, float_t value) {
      assert(asg < num_asg);
      assert(value >= 0);
      size_t loc = tree_loc_from_asg(asg);
      // Use atomic compare and swap to update the value
      float_t old_value = tree[loc];
      float_t new_value = value + old_value;
      while(!atomic_compare_and_swap(tree[loc], old_value, new_value)){
        old_value = tree[loc];
        new_value = value + old_value;
      }
      // Update support count
      if(old_value == 0 && new_value > 0) {
        num_support.inc();
      }
      propagate_change(loc);
    } // end of add

    //! Set a leaf value
    void max(size_t asg, float_t value) {
      assert(asg < num_asg);
      assert(value >= 0);
      size_t loc = tree_loc_from_asg(asg);
      // Use atomic compare and swap to update the value
      float_t old_value = tree[loc];
      float_t new_value = std::max(value, old_value);
      while(!atomic_compare_and_swap(tree[loc], old_value, new_value)){
        old_value = tree[loc];
        new_value = std::max(value, old_value);
      }
      if(old_value == 0 && new_value > 0) {
        num_support.inc();
      }
      propagate_change(loc);
      // Update support count
      if(old_value > 0 && new_value == 0) {
        num_support.dec();
      }
    } // end of set

    /**
     * Try to draw a sample from the multinomial.  If everything has
     * probability zero then return false.
     */
    bool sample(size_t& ret_asg, size_t cpuid) {
      // While there is positive support for some assignment
      // size_t spin_count = 0;
      volatile float_t *root = &(tree[0]);
      while(num_support.value > 0 || (*root) > 0) {
        // Try and get a sample
        if(try_sample(ret_asg, cpuid)) {
          assert(ret_asg < num_asg);
          return true;
        }

        //         if(++spin_count % 10000 == 0) {
        //           std::cerr // << THREAD_ID() << ": "
        //                     << "  Sample: " << spin_count
        //                     << ", " << tree[0]
        //                     << ", " << num_support.value
        //                     << std::endl;
        //           float_t sum = 0;
        //           for(size_t i = first_leaf_index; i < tree.size(); ++i) {
        //             sum += tree[i];
        //           }
        //           std::cerr << "Tree Sum: " << sum << std::endl;
        //           std::getchar();
        //         }

      }  // End of While loop

      //       if(spin_count >= 10){
      //         std::cerr // << THREAD_ID() << ": "
      //                   << "  Sample_recover: " << spin_count << std::endl;
      //       }

      return false;
    } // end of sample

    /**
     * Try to draw a sample from the multinomial and zero out the
     * probability of the element that was drawn.  If everything has
     * probability zero then return false.
     */
    bool pop(size_t& ret_asg, size_t cpuid) {
      if(tree.empty()) return false;
      // While there is positive support for some assignment
      while(num_support.value > 0 || tree[0] > 0) {
        // Try and get a sample
        if(try_sample(ret_asg, cpuid)) {
          assert(ret_asg < num_asg);
          // We have a sample but it is possible that another thread
          // also go this sample so we will use CAS to see who "wins"
          // and gets to keep the sample and who has to try again
          size_t loc = tree_loc_from_asg(ret_asg);
          // Use CAS
          float_t old_value = tree[loc];
          float_t new_value = 0;
          while(!atomic_compare_and_swap(tree[loc], old_value, new_value)){
            old_value = tree[loc];
          }
          // Figure out if we won and get to keep the sample or if
          // some other thread won and zeroed out the sample before we
          // got it.
          if(old_value > 0) {
            // We win!!! and keep the sample :-)
            propagate_change(loc);
            num_support.dec();
            return true;
          }
          // The other thread wins and we have to try agian :-(.
        }
      }

      std::cerr << "Queue emptied!: " << tree[0]
                << ", " << num_support.value << std::endl;
      print_tree();
      return false;
    } // end of pop

    /** Get the number of assignments with positive support */
    size_t positive_support() {
      return num_support.value;
    }

    /** print the tree */
    void print_tree() {
      for (size_t i = 0; i < std::min(tree.size(), size_t(1000)); ++i) {
        if(is_leaf(i)) {
          std::cout << "Leaf(" << asg_from_tree_loc(i)
                    << ", [" << parent(i) << "], "
                    << tree[i] << ") ";
        } else {
          std::cout << "Node(" << i <<  ", "
                    << "[" << left_child(i) << ", "
                    << right_child(i) << "], "
                    << tree[i] << ") ";
        }
      }
      std::cout << std::endl;
    }

    float_t get_weight(size_t asg) const {
      size_t loc = tree_loc_from_asg(asg);
      return tree[loc];
    }

    bool has_support(size_t asg) const {
      size_t loc = tree_loc_from_asg(asg);
      return tree[loc] > 0;
    }

    void clear() {
      // not thread safe
      std::fill(tree.begin(), tree.end(), 0.0);
      num_support.value = 0;
    }
  }; // end of fast_multinomial

} // end of namespace

#undef float_t
#endif


#include <boost/test/floating_point_comparison.hpp>
#include <boost/test/unit_test.hpp>

#include "engine/polyline_compressor.hpp"
#include "util/coordinate_calculation.hpp"

#include <osrm/coordinate.hpp>

#include <cmath>
#include <vector>

BOOST_AUTO_TEST_SUITE(polyline)

using namespace osrm;
using namespace osrm::engine;

BOOST_AUTO_TEST_CASE(decode)
{
    // Polyline string for the 5 coordinates
    const std::string polyline = "_gjaR_gjaR_pR_ibE_pR_ibE_pR_ibE_pR_ibE";
    const auto coords = polylineDecode(polyline);

    // Test coordinates; these would be the coordinates we give the loc parameter,
    // e.g. loc=10.00,10.0&loc=10.01,10.1...
    util::FixedPointCoordinate coord1(10.00 * COORDINATE_PRECISION, 10.0 * COORDINATE_PRECISION);
    util::FixedPointCoordinate coord2(10.01 * COORDINATE_PRECISION, 10.1 * COORDINATE_PRECISION);
    util::FixedPointCoordinate coord3(10.02 * COORDINATE_PRECISION, 10.2 * COORDINATE_PRECISION);
    util::FixedPointCoordinate coord4(10.03 * COORDINATE_PRECISION, 10.3 * COORDINATE_PRECISION);
    util::FixedPointCoordinate coord5(10.04 * COORDINATE_PRECISION, 10.4 * COORDINATE_PRECISION);

    // Put the test coordinates into the vector for comparison
    std::vector<util::FixedPointCoordinate> cmp_coords;
    cmp_coords.emplace_back(coord1);
    cmp_coords.emplace_back(coord2);
    cmp_coords.emplace_back(coord3);
    cmp_coords.emplace_back(coord4);
    cmp_coords.emplace_back(coord5);

    BOOST_CHECK_EQUAL(cmp_coords.size(), coords.size());

    for (unsigned i = 0; i < cmp_coords.size(); ++i)
    {
        const double cmp1_lat = coords.at(i).lat;
        const double cmp2_lat = cmp_coords.at(i).lat;
        BOOST_CHECK_CLOSE(cmp1_lat, cmp2_lat, 0.0001);

        const double cmp1_lon = coords.at(i).lon;
        const double cmp2_lon = cmp_coords.at(i).lon;
        BOOST_CHECK_CLOSE(cmp1_lon, cmp2_lon, 0.0001);
    }
}

BOOST_AUTO_TEST_SUITE_END()


#pragma once

#include <string>
#include <vector>
#include <boost/optional.hpp>

#include "GNAObject.hh"

namespace GNA{
    namespace GNAObjectTemplates {
        template<typename FloatType>
        class NormalizedConvolutionT: public GNASingleObjectT<FloatType,FloatType>,
                                      public TransformationBind<NormalizedConvolutionT<FloatType>,FloatType,FloatType> {
        protected:
            using NormalizedConvolutionType = NormalizedConvolutionT<FloatType>;
            using GNAObjectType = GNAObjectT<FloatType,FloatType>;
        public:
            using typename GNAObjectType::FunctionArgs;

            NormalizedConvolutionT();
            NormalizedConvolutionT(const std::string& scale);

        protected:
            void convolute(FunctionArgs& fargs);

            boost::optional<variable<FloatType>> m_scale;
        };
    }
}


/* Copyright (c) 2012-2013 Cheese and Bacon Games, LLC */
/* See the file docs/COPYING.txt for copying permission. */

#include "level.h"
#include "world.h"
#include "message_log.h"
#include "render.h"
#include "update.h"
#include "file_io.h"

#include <fstream>

#include <boost/algorithm/string.hpp>

using namespace std;

unsigned long Level::survival_spawn_items_time(){
    if(!survival_never_spawned_items){
        unsigned long minutes_played=survival_seconds_played/60;

        unsigned long base_seconds=(unsigned long)floor(((level_x/TILE_SIZE)*(level_y/TILE_SIZE))/500);
        if(base_seconds>30){
            base_seconds=30;
        }

        unsigned long seconds_for_spawn=base_seconds+minutes_played;

        return seconds_for_spawn;
    }
    else{
        survival_never_spawned_items=false;
        return 0;
    }
}

unsigned long Level::survival_spawn_npcs_time(){
    if(!survival_never_spawned_npcs){
        unsigned long minutes_played=survival_seconds_played/60;

        unsigned long base_seconds=(unsigned long)floor(((level_x/TILE_SIZE)*(level_y/TILE_SIZE))/500);
        if(base_seconds>30){
            base_seconds=30;
        }

        unsigned long seconds_for_spawn=base_seconds-minutes_played;
        if(seconds_for_spawn<10){
            seconds_for_spawn=10;
        }

        return seconds_for_spawn;
    }
    else{
        survival_never_spawned_npcs=false;
        return 0;
    }
}

int Level::survival_spawn_items_chance(){
    unsigned long minutes_played=survival_seconds_played/60;

    unsigned long base_chance=(unsigned long)floor(((double)(410.0/(double)((level_x/TILE_SIZE)*(level_y/TILE_SIZE)))*1000.0)*2.0);
    if(base_chance<50){
        base_chance=50;
    }

    int chance_for_spawn=base_chance-((int)floor(minutes_played*1.5));
    if(chance_for_spawn<35){
        chance_for_spawn=35;
    }

    return chance_for_spawn;
}

int Level::survival_spawn_npcs_chance(){
    unsigned long minutes_played=survival_seconds_played/60;

    unsigned long base_chance=(unsigned long)floor(((double)(410.0/(double)((level_x/TILE_SIZE)*(level_y/TILE_SIZE)))*1000.0)*2.0)/2;
    if(base_chance<70){
        base_chance=70;
    }

    int chance_for_spawn=base_chance+((int)floor(minutes_played*1.75));
    if(chance_for_spawn>95){
        chance_for_spawn=95;
    }

    return chance_for_spawn;
}

void Level::load_level_survival(){
    //Pause the game.
    player.toggle_pause(true);

    player.light_source.dimness=0.0;
    player.light_source.falloff=0.035/(TILE_SIZE/LIGHTING_TILE_SIZE);

    for(int i=0;i<mp_players.size();i++){
        mp_players[i].light_source.dimness=0.0;
        mp_players[i].light_source.falloff=0.035/(TILE_SIZE/LIGHTING_TILE_SIZE);
    }

    devworld_tiles.clear_tiles();

    alarm=false;

    int things_loaded=0;
    int things_to_load=3;

    //As long as a level has been loaded before
    //and the last level was not the same as this one.
    if(player.previous_level!=-1 && player.previous_level!=player.current_level){
        player.tracer_manager.clear_tracers();

        //Unload the previous level's images.
        image.unload_images_level(player.previous_level);

        //Reset the special achievement counts.
        player.special_count_shots_this_level=0;
        player.special_count_kills_this_level=0;
    }

    string current_level="survival/";
    current_level+=player.non_number_level;

    //Clear the vectors. This ensures that any previous data held in the vectors is deleted, and that any memory that was allocated by the objects in the vectors is freed.
    background_layers.clear();

    vector_items.clear();
    vector_npcs.clear();
    vector_shots.clear();
    vector_moving_platforms.clear();
    vector_triggers.clear();
    vector_doors.clear();
    vector_traps.clear();
    vector_signs.clear();
    vector_springs.clear();
    vector_boosters.clear();
    vector_effect_water_splash.clear();
    vector_effect_water_bubble.clear();
    vector_effect_score_floater.clear();
    vector_effect_firework.clear();
    vector_spawners.clear();

    //*******************************************//
    // First, we will read the level properties: //
    //*******************************************//

    //First, we open the file for reading.
    File_IO_Load load;

    string level_to_load="";

    level_to_load="data/levels/"+current_level+"/level_properties.blazelevel";

    load.open(level_to_load);

    bool items_stay=false;

    //As long as the file opened successfully, proceed to load the level data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        //Create any variables that will be used to pass on information from the level file.
        double start_y=0;
        double width=0;
        double height=0;
        short number_of_background_layers=0;
        double parallax_speed_x=0;
        double parallax_speed_y=0;

        //The first two numbers in the level file are the x and y dimensions of the entire level's map.
        data_stream >> level_x;
        data_stream >> level_y;

        data_stream >> player.ammo;
        player.ammo=floor((double)player.ammo*(1+((double)mp_players.size()*0.5)));
        for(int i=0;i<mp_players.size();i++){
            mp_players[i].ammo=player.ammo;
        }

        data_stream >> start_y;
        data_stream >> width;
        data_stream >> height;

        //Load in the number of background layers.
        data_stream >> number_of_background_layers;

        for(int i=0;i<number_of_background_layers;i++){
            data_stream >> parallax_speed_x;
            data_stream >> parallax_speed_y;
            background_layers.push_back(Background_Layer(start_y,width,height,parallax_speed_x,parallax_speed_y));
        }

        data_stream >> items_stay;

        data_stream >> player.night_mode;

        data_stream >> player.deadly_water;

        if(random_range(0,99)<50){
            player.facing=LEFT;
        }
        else{
            player.facing=RIGHT;
        }
        for(int i=0;i<mp_players.size();i++){
            if(random_range(0,99)<50){
                mp_players[i].facing=LEFT;
            }
            else{
                mp_players[i].facing=RIGHT;
            }
        }
        player.persistent_level_data=false;

        //Determine the date and time.
        /**time_t now;
        struct tm *tm_now;
        now=time(NULL);
        tm_now=localtime(&now);

        //If it is daytime.
        if(tm_now->tm_hour>=6 && tm_now->tm_hour<18){
            player.night_mode=false;
        }*/

        player.light_data.clear();
        player.light_data.resize(level_x/LIGHTING_TILE_SIZE,vector<Light_Data>(level_y/LIGHTING_TILE_SIZE));

        for(int int_y=0;int_y<level_y/LIGHTING_TILE_SIZE;int_y++){
            for(int int_x=0;int_x<level_x/LIGHTING_TILE_SIZE;int_x++){
                player.light_data[int_x][int_y].seen=false;
                player.light_data[int_x][int_y].color.red=-1.0;
                player.light_data[int_x][int_y].color.green=-1.0;
                player.light_data[int_x][int_y].color.blue=-1.0;
                player.light_data[int_x][int_y].light_intensity=-1.0;
                player.light_data[int_x][int_y].dimness=-1.0;
            }
        }

        //When we are done using the level file, we close it.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading level properties.");
    }

    //***************************************//
    // Next, we will read the terrain tiles: //
    //***************************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/tiles.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the level data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        tile_array.clear();
        tile_array.resize(level_x/TILE_SIZE,vector<Tile>(level_y/TILE_SIZE));

        //Now we make temporary variables for getting the type of each tile being set.
        short solidity=0;
        short sprite=0;
        short special=0;
        bool foreground=0;
        short slope=0;

        //Then we iterate through all of the tiles in the file, setting each one.
        for(int int_y=0;int_y<level_y/TILE_SIZE;int_y++){
            for(int int_x=0;int_x<level_x/TILE_SIZE;int_x++){
                //For each tile, load the type information from the level file.
                data_stream >> solidity;
                data_stream >> sprite;
                data_stream >> special;
                data_stream >> foreground;
                data_stream >> slope;

                //If the tile is an item holder, make it empty air.
                if(special==TILE_SPECIAL_ITEM_HOLDER){
                    solidity=0;
                    sprite=0;
                    special=0;
                    foreground=0;
                    slope=0;
                }

                //Then generate a new tile in the tile vector with the proper coordinates and the type loaded from the level file.
                tile_array[int_x][int_y].x=int_x*TILE_SIZE;
                tile_array[int_x][int_y].y=int_y*TILE_SIZE;
                tile_array[int_x][int_y].solidity=solidity;
                tile_array[int_x][int_y].sprite=sprite;
                tile_array[int_x][int_y].special=special;
                tile_array[int_x][int_y].foreground=foreground;
                tile_array[int_x][int_y].slope=slope;
                tile_array[int_x][int_y].set_height_mask();
            }
        }

        for(int int_x=0;int_x<level_x/TILE_SIZE;int_x++){
            for(int int_y=0;int_y<level_y/TILE_SIZE;int_y++){
                if(tile_array[int_x][int_y].special==TILE_SPECIAL_WATER && tile_array[int_x][int_y].sprite==1){
                    if(player.deadly_water){
                        ///if(player.current_level==10){
                            tile_array[int_x][int_y].light_source.on=true;
                            tile_array[int_x][int_y].light_source.color=color_name_to_doubles(COLOR_RED);
                        /**}
                        else{
                            tile_array[int_x][int_y].light_source.on=true;
                            tile_array[int_x][int_y].light_source.color=color_name_to_doubles(COLOR_GREEN);
                        }*/
                    }
                }
            }
        }

        //When we are done using the level file, we close it, and clear load for its next use.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading terrain tiles.");
    }

    //******************************************//
    // Next, we will read the background tiles: //
    //******************************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/tiles_background.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the level data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        tile_background_array.clear();

        tile_background_array.resize(level_x/TILE_SIZE,vector<Tile_Background>(level_y/TILE_SIZE));

        //Now we make a temporary variable for getting the sprite of each tile being set.
        short sprite=0;

        //Then we iterate through all of the tiles in the file, setting each one.
        for(int int_y=0;int_y<(level_y/TILE_SIZE);int_y++){
            for(int int_x=0;int_x<(level_x/TILE_SIZE);int_x++){
                //For each tile, load the type information from the level file.
                data_stream >> sprite;

                //Then generate a new tile in the tile vector with the proper coordinates and the type loaded from the level file.
                tile_background_array[int_x][int_y].x=int_x*TILE_SIZE;
                tile_background_array[int_x][int_y].y=int_y*TILE_SIZE;
                tile_background_array[int_x][int_y].sprite=sprite;
            }
        }

        //When we are done using the level file, we close it, and clear load for its next use.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading background tiles.");
    }

    //******************************//
    // Now, we will read the items: //
    //******************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/items.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        //Now we make a temporary variable for getting the type of each item being loaded.
        short type;
        double x;
        double y;
        int goal_level_to_load;
        bool goal_secret;

        //Then we iterate through all of the items in the file, setting each one.
        while(!data_stream.eof()){
            type=30000;
            x=0;
            y=0;
            goal_level_to_load=0;
            goal_secret=false;

            //For each item, load the type information from the file.
            data_stream >> type;
            data_stream >> x;
            data_stream >> y;
            data_stream >> goal_level_to_load;
            data_stream >> goal_secret;

            bool should_spawn=false;
            if(type!=ITEM_LEAF && type!=ITEM_CANDY){
                should_spawn=true;
            }

            if(type!=30000 && should_spawn){
                vector_items.push_back(Item(x,y,false,type,goal_level_to_load,goal_secret));
            }
        }

        //When we are done using the level file, we close it, and clear load for its next use.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading level items.");
    }

    //*****************************//
    // Now, we will read the npcs: //
    //*****************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/npcs.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        //Now we make a temporary variable for getting the type of each npc being loaded.
        short type;
        double x;
        double y;

        //Then we iterate through all of the items in the file, setting each one.
        while(!data_stream.eof()){
            type=0;
            x=0;
            y=0;

            //For each item, load the type information from the file.
            data_stream >> type;
            data_stream >> x;
            data_stream >> y;

            bool should_spawn=true;
            if(player.npc_is_main_boss(type)){
                should_spawn=false;

                //Replace bosses with two ammo boxes each.
                vector_items.push_back(Item(x,y,false,ITEM_AMMO,0,false));
                vector_items.push_back(Item(x+32,y,false,ITEM_AMMO,0,false));
            }

            if(type!=0 && should_spawn){
                vector_npcs.push_back(Npc(x,y,type,false));
                vector_npcs[vector_npcs.size()-1].ethereal_to_npcs=true;
            }
        }

        //When we are done using the level file, we close it, and clear load for its next use.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading level npcs.");
    }

    //*****************************************//
    // Now, we will read the moving platforms: //
    //*****************************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/moving_platforms.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the level data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        //Now we make temporary variables.
        double move_speed;
        double start_point_x;
        double start_point_y;
        double end_point_x;
        double end_point_y;
        bool active;
        bool round_trip;
        bool movement_type;
        short identifier;
        int number_of_waypoints;
        vector<moving_platform_waypoint> waypoints;

        //Then we iterate through all of the npcs in the file, setting each one.
        while(!data_stream.eof()){
            move_speed=0;
            start_point_x=0;
            start_point_y=0;
            end_point_x=0;
            end_point_y=0;
            active=false;
            round_trip=false;
            movement_type=false;
            identifier=0;
            number_of_waypoints=0;
            waypoints.clear();

            //For each item, load the type information from the file.
            data_stream >> move_speed;
            data_stream >> start_point_x;
            data_stream >> start_point_y;
            data_stream >> end_point_x;
            data_stream >> end_point_y;
            data_stream >> active;
            data_stream >> round_trip;
            data_stream >> movement_type;
            data_stream >> identifier;
            data_stream >> number_of_waypoints;

            for(int i=0;i<number_of_waypoints;i++){
                waypoints.push_back(moving_platform_waypoint());
                data_stream >> waypoints[i].x;
                data_stream >> waypoints[i].y;
            }

            if(move_speed!=0){
                vector_moving_platforms.push_back(Moving_Platform(move_speed,start_point_x,start_point_y,end_point_x,end_point_y,waypoints,active,round_trip,movement_type,identifier));
            }
        }

        //When we are done using the level file, we close it, and clear load for its next use.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading moving platforms.");
    }

    //*********************************//
    // Now, we will read the triggers: //
    //*********************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/triggers.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the level data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        //Now we make temporary variables.
        double x,y;
        double w,h;
        int number_of_targets;
        vector<target> targets;
        bool trigger_method;
        short user_type;
        bool repeating;
        short repeat_time;
        short render_trigger;

        //Then we iterate through all of the triggers in the file, setting each one.
        while(!data_stream.eof()){
            x=0;
            y=0;
            w=0;
            h=0;
            number_of_targets=0;
            targets.clear();
            trigger_method=false;
            user_type=0;
            repeating=false;
            repeat_time=0;
            render_trigger=0;

            //For each item, load the type information from the file.
            data_stream >> x;
            data_stream >> y;
            data_stream >> w;
            data_stream >> h;
            data_stream >> number_of_targets;

            for(int i=0;i<number_of_targets;i++){
                targets.push_back(target());
                data_stream >> targets[i].type;
                data_stream >> targets[i].identifier;
            }

            data_stream >> trigger_method;
            data_stream >> user_type;
            data_stream >> repeating;
            data_stream >> repeat_time;
            data_stream >> render_trigger;

            if(w!=0){
                vector_triggers.push_back(Trigger(x,y,w,h,targets,trigger_method,user_type,repeating,repeat_time,render_trigger));
            }
        }

        //When we are done using the level file, we close it.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading triggers.");
    }

    //******************************//
    // Now, we will read the doors: //
    //******************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/doors.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the level data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        //Now we make temporary variables.
        double x,y;
        short type;
        short number;
        bool open;
        short identifier;

        //Then we iterate through all of the doors in the file, setting each one.
        while(!data_stream.eof()){
            x=0;
            y=0;
            type=0;
            number=0;
            open=false;
            identifier=-1;

            //For each one, load the type information from the file.
            data_stream >> x;
            data_stream >> y;
            data_stream >> type;
            data_stream >> number;
            data_stream >> open;
            data_stream >> identifier;

            if(type==DOOR_TYPE_KEY){
                open=true;
            }

            if(identifier!=-1){
                vector_doors.push_back(Door(x,y,type,number,open,identifier));
            }
        }

        //When we are done using the level file, we close it.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading doors.");
    }

    //******************************//
    // Now, we will read the traps: //
    //******************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/traps.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the level data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        //Now we make temporary variables.
        double x,y;
        short type;
        bool active;
        short identifier;

        //Then we iterate through all of the traps in the file, setting each one.
        while(!data_stream.eof()){
            x=0;
            y=0;
            type=0;
            active=false;
            identifier=-1;

            //For each one, load the type information from the file.
            data_stream >> x;
            data_stream >> y;
            data_stream >> type;
            data_stream >> active;
            data_stream >> identifier;

            bool should_spawn=true;
            if(type==TRAP_WEATHER_MACHINE || type==TRAP_OPEN_SHOP || type==TRAP_OPEN_UPGRADES || type==TRAP_BOSS_CASTLE || type==TRAP_BOSS_PYRAMID){
                should_spawn=false;
            }

            if(identifier!=-1 && should_spawn){
                vector_traps.push_back(Trap(x,y,type,active,identifier));
            }
        }

        //When we are done using the level file, we close it.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading traps.");
    }

    //******************************//
    // Now, we will read the signs: //
    //******************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/signs.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the level data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        //Now we make temporary variables.
        double x,y;
        string message;
        short sign_type;
        short font_type;

        //Then we iterate through all of the signs in the file, setting each one.
        while(!data_stream.eof()){
            x=-1;
            y=-1;
            message="";
            sign_type=0;
            font_type=0;

            //For each one, load the type information from the file.
            data_stream >> x;
            data_stream >> y;
            data_stream >> message;
            data_stream >> sign_type;
            data_stream >> font_type;

            boost::algorithm::replace_all(message,"<SPACE>"," ");
            boost::algorithm::replace_all(message,"<NEWLINE>","\xA");

            if(x!=-1 && y!=-1){
                vector_signs.push_back(Sign(x,y,message,sign_type,font_type));
            }
        }

        //When we are done using the level file, we close it.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading signs.");
    }

    //********************************//
    // Now, we will read the springs: //
    //********************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/springs.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the level data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        //Now we make temporary variables.
        double x,y;
        double speed;
        short direction;

        //Then we iterate through all of the springs in the file, setting each one.
        while(!data_stream.eof()){
            x=-1;
            y=-1;
            speed=0.0;
            direction=LEFT;

            //For each one, load the type information from the file.
            data_stream >> x;
            data_stream >> y;
            data_stream >> speed;
            data_stream >> direction;

            if(x!=-1 && y!=-1){
                vector_springs.push_back(Spring(x,y,speed,direction));
            }
        }

        //When we are done using the level file, we close it.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading springs.");
    }

    //*********************************//
    // Now, we will read the boosters: //
    //*********************************//

    //First, we open the file for reading.

    level_to_load="data/levels/"+current_level+"/boosters.blazelevel";

    load.open(level_to_load);

    //As long as the file opened successfully, proceed to load the level data.
    if(load.is_opened()){
        istringstream data_stream(load.get_data());

        //Now we make temporary variables.
        double x,y;
        double speed;
        short direction;

        //Then we iterate through all of the boosters in the file, setting each one.
        while(!data_stream.eof()){
            x=-1;
            y=-1;
            speed=0.0;
            direction=LEFT;

            //For each one, load the type information from the file.
            data_stream >> x;
            data_stream >> y;
            data_stream >> speed;
            data_stream >> direction;

            if(x!=-1 && y!=-1){
                vector_boosters.push_back(Booster(x,y,speed,direction));
            }
        }

        //When we are done using the level file, we close it.
        load.close();
    }
    //If the level file could not be loaded for any reason, we print an error message letting the user know.
    else{
        update_error_log("Error loading boosters.");
    }

    //As long as the last level was not the same as this one.
    if(player.previous_level!=player.current_level){
        render_loading_screen((double)++things_loaded/(double)things_to_load,player.current_level);

        music.unload_track(MUSIC_BOSS);
        music.unload_track(MUSIC_BOSS_FAST);
        music.unload_track(MUSIC_LEVEL_NORMAL);
        music.unload_track(MUSIC_LEVEL_WATER);

        string random_music=num_to_string(random_range(1,3));

        music.load_track("data/music/survival/normal"+random_music+".ogg",MUSIC_LEVEL_NORMAL);
        render_loading_screen((double)++things_loaded/(double)things_to_load,player.current_level);

        music.load_track("data/music/survival/water"+random_music+".ogg",MUSIC_LEVEL_WATER);
        render_loading_screen((double)++things_loaded/(double)things_to_load,player.current_level);

        music.restart_track(MUSIC_ALARM);
        music.restart_track(MUSIC_WORLDMAP_VALLEY);
        music.restart_track(MUSIC_WORLDMAP_CASTLE);
        music.restart_track(MUSIC_WORLDMAP_HAUNTED);
        music.restart_track(MUSIC_WORLDMAP_MOUNTAIN);
        music.restart_track(MUSIC_WORLDMAP_VOLCANO);
        music.restart_track(MUSIC_WORLDMAP_DESERT);
        music.restart_track(MUSIC_WORLDMAP_OCEAN);
        music.restart_track(MUSIC_WORLDMAP_MOUNTAIN_PEAK);
        music.restart_track(MUSIC_WORLDMAP_LIGHTHOUSE);
        music.restart_track(MUSIC_BOSS);
        music.restart_track(MUSIC_BOSS_FAST);
        music.restart_track(MUSIC_LEVEL_NORMAL);
        music.restart_track(MUSIC_LEVEL_WATER);
        music.play_track(MUSIC_LEVEL_NORMAL);

        title_top_x=0.0;
        title_bottom_x=0.0;
        title_bg_y_top=0.0;
        title_bg_x_bottom=0.0;
        title_bg_x_left=0.0;
        title_top_string="";
        title_bottom_string="";
        title_moving=TITLE_NOT_MOVING;
        counter_title=0;

        timer_current_level.start();
        seconds_in_current_level=0;
        player.seconds_total_current_level=0;
        player.seconds_best_current_level=4294967295;
    }

    if(title_moving==TITLE_NOT_MOVING){
        //Unpause the game.
        player.toggle_pause(false);
    }

    survival_seconds_played=0;
    survival_never_spawned_items=true;
    survival_never_spawned_npcs=true;
    timer_survival_escape_counter.start();
    timer_survival_spawn_items.start();
    timer_survival_spawn_npcs.start();

    //Replace items with item spawners.
    for(int i=0;i<vector_items.size();i++){
        if(vector_items[i].type==ITEM_CHECKPOINT || vector_items[i].type==ITEM_ENDPOINT){
            vector_items[i].type=ITEM_SPAWNPOINT;
            vector_items[i].y+=64;
        }
        else if(vector_items[i].type==ITEM_CHEESE || vector_items[i].type==ITEM_AMMO_BARREL ||
                vector_items[i].type==ITEM_SWIMMING_GEAR ||
                (vector_items[i].type>=ITEM_KEY_RED && vector_items[i].type<=ITEM_KEY_PURPLE) ||
                vector_items[i].type==ITEM_TOWEL || vector_items[i].type==ITEM_SINK ||
                (vector_items[i].type>=ITEM_KEY_GRAY && vector_items[i].type<=ITEM_J_WING)){
            vector_items[i].type=ITEM_AMMO;
        }

        if(vector_items[i].type!=ITEM_SPAWNPOINT){
            vector_spawners.push_back(Spawner(SPAWN_ITEM,vector_items[i].type,vector_items[i].x,vector_items[i].y,false,items_stay));

            vector_items.erase(vector_items.begin()+i);
            i--;
        }
    }

    //Replace NPCs with NPC spawners.
    for(int i=0;i<vector_npcs.size();i++){
        bool should_become_spawner=true;
        bool disallow_doubles=false;

        if(vector_npcs[i].invulnerable || vector_npcs[i].counts_as_trap || vector_npcs[i].BOSS ||
           !vector_npcs[i].exists){
            should_become_spawner=false;
        }

        if(vector_npcs[i].type==NPC_SHARK){
            should_become_spawner=true;
        }

        if(vector_npcs[i].type==NPC_MONKEY || vector_npcs[i].type==NPC_ARCHER || vector_npcs[i].type==NPC_SCARECROW ||
           vector_npcs[i].type==NPC_EAGLE){
            disallow_doubles=true;
        }

        if(should_become_spawner){
            vector_spawners.push_back(Spawner(SPAWN_NPC,vector_npcs[i].type,vector_npcs[i].x,vector_npcs[i].y,disallow_doubles,false));

            vector_npcs.erase(vector_npcs.begin()+i);
            i--;
        }
    }

    //Reset the item counts for the current level, as these will be loaded next.
    leaves=0;
    cheese=0;
    player.current_level_leaves=0;
    player.current_level_cheese=0;
    player.x=-1;
    player.y=-1;
    for(int i=0;i<mp_players.size();i++){
        mp_players[i].x=-1;
        mp_players[i].y=-1;
    }

    //Compile a list of spawn points.
    vector<int> spawns;
    for(int i=0;i<vector_items.size();i++){
        if(vector_items[i].type==ITEM_SPAWNPOINT){
            spawns.push_back(i);
        }
    }

    //If there are no spawns, just spawn at the default location.
    if(spawns.size()==0){
        player.x=0;
        player.y=0;

        for(int i=0;i<mp_players.size();i++){
            mp_players[i].x=0;
            mp_players[i].y=0;
        }
    }
    //Otherwise, choose one randomly and spawn at it.
    else{
        int random_spawn=random_range(0,spawns.size()-1);

        player.x=vector_items[spawns[random_spawn]].x;
        player.y=vector_items[spawns[random_spawn]].y-14;

        for(int i=0;i<mp_players.size();i++){
            mp_players[i].x=vector_items[spawns[random_spawn]].x;
            mp_players[i].y=vector_items[spawns[random_spawn]].y-14;
        }
    }

    player.camera_trap_x=player.x;
    player.camera_trap_y=player.y+player.h-CAMERA_TRAP_H;

    //Snap the camera to the starting position.
    player.set_camera();

    //Load this level's images.
    if(player.previous_level!=player.current_level){
        image.load_images_level(player.current_level,player.current_sub_level,player.non_number_level);
    }
}


#include <booster/booster.h>
#include <booster/helper.h>

#include "utils.h"
// #define TEST_SGECONV
#define TEST_WINOGRADF63FUSED
int test_general_conv_kernels(int output_channels, int input_channels, int input_h, int input_w, int kernel_h, int kernel_w, int stride, int pad, int nloops)
{
    booster::ConvParam conv_param;
    // conv_param
    conv_param.output_channels = output_channels;
    conv_param.input_channels = input_channels;
    conv_param.input_h = input_h;
    conv_param.input_w = input_w;
    conv_param.kernel_h = kernel_h;
    conv_param.kernel_w = kernel_w;
    conv_param.stride_h = stride;
    conv_param.stride_w = stride;
    conv_param.pad_left = pad;
    conv_param.pad_bottom = pad;
    conv_param.pad_right = pad;
    conv_param.pad_top = pad;
    conv_param.group = 1;
    // conv_param.bias_term = true;
    conv_param.bias_term = false;
    conv_param.AssignOutputDim();
    conv_param.activation = booster::None;
    conv_param.LogParams("TEST");

    float* kernel_data = (float*) malloc(sizeof(float) * conv_param.kernel_h * conv_param.kernel_w * conv_param.input_channels * conv_param.output_channels);
    rand_fill<float>(kernel_data, conv_param.kernel_h * conv_param.kernel_w * conv_param.input_channels * conv_param.output_channels);
    // float* naive_processed_kernel = (float*) malloc(sizeof(float) * conv_param.kernel_h * conv_param.kernel_w * conv_param.input_channels * conv_param.output_channels);
    // float* im2col_processed_kernel = (float*) malloc(sizeof(float) * conv_param.kernel_h * conv_param.kernel_w * conv_param.input_channels * conv_param.output_channels);

    float* input_data = (float*) malloc(sizeof(float) * conv_param.input_channels * conv_param.input_h * conv_param.input_w);
    rand_fill<float>(input_data, conv_param.input_channels * conv_param.input_h * conv_param.input_w);
    float* output_data_1 = (float*) malloc(sizeof(float) * conv_param.output_channels * conv_param.output_h * conv_param.output_w);
    float* output_data_2 = (float*) malloc(sizeof(float) * conv_param.output_channels * conv_param.output_h * conv_param.output_w);
    float* output_data_3 = (float*) malloc(sizeof(float) * conv_param.output_channels * conv_param.output_h * conv_param.output_w);
    float* output_data_4 = (float*) malloc(sizeof(float) * conv_param.output_channels * conv_param.output_h * conv_param.output_w);

    float* bias_data = (float*) malloc(sizeof(float) * conv_param.output_channels);

    rand_fill<float>(bias_data, conv_param.output_channels);
    //Initialize
    int buffer_size = 0;
    int processed_kernel_size = 0;
#ifdef TEST_NAIVE
    printf("Initializing naive...\n");
    booster::ConvBooster naive_booster;
    naive_booster.ForceSelectAlgo(booster::NAIVE);
    naive_booster.GetBufferSize(&conv_param, &buffer_size, &processed_kernel_size);
    float* naive_processed_kernel = (float*) malloc(sizeof(float) * processed_kernel_size);
    float* naive_buffer = (float*) malloc(sizeof(float) * buffer_size);
    naive_booster.Init(&conv_param, naive_processed_kernel, kernel_data);
#endif

    printf("Initializing im2col...\n");
    booster::ConvBooster im2col_booster;
    im2col_booster.ForceSelectAlgo(booster::IM2COL);
    im2col_booster.GetBufferSize(&conv_param, &buffer_size, &processed_kernel_size);
    float* im2col_processed_kernel = (float*) malloc(sizeof(float) * processed_kernel_size);
    float* im2col_buffer = (float*) malloc(sizeof(float) * buffer_size);
    im2col_booster.Init(&conv_param, im2col_processed_kernel, kernel_data);

#ifdef TEST_SGECONV
    printf("Initializing sgeconv...\n");
    booster::ConvBooster sgeconv_booster;
    sgeconv_booster.ForceSelectAlgo(booster::SGECONV);
    sgeconv_booster.GetBufferSize(&conv_param, &buffer_size, &processed_kernel_size);
    float* sgeconv_processed_kernel = (float*) malloc(sizeof(float) * processed_kernel_size);
    float* sgeconv_buffer = (float*) malloc(sizeof(float) * buffer_size);
    sgeconv_booster.Init(&conv_param, sgeconv_processed_kernel, kernel_data);
#endif

#ifdef TEST_WINOGRADF63FUSED
    printf("Initializing fused Winograd F(6x6, 3x3)...\n");
    booster::ConvBooster winogradf63fused_booster;
    winogradf63fused_booster.ForceSelectAlgo(booster::WINOGRADF63FUSED);
    winogradf63fused_booster.GetBufferSize(&conv_param, &buffer_size, &processed_kernel_size);
    float* winogradf63fused_processed_kernel = (float*) malloc(sizeof(float) * processed_kernel_size);
    float* winogradf63fused_buffer = (float*) malloc(sizeof(float) * buffer_size);
    winogradf63fused_booster.Init(&conv_param, winogradf63fused_processed_kernel, kernel_data);
#endif

    //Forward
#ifdef TEST_NAIVE
    printf("Forward naive...\n");
    naive_booster.Forward(&conv_param, output_data_1, input_data, naive_processed_kernel, naive_buffer, bias_data);
#endif

    printf("Forward im2col...\n");
    Timer tmr;
    tmr.startBench();
    for (int i = 0; i < nloops; ++i)
        im2col_booster.Forward(&conv_param, output_data_2, input_data, im2col_processed_kernel, im2col_buffer, bias_data);
    double im2col_time_ms = tmr.endBench() / nloops;
    double im2col_gflops = conv_param.GetFLOPS() / im2col_time_ms / 1.0e6;
    printf("IM2COL spent %lfms at %5.3lfGFLOPS.\n", im2col_time_ms, im2col_gflops);

#ifdef TEST_SGECONV
    printf("Forward sgeconv...\n");
    sgeconv_booster.Forward(&conv_param, output_data_3, input_data, sgeconv_processed_kernel, sgeconv_buffer, bias_data);
#endif

#ifdef TEST_WINOGRADF63FUSED
    printf("Forward fused Winograd F(6x6, 3x3)...\n");
    tmr.startBench();
    Timer inner_tmr;
    for (int i = 0; i < nloops; ++i)
    {
        inner_tmr.startBench();
        winogradf63fused_booster.Forward(&conv_param, output_data_4, input_data, winogradf63fused_processed_kernel, winogradf63fused_buffer, bias_data);
        inner_tmr.endBench("WINOGRADF63FUSED spent");
    }
    double winogradf63fused_time_ms = tmr.endBench() / nloops;
    double winogradf63fused_gflops = conv_param.GetFLOPS() / winogradf63fused_time_ms / 1.0e6;
    printf("WINOGRADF63FUSED spent %lfms at %5.3lfGFLOPS.\n", winogradf63fused_time_ms, winogradf63fused_gflops);
#endif

    // Check results
#ifdef TEST_NAIVE
    diff(output_data_1, output_data_2, conv_param.output_channels * conv_param.output_w * conv_param.output_h);
#endif
#ifdef TEST_SGECONV
    diff(output_data_2, output_data_3, conv_param.output_channels * conv_param.output_w * conv_param.output_h);
#endif
#ifdef TEST_WINOGRADF63FUSED
    diff(output_data_2, output_data_4, conv_param.output_channels * conv_param.output_w * conv_param.output_h);
#endif
    //Cleanup
#ifdef TEST_NAIVE
    free(naive_processed_kernel);
    free(naive_buffer);
#endif
    free(im2col_processed_kernel);
    free(im2col_buffer);
#ifdef TEST_SGECONV
    free(sgeconv_processed_kernel);
    free(sgeconv_buffer);
#endif
#ifdef TEST_WINOGRADF63FUSED
    free(winogradf63fused_processed_kernel);
    free(winogradf63fused_buffer);
#endif
    free(kernel_data);
    free(input_data);
    free(output_data_1);
    free(output_data_2);
    free(output_data_3);
    free(output_data_4);
    free(bias_data);

    return 0;
}

int main()
{
    // test_general_conv_kernels(4, 16, 12, 12, 3, 3, 1, 1, 1);
    test_general_conv_kernels(1024, 1280, 18, 18, 3, 3, 1, 1, 30);
    test_general_conv_kernels(1024, 1280, 18, 18, 3, 3, 1, 1, 1);
    test_general_conv_kernels(64, 64, 224, 224, 3, 3, 1, 1, 30);
    test_general_conv_kernels(64, 64, 224, 224, 3, 3, 1, 1, 1);

    return 0;
}


/********
 * Author: Liheng Gong(lg2848@nyu.edu)
 * This is the main program of this project.
 * Shader programs, VAOs, VBOs and VEOs are created and
 * data is binded to buffer.
 **********/
#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <Eigen/Core>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "camera.h"
#include "load_obj.h"
#include "Helpers.h"

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);

void generate_random_model_matrices(glm::mat4* model_matrices, unsigned int amount);
void config_instanced_array_buffer(VertexArrayObject& VAO,
                                   glm::mat4* model_matrices,
                                   unsigned int amount);
void config_instanced_array_buffer2(VertexArrayObject& VAO,
                                   Eigen::MatrixXf model_matrices,
                                   unsigned int amount);
void generate_random_model_matrices2(Eigen::MatrixXf& model_matrices, unsigned int amount);
void draw_planet(Program& shader, unsigned int planet_texture_id,
                 VertexArrayObject& VAO, unsigned int indices_size);
void draw_asteroids(Program& shader, VertexArrayObject& VAO,
                    unsigned int rock_texture_id, unsigned int indices_size,
                    int amount);
void enable_bind_vertext_attrib_pointers2(
    VertexArrayObject& VAO,
    VertexBufferObject& VBO,
    ElementArrayBufferObject& EBO,
    const Eigen::MatrixXf& vertices,
    const Eigen::MatrixXi& indices
);

const unsigned int SCR_WIDTH = 1280;
const unsigned int SCR_HEIGHT = 720;
extern const char* asteroid_vertexcode;
extern const char* asteroid_fragmentcode;
extern const char* planet_vertexcode;
extern const char* planet_fragmentcode;

// camera
Camera camera(glm::vec3(0.0f, 0.0f, 155.0f));
float lastX = (float)SCR_WIDTH / 2.0;
float lastY = (float)SCR_HEIGHT / 2.0;
bool firstMouse = true;

// timing
float deltaTime = 0.0f;
float lastFrame = 0.0f;

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // GLFW captures the mouse
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    glEnable(GL_DEPTH_TEST);

    Program asteroid_shader;
    asteroid_shader.init(asteroid_vertexcode,
                         asteroid_fragmentcode,
                         "FragColor");

    Program planet_shader;
    planet_shader.init(planet_vertexcode,
                       planet_fragmentcode,
                       "FragColor");

    std::string planet_filename = "../data/objects/planet/planet.obj";
    std::string rock_filename = "../data/objects/rock/rock.obj";
    std::string rock_texturename = "../data/objects/rock/rock.png";
    std::string planet_texturename = "../data/objects/planet/planet_Quom1200.png";

    Eigen::MatrixXf rock_vertices = Eigen::MatrixXf::Identity(5, 20);
    Eigen::MatrixXf rock_uvs1 = Eigen::MatrixXf::Identity(2, 20);
    Eigen::MatrixXi rock_indices = Eigen::MatrixXi::Identity(3, 20);
    Eigen::MatrixXf rock_normals = Eigen::MatrixXf::Identity(3, 20);
    load_obj_mesh_data_new(
        rock_filename,
        rock_vertices,
        rock_indices,
        rock_normals
    );

    unsigned int rock_texture_id;
    load_texture(rock_texturename, rock_texture_id);
    std::cout << "rock texture id is : " << rock_texture_id << "\n";

    VertexArrayObject r_VAO;
    VertexBufferObject r_VBO;
    ElementArrayBufferObject r_EBO;
    enable_bind_vertext_attrib_pointers2(
        r_VAO,
        r_VBO,
        r_EBO,
        rock_vertices,
        rock_indices
    );

    Eigen::MatrixXf planet_vertices = Eigen::MatrixXf::Identity(5, 20);
    Eigen::MatrixXf planet_uvs = Eigen::MatrixXf::Identity(2, 20);
    Eigen::MatrixXi planet_indices = Eigen::MatrixXi::Identity(3, 20);
    Eigen::MatrixXf planet_normals = Eigen::MatrixXf::Identity(3, 20);

    load_obj_mesh_data_new(
        planet_filename,
        planet_vertices,
        planet_indices,
        planet_normals
    );

    unsigned int planet_texture_id;
    load_texture(planet_texturename, planet_texture_id);
    std::cout << "planet texture id is : " << planet_texture_id << "\n";

    VertexArrayObject p_VAO;
    VertexBufferObject p_vVBO;
    VertexBufferObject p_tVBO;
    ElementArrayBufferObject p_EBO;
    enable_bind_vertext_attrib_pointers2(
        p_VAO,
        p_vVBO,
        p_EBO,
        planet_vertices,
        planet_indices
    );

    unsigned int amount = 10;
    glm::mat4* model_matrices;
    model_matrices = new glm::mat4[amount];
    srand(glfwGetTime());
    // generate_random_model_matrices(model_matrices, amount);

    Eigen::MatrixXf eigen_model_matrices = Eigen::MatrixXf(4, 4 * amount);
    generate_random_model_matrices2(eigen_model_matrices, amount);

    // Eigen::MatrixXf eigen_model_matrices = Eigen::MatrixXf(4, 4 * amount);
    // for (int i = 0; i < amount; ++i) {
    //     for (int row = 0; row < 4; ++row) {
    //         for (int col = 0; col < 4; ++col) {
    //             eigen_model_matrices(row, col + i * 4) = model_matrices[i][col][row];
    //         }
    //     }
    // }

    std::cout << "eigen::::::::\n";
    // std::cout << eigen_model_matrics.transpose() << std::endl;

    config_instanced_array_buffer2(
        r_VAO, eigen_model_matrices, amount
        );

    while (!glfwWindowShouldClose(window))
    {

        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        processInput(window);

        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 1000.0f);
        glm::mat4 view = camera.GetViewMatrix();

        asteroid_shader.bind();
        glUniformMatrix4fv(
            asteroid_shader.uniform("projection"),
            1, GL_FALSE, &projection[0][0]
            );

        glUniformMatrix4fv(
            asteroid_shader.uniform("view"),
            1, GL_FALSE, &view[0][0]
            );

        planet_shader.bind();

        glUniformMatrix4fv(
            planet_shader.uniform("projection"),
            1, GL_FALSE, &projection[0][0]
            );

        glUniformMatrix4fv(
            planet_shader.uniform("view"),
            1, GL_FALSE, &view[0][0]
            );

        draw_planet(
            planet_shader,
            planet_texture_id,
            p_VAO,
            planet_vertices.cols()
            );
        check_gl_error();

        draw_asteroids(
            asteroid_shader,
            r_VAO,
            rock_texture_id,
            rock_vertices.cols(),
            amount);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

void enable_bind_vertext_attrib_pointers2(
    VertexArrayObject& VAO,
    VertexBufferObject& VBO,
    ElementArrayBufferObject& EBO,
    const Eigen::MatrixXf& vertices,
    const Eigen::MatrixXi& indices
) {
    // init buffers
    VAO.init();
    VBO.init();
    EBO.init();

    // bind buffers and data
    VAO.bind();

    VBO.update(vertices);

    // Textures
    EBO.bind();
    EBO.update(indices);

    // Describe buffer data format
    // vertices coordinates
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);

    // Texture coordinates
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));

    glBindVertexArray(0);
}

void generate_random_model_matrices(glm::mat4* model_matrices, unsigned int amount) {
    float radius = 150.0f;
    float offset = 25.0f;

    for (unsigned int i = 0; i < amount; i++)
    {
        glm::mat4 model;
        // 1. translation: displace along circle with 'radius' in range [-offset, offset]
        float angle = (float)i / (float)amount * 360.0f;
        float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float x = sin(angle) * radius + displacement;
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float y = displacement * 0.4f; // keep height of asteroid field smaller compared to width of x and z
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float z = cos(angle) * radius + displacement;
        model = glm::translate(model, glm::vec3(x, y, z));

        // 2. scale: Scale between 0.05 and 0.25f
        float scale = (rand() % 20) / 100.0f + 0.05;
        model = glm::scale(model, glm::vec3(scale));

        // 3. rotation: add random rotation around a (semi)randomly picked rotation axis vector
        float rotAngle = (rand() % 360);
        model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));

        // 4. now add to list of matrices
        model_matrices[i] = model;
    }
}

void translate(Eigen::Matrix4f& model, float dis_x, float dis_y, float dis_z) {
  Eigen::Matrix4f trans = Eigen::Matrix4f::Identity();
  trans.col(3) << dis_x, dis_y, dis_z, 1.0;
  model = trans * model;
}

void scale_model(Eigen::Matrix4f& model, float scale_x, float scale_y, float scale_z) {
    Eigen::Matrix4f scale_mat = Eigen::Matrix4f::Identity();
    scale_mat(0, 0) = scale_x;
    scale_mat(1, 1) = scale_y;
    scale_mat(2, 2) = scale_z;
    model = scale_mat * model;
}

void generate_random_model_matrices2(Eigen::MatrixXf& model_matrices, unsigned int amount) {
    float radius = 150.0f;
    float offset = 25.0f;

    for (unsigned int i = 0; i < amount; i++)
    {
        Eigen::Matrix4f model;
        // 1. translation: displace along circle with 'radius' in range [-offset, offset]
        float angle = (float)i / (float)amount * 360.0f;
        float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float x = sin(angle) * radius + displacement;
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float y = displacement * 0.4f; // keep height of asteroid field smaller compared to width of x and z
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float z = cos(angle) * radius + displacement;
        // model = glm::translate(model, glm::vec3(x, y, z));
        translate(model, x, y, z);

        // 2. scale: Scale between 0.05 and 0.25f
        float scale = (rand() % 20) / 100.0f + 0.05;
        // model = glm::scale(model, glm::vec3(scale));
        scale_model(model, scale, scale, scale);

        // 3. rotation: add random rotation around a (semi)randomly picked rotation axis vector
        // float rotAngle = (rand() % 360);
        // model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));

        // 4. now add to list of matrices
        // model_matrices[i] = model;
        for (int row_ind = 0; row_ind < 4; ++row_ind) {
            for (int col_ind = 0; col_ind < 4; ++col_ind) {
                model_matrices(row_ind, col_ind + i * 4) = model(row_ind, col_ind);
            }
        }
    }
}

void config_instanced_array_buffer(VertexArrayObject& VAO,
                                   glm::mat4* model_matrices,
                                   unsigned int amount) {
    unsigned int buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(
        GL_ARRAY_BUFFER,
        amount * sizeof(glm::mat4),
        &model_matrices[0], GL_STATIC_DRAW);

    glBindVertexArray(VAO.id);
    unsigned int vec_size = sizeof(glm::vec4);
    unsigned int mat_size = sizeof(glm::mat4);
    for (int i = 3; i < 7; ++i) {
        glEnableVertexAttribArray(i);
        glVertexAttribPointer(
            i, 4, GL_FLOAT, GL_FALSE, mat_size,
            (void*)((int(i - 3)) * vec_size));
        glVertexAttribDivisor(i, 1);
    }

    glBindVertexArray(0);

    check_gl_error();

}

void config_instanced_array_buffer2(VertexArrayObject& VAO,
                                   Eigen::MatrixXf model_matrices,
                                   unsigned int amount) {
    unsigned int buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    // glBufferData(
    //     GL_ARRAY_BUFFER,
    //     amount * sizeof(glm::mat4),
    //     &model_matrices[0], GL_STATIC_DRAW);
    glBufferData(
        GL_ARRAY_BUFFER,
        model_matrices.size() * sizeof(float),
        model_matrices.data(),
        GL_STATIC_DRAW
    );

    glBindVertexArray(VAO.id);
    // unsigned int vec_size = sizeof(glm::vec4);
    // unsigned int mat_size = sizeof(glm::mat4);
    // for (int i = 3; i < 7; ++i) {
    //     glEnableVertexAttribArray(i);
    //     glVertexAttribPointer(
    //         i, 4, GL_FLOAT, GL_FALSE, mat_size,
    //         (void*)((int(i - 3)) * vec_size));
    //     glVertexAttribDivisor(i, 1);
    // }
    glEnableVertexAttribArray(3);
    glVertexAttribPointer(
        3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0
        );
    glEnableVertexAttribArray(4);
    glVertexAttribPointer(
        4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4),
        (void*)(sizeof(glm::vec4))
        );
    glEnableVertexAttribArray(5);
    glVertexAttribPointer(
        5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4),
        (void*)(2 * sizeof(glm::vec4))
        );
    glEnableVertexAttribArray(6);
    glVertexAttribPointer(
        6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4),
        (void*)(3 * sizeof(glm::vec4))
        );

    glVertexAttribDivisor(3, 1);
    glVertexAttribDivisor(4, 1);
    glVertexAttribDivisor(5, 1);
    glVertexAttribDivisor(6, 1);

    glBindVertexArray(0);

    check_gl_error();

}

void draw_planet(Program& shader, unsigned int planet_texture_id,
                 VertexArrayObject& VAO, unsigned int indices_size) {
    glm::mat4 model;
    model = glm::translate(model, glm::vec3(0.0f, -3.0f, 0.0f));
    model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f));
    glUniformMatrix4fv(shader.uniform("model"), 1, GL_FALSE, &model[0][0]);
    glActiveTexture(GL_TEXTURE0);
    glUniform1i(shader.uniform("texture_diffuse1"), 0);
    glBindTexture(GL_TEXTURE_2D, planet_texture_id);
    glBindVertexArray(VAO.id);
    glDrawElements(GL_TRIANGLES, indices_size, GL_UNSIGNED_INT, 0);

    glBindVertexArray(0);

    glActiveTexture(GL_TEXTURE0);
    check_gl_error();
}

void draw_asteroids(Program& shader, VertexArrayObject& VAO,
                    unsigned int rock_texture_id, unsigned int indices_size,
                    int amount) {
    shader.bind();

    glUniform1i(shader.uniform("texture_diffuse1"), 0);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, rock_texture_id);

    glBindVertexArray(VAO.id);
    glDrawElementsInstanced(GL_TRIANGLES, indices_size, GL_UNSIGNED_INT, 0, amount);

    glBindVertexArray(0);
    check_gl_error();
}

void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);

    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos;

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(yoffset);
}