#include <conio.h>

#ifdef __TURBOC__
    // Turbo C++ stuff
    #include <iostream.h>
    #include <math.h>
    #include <graphics.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <dos.h>

    int maxx, maxy;
    void DrawCircle(int X, int Y, int Size, int Color)
    {
        setcolor(Color);
        circle(X, Y, Size);
    }
    void DrawPixel(int X, int Y, int Color)
    {
        putpixel(X, Y, Color);
    }
#else
    // GLUT stuff
    #include <cmath>
    #include <ctime>
    #include "glut.h"

    const int maxx = 640;
    const int maxy = 480;

    const GLfloat YELLOW[3] = {1.0f, 1.0f, 0.0f};
    const GLfloat BLACK[3] = {0.0f, 0.0f, 0.0f};
    const GLfloat LIGHTGREEN[3] = {0.0f, 1.0f, 0.0f};
    const GLfloat LIGHTGRAY[3] = {1.0f, 1.0f, 1.0f};
    const GLfloat WHITE[3] = {1.0f, 1.0f, 1.0f};

    void DrawCircle(int X, int Y, int Size, const GLfloat *Color)
    {
        glEnable(GL_POINT_SMOOTH);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glPointSize(Size);
        glBegin(GL_POINTS);
        glColor3fv(Color);
        glVertex2f(X, Y);
        glEnd();
    }

    void DrawPixel(int X, int Y, const GLfloat *Color)
    {
        DrawCircle(X, Y, 1, Color);
    }
#endif


class Celestial
{
protected:
    double X, Y;
    int Size;
    virtual void Show(int) = 0;
    virtual void Recalc() { ; }
public:
    Celestial(double px, double py, int psize);
    double getX() const { return X; }
    double getY() const { return Y; }
    int getSize() const { return Size; }

    void Update();
};

Celestial :: Celestial(double px, double py, int psize) :
    X(px), Y(py), Size(psize)
{
}
void Celestial :: Update()
{
    #ifdef __TURBOC__
        Show(0); // в GLUT не нужно удалять старое изображение
    #endif
    Recalc();
    Show(1);
}

double dist(Celestial &a, Celestial &b)
{
    double dx = a.getX() - b.getX();
    double dy = a.getY() - b.getY();
    return sqrt(dx * dx + dy * dy);
}

class Star : public Celestial
{
    int Shown;
public:
    Star(int psize) :
        Celestial(maxx / 2, maxy / 2, psize), Shown(0)
    {
    }
    virtual void Show(int);
};
void Star :: Show(int bShow)
{
    DrawCircle((int)X, (int)Y, Size, YELLOW);
}
Star *sun;
class Planet : public Celestial
{
private:
   int R;
   const char *name;
   double Angle, DeltaAngle;

protected:
   virtual void Recalc()
   {
      Angle -= DeltaAngle;
      if(Angle < 0) Angle += 360;

      X = maxx/2 + R*cos(Angle*M_PI/180);
      Y = maxy/2 + R*sin(Angle*M_PI/180);
   }

public:
   Planet(double px, double py, int psize,
	  int pr, double pdeltaangle, const char *pname);
   virtual void Show(int);
};
Planet :: Planet(double px,double py, int psize,
		 int pr, double pdeltaangle, const char *pname) :
		 Celestial(px, py, psize),
		 R(pr), name(pname), Angle(360), DeltaAngle(pdeltaangle)
{
}


void Planet :: Show(int bShow)
{
    DrawCircle((int)X, (int)Y, Size, (bShow ? LIGHTGREEN : BLACK));
}
class Particle : public Celestial
{
private:
   double Vx, Vy;
public:
   Particle(double px, double py, double pvx, double pvy);
   ~Particle();
   virtual void Show(int);
   virtual void Recalc();
};
Particle :: Particle(double px, double py, double pvx, double pvy) :
	    Celestial(px, py, 1),
	    Vx(pvx), Vy(pvy)
{
}
Particle :: ~Particle()
{
   Show(0);
}
void Particle :: Show(int bShow)
{
    DrawPixel((int)X, (int)Y, (bShow ? LIGHTGRAY : BLACK));
}
void Particle :: Recalc()
{
   Vx += ((getX() - sun->getX()) / dist(*sun, *this));
   Vy += ((getY() - sun->getY()) / dist(*sun, *this));
   X += Vx;
   Y += Vy;
}

const int parrSize = 1500;
const int stepParticles = 15;
const int maxPartSpeed = 3;

class Comet : public Celestial
{
private:
    double Ex, Ey;
    int Vx, Vy;
    Particle *trace[parrSize];
    int maxParticles;
    int Cvt(int curr);
protected:
    virtual void Recalc();
public:
    Comet(double px, double py, int psize, int pvx, int pvy);
    virtual void Show(int);
    int OutOfSystem()
    {
        return (X < -5 || X > maxx + 5 || Y < -5 || Y > maxy + 5) ? 1 : 0;
    }
};

Planet *earth, *mars;
Comet *c_01;

int Comet :: Cvt(int curr)
{
    int sign;
    if(!curr) sign = 0;
    else sign = curr / abs(curr);
    return sign * (maxPartSpeed - abs(curr)) ? (rand() % (maxPartSpeed - abs(curr))) : 0;
}

Comet :: Comet(double px, double py, int psize, int pvx, int pvy) :
    Celestial(px, py, psize),
    Vx(pvx), Vy(pvy), maxParticles(100)
{
    Ex = (sun->getX() - sun->getSize()) / maxPartSpeed;
    Ey = (sun->getY() - sun->getSize()) / maxPartSpeed;
    for(int i = 0; i < maxParticles; i++)
    {
        double dx = (px - sun->getX());
        double dy = (py - sun->getY());
        trace[i] = new Particle(px - 1 + (rand() % 2), py - 1 + (rand() % 2),
                                Vx + Cvt((int)(dx / Ex)) - 1 + (rand() % 2),
                                Vy + Cvt((int)(dy / Ey)) - 1 + (rand() % 2));
    }
}
void Comet :: Recalc()
{
    X += Vx;
    Y += Vy;
    int count = 0;
    for(int i = 0; i < maxParticles; i++)
    {
        trace[i]->Recalc();
        double d = dist(*trace[i], *this);
        if(d > 25 * Size)
        {
            delete trace[i];
            count += 1;
            trace[i] = new Particle((int)(getX()) - 1 + (rand() % 2), (int)(getY()) - 1 + (rand() % 2),
                                    Vx + Cvt((int)((getX() - sun->getX()) / Ex)) - 1 + (rand() % 2),
                                    Vy + Cvt((int)((getY() - sun->getY()) / Ey)) - 1 + (rand() % 2));
        }
    }
    for(int j = 0; j <= stepParticles - count; j++)
    {
        trace[maxParticles + j] = new Particle((int)(getX()) - 1 + (rand() % 2), (int)(getY()) - 1 + (rand() % 2),
                                               Vx + Cvt((int)((getX() - sun->getX()) / Ex)) - 1 + (rand() % 2),
                                               Vy + Cvt((int)((getY() - sun->getY()) / Ey)) - 1 + (rand() % 2));
    }
    if(stepParticles - count > 0)
        maxParticles += (stepParticles - count);
}

void Comet :: Show(int bShow)
{
    DrawCircle((int)X, (int)Y, Size, (bShow ? WHITE : BLACK));
    for(int i = 0; i < maxParticles; i++) trace[i]->Show(bShow);
}

class Observer
{
public:
    Observer();
    ~Observer();
    void Run();
    void Cycle();
};

Observer :: Observer()
{
    sun = new Star(10);
    earth = new Planet(maxx / 2.+ 3 * 20, maxy / 2., 3, 60, 360./365., "earth");
    mars = new Planet(maxx / 2.+ 4 * 20, maxy / 2., 3, 80, 360./687., "mars");
    c_01 = new Comet(rand()%maxx, maxy, 2, 5, -5);
}

Observer :: ~Observer()
{
    #ifdef __TURBOC__
        closegraph();
    #endif
}

void Observer::Cycle()
{
    sun->Update();
    earth->Update();
    mars->Update();
    c_01->Update();

    if(c_01->OutOfSystem()) // Проверяем, если вышли за пределы - то пересоздаем комету...
    {
        delete c_01;
        c_01 = new Comet(rand()%maxx, maxy, 2, 5, -5);
    }
}

void Observer::Run()
{
    #ifdef __TURBOC__
    // Это не нужно делать в GLUT, это работа таймера
    while(1)
    {
        Cycle();

        if(kbhit())
        {
            if(getch() == 27) break;
        }
        delay(5);
    }
    #endif
}

Observer *engine;

#ifdef __TURBOC__
#else
void display()
{
    glClear(GL_COLOR_BUFFER_BIT);
    //
    engine->Cycle();
    glutSwapBuffers();
}
void timer(int = 0)
{
    glutPostRedisplay();
    //
    glutTimerFunc(1, timer, 0);
}
#endif

int main(int argc, char** argv)
{
    #ifdef __TURBOC__
        // Старая ламповая BGI-графика - под Турбо С++
        int gdriver = DETECT, gmode, errorcode;
        initgraph(&gdriver, &gmode, "../BGI");
        errorcode = graphresult();
        if(errorcode != grOk)
        {
            cerr << "Graphics error: " << grapherrormsg(errorcode) << endl;
            exit(-1);
        }
        maxx = getmaxx(); maxy = getmaxy();
        randomize();
    #else
        // Это не для ДОС
        glutInit(&argc, argv);
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
        glutInitWindowSize(maxx, maxy);
        glutInitWindowPosition(100, 100);
        glutCreateWindow("Solar System");
        glClearColor(0, 0, 0, 1.0);
        glLoadIdentity();
        glOrtho(0, maxx, maxy, 0, -1, 1);

        srand(time(0));
    #endif

    // Инициализируем основной класс и запускаем главный цикл:
    engine = new Observer();

    #ifdef __TURBOC__
        engine->Run();
        delete engine;
    #else
        glutDisplayFunc(display);
        timer();
        glutMainLoop();
    #endif
    return 0;
}