/* NAME: Kirill Vasil'evich Timofeev, 322
 * ASGN: N1
 */

#include <QtGui>

 #include "mainwindow.h"

 MainWindow::MainWindow()
 {
     window = new QWidget;
     Hey = 768;
     Wid = 1024;
     AntiAl = false;
     labelIm = new QLabel;
     image = new QPixmap(Wid, Hey);
     curImage = new QImage(Wid, Hey, QImage::Format_ARGB32);
     CamPos = Vector3D((float)0, (float)0, (float)-1.5);


     makeFigures();
     makeLights();

     Paint();

     statusBar()->showMessage(tr("Rayed"), 2000);
     mainLayout = new QVBoxLayout;
     mainLayout->setSizeConstraint(QLayout::SetMinimumSize);
     labelIm->setPixmap(*image);
     mainLayout->addWidget(labelIm);
     window->setLayout(mainLayout);
     setCentralWidget(window);

     createActions();
     createMenus();
     createStatusBar();

     setUnifiedTitleAndToolBarOnMac(true);
 }

 MainWindow::~MainWindow()
 {

     delete image;
     delete curImage;
     delete labelIm;
     delete mainLayout;
     delete window;


 }


void MainWindow::exit()
{
    QMessageBox msgBox;
    msgBox.setWindowTitle("Exit");
    msgBox.setText("Thank you for using my Program)) Goodbye!!");
    msgBox.exec();
    close();
}

void MainWindow::createActions()
{

    exitAct = new QAction(tr("E&xit"), this);
    exitAct->setShortcuts(QKeySequence::Quit);
    exitAct->setStatusTip(tr("Exit poorPaint"));
    connect(exitAct, SIGNAL(triggered()), this, SLOT(exit()));

}


void MainWindow::createMenus()
{

    fileMenu = menuBar()->addMenu(tr("&File"));
    fileMenu->addAction(exitAct);

    menuBar()->addSeparator();

}

void MainWindow::createStatusBar()
{
    statusBar()->showMessage(tr("Ready"));
}

void MainWindow::makeFigures()
{

    Material1.SetRef(Vector3D(1.0f, 1.0f, 1.0f));
    Material1.SetCol(Vector3D(0.9f, 0.0f, 0.0f));
    Material2.SetSpe(Vector3D(0.0f, 0.0f, 0.0f));

    Material2.SetRef(Vector3D(0.3f, 0.3f, 0.3f));
    Material2.SetCol(Vector3D(0.0f, 0.2f, 0.7f));
    Material2.SetSpe(Vector3D(0.0f, 0.0f, 0.0f));

    Material3.SetRef(Vector3D(1.0f, 1.0f, 1.0f));
    Material3.SetCol(Vector3D(0.0f, 1.0f, 0.0f));
    Material3.SetSpe(Vector3D(0.0f, 0.9f, 0.0f));



    Sphere1.SetPos(Vector3D(3,0,3));
    Sphere1.SetRad(1.0f);
    Sphere1.SetMater(Material1);

    Sphere2.SetPos(Vector3D(0,0,3));
    Sphere2.SetRad(2.0f);
    Sphere2.SetMater(Material2);


    Cylinder1.SetPosCentr(Vector3D(2,2,2));
    Cylinder1.SetLen(3.0f);
    Cylinder1.SetRad(2.0f);
    Cylinder1.SetMater(Material3);

    Cylinder2.SetPosCentr(Vector3D(-2,-2,2));
    Cylinder2.SetLen(2.0f);
    Cylinder2.SetRad(2.0f);
    Cylinder2.SetMater(Material1);


    Thor1.SetPosCen(Vector3D(-2,1, 1));
    Thor1.SetRad(1.0f);
    Thor1.SetWid(0.4f);
    Thor1.SetMater(Material3);

    Thor2.SetPosCen(Vector3D(4,2, 3));
    Thor2.SetRad(0.8f);
    Thor2.SetWid(0.2f);
    Thor2.SetMater(Material2);

    Thor3.SetPosCen(Vector3D(4,-2, 3));
    Thor3.SetRad(0.4f);
    Thor3.SetWid(0.1f);
    Thor3.SetMater(Material1);


}

void MainWindow::makeLights()
{


    Light1.SetPos(Vector3D(4,0,2));
    Light1.SetColor(Vector3D(0.1f, 0.4f, 0.9f));
    Light1.SetConst(0.0f);
    Light1.SetLinear(0.05f);
    Light1.SetQuadric(0.05f);

    Light2.SetPos(Vector3D(-4,0,2));
    Light2.SetColor(Vector3D(0.9f, 0.4f, 0.1f));
    Light2.SetConst(0.0f);
    Light2.SetLinear(0.1f);
    Light2.SetQuadric(0.05f);

}


void MainWindow::keyPressEvent(QKeyEvent* pe)
{
   switch (pe->key())
   {

      case Qt::Key_Q:
          AntiAliasing();     // AA
          AntiAl = TRUE;
      return;
      case Qt::Key_Z:
         CamPos.Z -= 0.1;     // приблизить сцену
      break;

      case Qt::Key_X:
         CamPos.Z += 0.1;// удалиться от сцены
      break;

      case Qt::Key_S:
         CamPos.Y += 0.1; // транслировать сцену вниз
      break;

      case Qt::Key_W:
         CamPos.Y -= 0.1;   // транслировать сцену вверх
      break;

      case Qt::Key_A:
        CamPos.X -= 0.1;   // транслировать сцену влево
      break;

      case Qt::Key_D:
        CamPos.X += 0.1;   // транслировать сцену вправо
      break;

      case Qt::Key_Escape: // клавиша "эскейп"
         this->close();    // завершает приложение
      break;
    }

    Paint();
    AntiAl = false;
    repaint(); // обновление изображения
    statusBar()->showMessage(tr("Made"), 2000);

}

void MainWindow::wheelEvent(QWheelEvent* pe)
{
   if ((pe->delta())>0) CamPos.Z -= 0.1; else if ((pe->delta())<0) CamPos.Z += 0.1;

   Paint();
   AntiAl = false;
   repaint();  // обновление изображения
   statusBar()->showMessage(tr("Zoomed"), 2000);
}

void MainWindow::mousePressEvent(QMouseEvent* pe) // нажатие клавиши мыши
{
   ptrMousePosition = pe->pos();

}

// изменение положения стрелки мыши
void MainWindow::mouseMoveEvent(QMouseEvent* pe)
{
   // поворот
   CamPos.Y += (float)(pe->y()-ptrMousePosition.y())/3 /Hey;
   CamPos.X += (float)(pe->x()-ptrMousePosition.x())/3 /Wid;

}

void MainWindow::mouseReleaseEvent (QMouseEvent * pe)
{

    if (ptrMousePosition != pe->pos())
    {
        Paint();
        AntiAl = false;
        repaint();  // обновление изображения
        statusBar()->showMessage(tr("Translated"), 2000);
    }
    ptrMousePosition = pe->pos();

}

float MainWindow::countMinDist(Vector3D temp, int *numObj, bool b1, bool b2, bool b3, bool b4, bool b5)
{
    float len, t12 = 1000000, t22 = 1000000, t32 = 1000000, t42 = 1000000, t52 = 1000000;

    if (b1) t12 = (Sphere1.GetPos() - temp).Length() - Sphere1.GetRad();

    if (b2) t22 = (Sphere2.GetPos() - temp).Length() - Sphere2.GetRad();

    if (b3)
    {
        len = sqrt((temp.X - Thor1.GetPosCen().X) * (temp.X - Thor1.GetPosCen().X)
                    + (temp.Y - Thor1.GetPosCen().Y) * (temp.Y - Thor1.GetPosCen().Y));
        len -= Thor1.GetRad();
        t32 = sqrt(len * len + (temp.Z - Thor1.GetPosCen().Z) * (temp.Z - Thor1.GetPosCen().Z)) - Thor1.GetWid();
    }

    if (b4)
    {
        len = sqrt((temp.X - Thor2.GetPosCen().X) * (temp.X - Thor2.GetPosCen().X)
                    + (temp.Y - Thor2.GetPosCen().Y) * (temp.Y - Thor2.GetPosCen().Y));
        len -= Thor2.GetRad();
        t42 = sqrt(sqrt(sqrt(pow(len, 8) + pow(temp.Z - Thor2.GetPosCen().Z, 8)))) - Thor2.GetWid();
    }

    if (b5)
    {
        len = sqrt(sqrt(sqrt( pow((temp.X - Thor3.GetPosCen().X), 8)
                    + pow((temp.Y - Thor3.GetPosCen().Y) , 8))));
        len -= Thor3.GetRad();
        t52 = sqrt(sqrt(sqrt(pow(len, 8) + pow((temp.Z - Thor3.GetPosCen().Z), 8)))) - Thor3.GetWid();
    }

    len = t12;
    *numObj = 1;
    if (len > t22)
        {
        len = t22;
        *numObj = 2;
        }
    if (len > t32)
        {
        len = t32;
        *numObj = 3;
        }
    if (len > t42)
        {
        len = t42;
        *numObj = 4;
        }
    if (len > t52)
        {
        len = t52;
        *numObj = 5;
        }

    return len;
}

Vector3D MainWindow::countLites(Vector3D Dist, Vector3D Color, Vector3D SpeColor, Light Light0)
{
    float t , p;
    Vector3D Col(Color);
    //do
    //{
        //minDist1 = countMinDist(temp1, &numObj, true, true, true, true, true);
        //temp1 += (- Light0.GetPos() + Dist).NormVect() * minDist1;
        //minDist2 = countMinDist(temp2, &numObj, true, true, true, true, true);
        //temp2 += (-Light0.GetPos() - Dist).NormVect() * minDist2;
    //}
    //while ((minDist1 >= ((float)1/ Wid)) || (minDist2 >= ((float)1/ Wid)));
    //if (minDist1 >= minDist2) temp = temp2;
    //else temp = temp1;

    t = ( Dist - Light0.GetPos()).Length();
    p = (float)1 / (Light0.GetConst() + t * Light0.GetLinear() + t * t * Light0.GetQuadric());
    Col.X *= p * Light0.GetColor().X * (1 + SpeColor.X);
    Col.Y *= p * Light0.GetColor().Y * (1 + SpeColor.Y);
    Col.Z *= p * Light0.GetColor().Z * (1 + SpeColor.Z);
    if (Col.X > 255)
    {
        Col.X = 255;
    }
    if (Col.Y > 255)
    {
        Col.Y = 255;
    }
    if (Col.Z > 255)
    {
        Col.Z = 255;
    }

   return Col;
}

void MainWindow::Paint()
{
    RGBCol = new Vector3D*[Wid];
    for (int i = 0; i < Wid; i++)
            RGBCol[i] = new Vector3D[Hey];

    float W1 = (float)1 / Wid;
    for(int i = 0;i < Wid; i++)
        for(int j = 0;j < Hey; j++)
        {
            float len;
            Vector3D PixOnScreen((float)(i - Wid / 2) / Wid * 4,(float)(j - Hey / 2) / Hey * 3, 0);
            Vector3D RayCol(1.0f, 1.0f, 1.0f);
            Ray Rayij(PixOnScreen, CamPos);
            Vector3D temp0(Rayij.GetDir());
            temp0 = temp0 * ((float)W1 * 4);
            Vector3D temp(CamPos);
            bool b1 =true, b2=true, b3 =true, b4=true, b5 =true;
            int cola = 0;
            do
            {
                int numObj;

                len = countMinDist(temp, &numObj, b1, b2, b3, b4, b5);
                temp += Rayij.GetDir() * len;

                if (len <= W1)
                {
                    cola++;
                    Vector3D Colour , Col, SpeCol, Dist;
                    Dist = temp - CamPos + PixOnScreen;

                    switch (numObj)
                    {
                       case 1:
                            {
                                Col.X = Sphere1.GetMater().GetCol().X * RayCol.X * 255;
                                Col.Y = Sphere1.GetMater().GetCol().Y * RayCol.Y * 255;
                                Col.Z = Sphere1.GetMater().GetCol().Z * RayCol.Z * 255;
                                SpeCol = Sphere1.GetMater().GetSpe();
                                break;
                            }


                        case 2:
                            {
                                Col.X = Sphere2.GetMater().GetCol().X * RayCol.X * 255;
                                Col.Y = Sphere2.GetMater().GetCol().Y * RayCol.Y * 255;
                                Col.Z = Sphere2.GetMater().GetCol().Z * RayCol.Z * 255;
                                SpeCol = Sphere2.GetMater().GetSpe();
                                break;
                            }


                        case 3:
                            {
                                Col.X = Thor1.GetMater().GetCol().X * RayCol.X * 255;
                                Col.Y = Thor1.GetMater().GetCol().Y * RayCol.Y * 255;
                                Col.Z = Thor1.GetMater().GetCol().Z * RayCol.Z * 255;
                                SpeCol = Thor1.GetMater().GetSpe();
                                break;
                            }



                        case 4:
                            {
                                Col.X = Thor2.GetMater().GetCol().X * RayCol.X * 255;
                                Col.Y = Thor2.GetMater().GetCol().Y * RayCol.Y * 255;
                                Col.Z = Thor2.GetMater().GetCol().Z * RayCol.Z * 255;
                                SpeCol = Thor2.GetMater().GetSpe();
                                break;
                            }



                        case 5:
                            {
                                Col.X = Thor3.GetMater().GetCol().X * RayCol.X * 255;
                                Col.Y = Thor3.GetMater().GetCol().Y * RayCol.Y * 255;
                                Col.Z = Thor3.GetMater().GetCol().Z * RayCol.Z * 255;
                                SpeCol = Thor3.GetMater().GetSpe();
                                break;
                            }


                     }

                    Colour += countLites(Dist, Col, SpeCol, Light1);
                    Colour += countLites(Dist, Col, SpeCol, Light2);
                    if (Colour.X > 255) Colour.X = 255;
                    if (Colour.Y > 255) Colour.Y = 255;
                    if (Colour.Z > 255) Colour.Z = 255;
                    RGBCol[i][j] += Colour;
                    switch (numObj)
                    {
                       case 1:
                            {
                                RayCol = Vector3D((1.0f - Sphere1.GetMater().GetRef().X) * RayCol.X,
                                                  (1.0f - Sphere1.GetMater().GetRef().Y )* RayCol.Y,
                                                  (1.0f - Sphere1.GetMater().GetRef().Z )* RayCol.Z);
                                b1 =false;
                                break;
                            }


                        case 2:
                            {
                                RayCol = Vector3D((1.0f - Sphere2.GetMater().GetRef().X) * RayCol.X,
                                                (1.0f - Sphere2.GetMater().GetRef().Y )* RayCol.Y,
                                                (1.0f - Sphere2.GetMater().GetRef().Z )* RayCol.Z);
                                b2 =false;
                                break;
                            }


                        case 3:
                            {
                                RayCol = Vector3D((1.0f - Thor1.GetMater().GetRef().X) * RayCol.X,
                                                (1.0f - Thor1.GetMater().GetRef().Y )* RayCol.Y,
                                                (1.0f - Thor1.GetMater().GetRef().Z )* RayCol.Z);
                                b3 =false;
                                break;
                            }


                        case 4:
                            {
                                RayCol = Vector3D((1.0f - Thor2.GetMater().GetRef().X) * RayCol.X,
                                                (1.0f - Thor2.GetMater().GetRef().Y )* RayCol.Y,
                                                (1.0f - Thor2.GetMater().GetRef().Z )* RayCol.Z);
                                b4 =false;
                                break;
                            }



                        case 5:
                            {
                                RayCol = Vector3D((1.0f - Thor3.GetMater().GetRef().X) * RayCol.X,
                                                (1.0f - Thor3.GetMater().GetRef().Y )* RayCol.Y,
                                                (1.0f - Thor3.GetMater().GetRef().Z )* RayCol.Z);
                                b5 =false;
                                break;
                            }
                     }


                }
            }
            while ((temp.Length() < 100) && ((RayCol.X != 0) || (RayCol.Y != 0) || (RayCol.Z != 0)) && (cola < 5));

        }



    for(int i = 0;i < Wid; i++)
        for(int j = 0;j < Hey; j++)
        {
            if (RGBCol [i][j].X > 255) RGBCol [i][j].X = 255;
            if (RGBCol [i][j].Y > 255) RGBCol [i][j].Y = 255;
            if (RGBCol [i][j].Z > 255) RGBCol [i][j].Z = 255;
            curImage->setPixel(i, j, qRgb( RGBCol[i][j].X,RGBCol[i][j].Y, RGBCol[i][j].Z));
        }
    image->convertFromImage(*curImage);

    for (int i = 0; i < Wid; i++)
            delete[] RGBCol[i];
    delete[] RGBCol;

}

void MainWindow::AntiAliasing()//сепарабельное размытие
{
    if (!AntiAl)
    {
        double sigma = 1.0;
        int N = 1 + (float)(3 * sigma), i;
        double tem;
        double wind[(2 * N) + 1];
        QImage *Temp = new QImage(curImage->width(),curImage->height(),QImage::Format_RGB32);
        for (i = (2 * N); i >= 0; i--)
        {
            if (i >= N)
            {
                tem = (float)(N - i) * (i - N) / (2 * sigma * sigma);
                if (tem >= 0)
                    wind[i]= exp(tem);
                else
                    wind[i]= 1 / exp(-tem);
            }
            else wind[i] = wind[2 * N - i] ;
        }
        wash(Temp, curImage->height(), curImage->width(), N, wind, true);
        wash(Temp, curImage->width(), curImage->height(), N, wind , false);
        image->convertFromImage(*curImage);
        delete Temp;
        statusBar()->showMessage(tr("AntiAliasing Made"), 2000);
        repaint();
    }

}

void MainWindow::wash(QImage *Temp,int koef1, int koef2, int N, double* wind, bool a)//размытие
{
    int i , j , k;
    double red, green, blue;
    double sum;
    for (i = 0; i < koef1; i++)
        for (j = 0; j < koef2; j++)
        {
            sum = 0;
            red = 0;
            green = 0;
            blue = 0;
            for (k = (-N) ; k <= N ; k++)
                if (( (j + k) >= 0) && ( (j + k) < koef2))
                {
                    if (a)
                    {
                        red += qRed(curImage->pixel(j + k, i)) * wind [k + N] ;
                        green += qGreen(curImage->pixel(j + k, i)) * wind [k + N] ;
                        blue += qBlue(curImage->pixel(j + k, i)) * wind [k + N] ;
                    }
                    else
                    {
                        red += qRed(curImage->pixel(i, j + k)) * wind [k + N] ;
                        green += qGreen(curImage->pixel(i, j + k)) * wind [k + N] ;
                        blue += qBlue(curImage->pixel(i, j + k)) * wind [k + N] ;
                    }
                }
            for (k = (-N) ; k <= N ; k++) if (( (j + k) >= 0) && ( (j + k) < koef2))
                sum += wind[k + N];
            if (a)
                Temp->setPixel(j , i , qRgb(red / sum, green / sum, blue / sum));
            else
                Temp->setPixel(i , j , qRgb(red / sum, green / sum, blue / sum));
        }
    *curImage = *Temp;
}

