/*
 *  Copyright (C) 2008 Kristian Kristola
 *
 *  This program is distributed under the terms of the
 *  GNU General Public License.
 *
 *  This file is part of Dave the Ordinary Spaceman.
 *
 *  Dave the Ordinary Spaceman is free software: you can redistribute
 *  it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation, either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  Dave the Ordinary Spaceman is distributed in the hope that it
 *  will be useful, but WITHOUT ANY WARRANTY; without even the
 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Dave the Ordinary Spaceman. If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

#ifndef _LOISTAVAN_ERINOMAINEN_VAAPUKKAMEHU_EI_LOISKU_
#define _LOISTAVAN_ERINOMAINEN_VAAPUKKAMEHU_EI_LOISKU_

#include <string>
#include <list>
#include <utility>
#include <map>
#include <string>
#include <iostream>
#include <vector>
#include <set>
#if defined(__MACOS__) || defined(__APPLE__)
#include <SDL_mixer.h>
#else
#include <SDL/SDL_mixer.h>
#endif
#include "vector.h"
#include "image.h"
#include "analog_effects.h"

#define LEVEL_COUNT 4

using namespace std;

class Fellow;
class Scene;
class Model;
class Starfield;
class Teletext;

/*
class LevelBlockPurkka
{
};
*/

class Level : private Effect
{
public:
  Level();
  Level(const string &name);
  virtual ~Level();
  void init();

  void render() const;
  void render_background_effect() const;
  void handle(float dt, float time);
  void begin_physics_sequence();
  Fellow *add_fellow(Fellow *f);
  pair<Vector, Vector> collide(Vector pos);    // Tyrmää pos:n kohdalla olevan 8x16-blokin kenttään
                                               // ja palauttaa törmäyskohdan ja törmäyspinnan normaalivektorin.
  class Block;
  const bool col_is_intersecting(const Vector &fel_pos, const Vector &fel_size) const;
  struct BlockInfo
  {
    float common_area;
    float projected_distance;
    Vector position;
    BlockInfo(const float ca, const float pd, const Vector &pos) :
      common_area(ca), projected_distance(pd), position(pos) {}

    struct comp
    {
      bool operator()(const BlockInfo o1, const BlockInfo o2)
      {
	if (o1.common_area != o2.common_area)
	  return o1.common_area > o2.common_area;
	else
	  return o1.projected_distance < o2.projected_distance;
      }
    };
  };
  const multimap<BlockInfo, Block *, BlockInfo::comp>  // Nua kolme parametriä on nulleja jos pelataan bool-törmäyksillä, muuten vektoreita
    col_get_nearest_blocks(const Vector &point,
			   const Vector *fel_pos = NULL,
			   const Vector *fel_size = NULL,
			   const Vector *velocity = NULL,
			   const Block *exception = NULL) const;

  typedef map<Block *, bool> unique_blocks; // {block, has it been handled}  // It would be better if these
  typedef multimap<float, Block *> areas;   // {colliding area, block}       //    were hash structures
  void register_potentially_colliding_area(float time, const Vector &pos, const Vector &size, Block *target, unique_blocks *feedback_uniq, areas *feedback_areas);

  int get_size_x(int l=3) const;
  int get_size_y(int l=3) const;

  class Block /*: public LevelBlockPurkka*/
  {
  public:

    class Type
    {
    public:
      enum Family {
	STATIC,       // blocks
	DYNAMIC,      // fellows
	STATIC_COAT,  // items
	DYNAMIC_COAT  // ammo
      } family;
      static const unsigned int CODE;
      Type(const string &tname, const Family f = STATIC, const unsigned int flags = 0);
      ~Type();
      bool collidable;
      bool empty;
      bool obscured;
      bool end;
      bool big;
      int invisible;
      Image *image;
      int image_count;
      float animation_delay;
      Vector size;
      enum Geometry {
	RECTANGLE,
	R_TRIANGLE
      } geometry;
      int orientation;
      string name;
      static void insert(const string &name, const Family family = STATIC, const unsigned int flags = 0);
      static void insert(Type *const t);
      static Type *get(const string &name);
      static Type *get_obscured();
      static Type *get_empty();
      static void initialize();
      Family get_family() const { return family; }
      const vector<Type *> *get_similar_group() const;  // Slow
      int get_animation_frame_count(int animation) const;
      float get_animation_speed(int animation) const;
      const Image *get_animation_frame(int animation, int frame) const;
      int get_animation_count() const { return int(animations.size()); }
      string remove_number() const;
      string remove_anim_info() const;
      bool good() const;
    private:
      static map<string, Type *> pool;
      static Type *pool_obscured;
      static Type *pool_empty;
      string get_real_name() const;     // Very slow! Only used once per type by constructor
      string get_animations_owner_slowly();
      vector<Type *> similar_group;
      vector<vector<Image *> > animations;
      void insert_frame(int animation, int frame, Image *image);
      Type *animations_owner;
      float animation_speed;
    };

    Block();
    Block(Type *const t);
    Block(const Block &x);
    virtual ~Block();
    Block &operator=(const Block &x);

    void set_position(const int l, const Vector &p);
    void set_type(Type *const t);
    Type *get_type() const {return const_cast<Type *>(type);}
    Vector get_size() const;
    static const int MIRRORED;
    static const int NOT_TRANSFORMED;
    static const int MONOCHROME;
    void set_flags(int f, bool v) {if (v) flags |= f; else flags &= ~f;}
    void add_flags(int f) {flags |= f;}
    void bind_image() const;
    virtual void render() const;
    void render_credit() const;
    void render_frame() const;
    Vector collide(const Vector &fel_pos, const Vector &fel_size, bool check = true);
    const bool col_is_intersecting(const Vector &fel_pos, const Vector &fel_size) const;
    const float col_get_intersecting_area(const Vector &fel_pos, const Vector &fel_size) const;
    virtual bool handle_collision_events(Level::Block *against, const Vector &col_surf_normal, const Vector &original_velocity);
    void set_parent(Block *const p);
    Block *get_parent() const;
    void set_level(Level *const l);
    void blank() /*const*/;
    const Vector &get_position() const;
    int get_layer() const {return layer;}
    int get_animation_number() const { return current_animation; }
    void set_animation_number(int a) { current_animation = a; }
    virtual void die(const float t, int score);
    bool is_alive() const {return alive == ALIVE;}
    bool is_dying() const {return alive == DYING;}
    bool is_dead () const {return alive == DEAD ;}
    virtual float death_animation_state() const;
    void set_alive(bool x);

    struct Collisions
    {
      float as_of;
      Block *colliding_with[16];
      int count;
      Collisions() : as_of(-1337.0f), count(0) {}
    } collisions;

    int program;

  protected:
    Vector position;
    const Type *type;
    mutable enum Alive { ALIVE, DYING, DEAD } alive;
    float died_at;
    Teletext *credit;

  private:
    Level *level;
    int layer;
    Block *parent;
    int orientation;
    int current_animation;
    int flags;
    mutable float credit_rendered_at;
    class Image *get_image() const;
    void r_render() const;
  };

  virtual Block *get_block(const int x, const int y) const;
  virtual Block *get_block(const int l, const int x, const int y) const;
  static const int ITEM_LAYER, FELLOW_STP_LAYER;
  float *get_background();

  void save(ostream &stream) const;
  void load(istream &stream);
  void resize(int x, int y);
  void augment(int left, int right, int bottom, int top);
  void blank();
  int get_layer_count() const;
  void set_layer_opacity(const int l, const float o);
  float get_layer_opacity(const int l);
  Block *add_item(Block *i);
  list<Block *>::const_iterator items_begin()    const {return items.begin();  }
  list<Block *>::const_iterator items_end()      const {return items.end();    }
  list<Fellow *>::const_iterator fellows_begin() const {return fellows.begin();}
  list<Fellow *>::const_iterator fellows_end()   const {return fellows.end();  }
  list<Block *>::iterator items_begin()    {return items.begin();  }
  list<Block *>::iterator items_end()      {return items.end();    }
  list<Fellow *>::iterator fellows_begin() {return fellows.begin();}
  list<Fellow *>::iterator fellows_end()   {return fellows.end();  }
  void remove(Block *b);
  bool render_fellow_start_positions;
  void reset();
  Vector find_start_position(Block::Type *type) const;
  Mix_Music *get_music(bool reversed) const;
  void play_music(bool rewinding, bool same_as_before = false);
  void play_intro();
  void set_music_position(float x);
  float get_music_duration();
  float get_music_position();

  bool completed;

private:

  void load(const string &name);
  void remove_really(Block *b);

  class Size
  {
  public:
    Size() : w(0), h(0) {}
    Size(int x, int y) : w(x), h(y) {}
    ~Size() {}
    int w, h;
  } size;

  struct Layer
  {
    Block **blocks;
    Size size;
    bool used;
    float opacity;
    Layer();
    Layer(const Layer &l);
    ~Layer();
    void clear();
    void create(const Size &size);
    void format(int layer_num, Level *level);
    Block *get(int x, int y) const
    {
      if (x < 0 || y < 0 || x >= size.w || y >= size.h)
	return NULL;
      else
	return &blocks[x][y];
    }
    void augment(int left, int right, int bottom, int top, int layer_num, Level *level);
  };

  const Layer *generate_temporary_fellow_layer() const;
  const Layer *generate_temporary_item_layer() const;

  float background[3];
  vector<Layer> layers;
  Block **blocks;     // Pointteri niihin blokkeihin jotka törmäävät
  Layer fellow_start_positions;
  Layer item_layer;
  Layer *get_layer(int i)
  {return i>=0 ? &layers[i]
      : i==FELLOW_STP_LAYER ? &fellow_start_positions
      : i==ITEM_LAYER ? &item_layer : NULL;}
  list<Fellow *> fellows;
  list<Block *> items;
  list<Block *> to_remove;

  static Mix_Music *current_music[LEVEL_COUNT], *current_music_reversed[LEVEL_COUNT], *intro[LEVEL_COUNT];
  float music_start_time;
  bool playing_reverse_music;

  mutable Scene *background_scene;
  mutable Starfield *starfield;
  mutable Model *rocket;
  mutable Model *valley;
  mutable Model *sun;
  mutable GaussianBlur *gaussian;

public:
  static Mix_Chunk
    *ch_lever,
    *ch_lazor,
    *ch_pink,
    *ch_piuingg,
    *ch_random,
    *ch_tv_noise,
    *ch_tilu,
    *ch_charge,
    *ch_pack;
};

#endif
