#include "Kam3D.h"

#include <d3d11.h>
#include <DirectXMath.h>

#include "Globals.h"
#include "MausEreignis.h"
#include "Shader.h"
#include "TastaturEreignis.h"
#include "Welt3D.h"

using namespace Framework;

// Inhalt der Kam3D Klasse
// Konstruktor
Kam3D::Kam3D()
    : ReferenceCounter(),
      rend(0)
{
    openingAngle = (float)PI / 4;
    minZ = 0.1f;
    maxZ = 5000;
    pos = Vec3<float>(0, 0, -1000);
    rotX = 0;
    rotY = 0;
    rotZ = 0;

    viewport.x = 0;
    viewport.y = 0;
    viewport.front = 0.f;
    viewport.back = 1.f;
    viewport.width = 200;
    viewport.height = 200;

    welt = 0;
    style = 0;
    speed = 1;

    updateMatrix();
}

// Destruktor
Kam3D::~Kam3D()
{
    if (welt) welt->release();
}

// private

// Aktualisiert die view und projektion matrizen
void Kam3D::updateMatrix()
{
    view = view.rotationX(-rotX) * view.rotationY(-rotY) * view.rotationZ(-rotZ)
         * view.translation(Vec3<float>(-pos.x, -pos.y, -pos.z));
    proj = proj.projektion(
        openingAngle, viewport.width / viewport.height, minZ, maxZ);
}

// Setzt die Position der Kamera in der 3D Welt
void Kam3D::setPosition(Vec3<float> pos)
{
    this->pos = pos;
    rend = 1;
    updateMatrix();
}

// zoomt heran, indem sich die Kamera etwas auf das Blickziel zubewegt
//  val: Die länge der Strecke um die sich die Kamera bewegen soll
void Kam3D::scrollIn(float val)
{
    Vec3<float> n(0, 0, 1);
    Mat4<float> tmp
        = tmp.rotationY(rotY) * tmp.rotationX(rotX) * tmp.rotationZ(rotZ);
    n = tmp * n * val;
    pos += n;
    rend = 1;
    updateMatrix();
}

// zppmt heraus, indem sich die Kamera etwas von dem Blockziel entfernt
//  val: Die länge der Strecke um die sich die Kamera bewegen soll
void Kam3D::scrollOut(float val)
{
    Vec3<float> n(0, 0, 1);
    Mat4<float> tmp
        = tmp.rotationY(rotY) * tmp.rotationX(rotX) * tmp.rotationZ(rotZ);
    n = tmp * n * val;
    pos -= n;
    rend = 1;
    updateMatrix();
}

// Richtet die Kamera so aus, dass sie genau auf einen bestimmten Punkt zeigt
//  ziel: Der Punkt, auf den die Kamera zeigen soll
void Kam3D::setAusrichtung(Vec3<float> ziel)
{
    Vec3<float> target = (ziel - pos).normalize();
    if (Vec3<float>(0, target.y, target.z).getLength() == 0)
        rotX = 0;
    else
        rotX = -lowPrecisionACos(
            target.z / Vec3<float>(0, target.y, target.z).getLength());
    if (target.y < 0) rotX = -rotX;
    if (Vec3<float>(target.x, 0, target.z).getLength() == 0)
        rotY = 0;
    else
        rotY = lowPrecisionACos(
            abs(target.z) / Vec3<float>(target.x, 0, target.z).getLength());
    if (target.x < 0) rotY = -rotY;
    rotZ = 0;
    rend = 1;
    updateMatrix();
}

//! Legt die Drehung der Kamera um die einzelnen Axen fest
//! \param rotation Die Drehung um die einzelnen Axen
void Kam3D::setRotation(Vec3<float> rotation)
{
    rotX = rotation.x;
    rotY = rotation.y;
    rotZ = rotation.z;
    rend = 1;
    updateMatrix();
}

// Setzt die Position des Bildes auf dem Bildschirm
//  p: Ein Punkt mit x und y Koordinaten in Pixeln
void Kam3D::setBildschirmPosition(Punkt p)
{
    viewport.x = (float)p.x;
    viewport.y = (float)p.y;
}

// Setzt die Position des Bildes auf dem Bildschirm
//  x: Die x Koordinate in Pixeln
//  y: Die y Koordinate in Pixeln
void Kam3D::setBildschirmPosition(int x, int y)
{
    viewport.x = (float)x;
    viewport.y = (float)y;
}

// Setzt die Größe des Bildes auf dem Bildschirm
//  p: Ein Punkt, mit x als Breite und y als Höhe in Pixlen
void Kam3D::setBildschirmSize(Punkt p)
{
    viewport.width = (float)p.x;
    viewport.height = (float)p.y;
    updateMatrix();
}

// Setzt die Größe des Bildes auf dem Bildschirm
//  br: Die Breite in Pixeln
//  hö: Die Höhe in Pixeln
void Kam3D::setBildschirmSize(int br, int hö)
{
    viewport.width = (float)br;
    viewport.height = (float)hö;
    updateMatrix();
}

// Setzt die Welt, die gezeichnet werden soll
//  w: Die Welt
void Kam3D::setWelt(Welt3D* w)
{
    if (welt) welt->release();
    welt = w;
}

// Setzt den Style der Kamera
//  style: Der neue Style bestehend aus den Flags aus der zugehörigen Style
//  Klasse
void Kam3D::setStyle(__int64 style)
{
    this->style = style;
}

// Setzt den Style der Kamera
//  style: Alle Style Flags, die verändert werden sollen
//  add_remove: 1, falls der Style hinzugefügt werden soll. 0, falls der Style
//  entfernt weden soll
void Kam3D::setStyle(__int64 style, bool add_remove)
{
    if (add_remove)
        this->style |= style;
    else if (!add_remove)
        this->style &= ~style;
}

// Fügt Style Flags hinzu
//  style: Der Style, der hinzugefügt werden soll
void Kam3D::addStyle(__int64 style)
{
    this->style |= style;
}

// Entfernt Style Flags
//  style: Der Style, der entfernt werden soll
void Kam3D::removeStyle(__int64 style)
{
    this->style &= ~style;
}

//! Set the movement speed per second if the camera has style Movable
void Kam3D::setMovementSpeed(float speed)
{
    this->speed = speed;
}

// Verarbeitet die vergangene Zeit
//  tickval: Die zeit in sekunden, die seit dem letzten Aufruf der Funktion
//  vergangen ist return: true, wenn sich das Bild neu gezeichnet werden muss,
//  false sonnst.
bool Kam3D::tick(double tv)
{
    bool ret = rend;
    rend = 0;
    if (hatStyle(Style::Rotatable))
    {
        if (getTastenStand(T_Oben))
        {
            rotX -= (float)tv;
            ret = 1;
        }
        if (getTastenStand(T_Unten))
        {
            rotX += (float)tv;
            ret = 1;
        }
        if (getTastenStand(T_Links))
        {
            rotY -= (float)tv;
            ret = 1;
        }
        if (getTastenStand(T_Rechts))
        {
            rotY += (float)tv;
            ret = 1;
        }
    }
    if (hatStyle(Style::Movable))
    {
        Vec3<float> n(0, 0, 1);
        Vec3<float> n2(1, 0, 0);
        Vec3<float> n3(0, 1, 0);
        Mat4<float> tmp = tmp.rotationY(rotY) * tmp.rotationX(rotX);
        n = tmp * n;
        n = n * (float)tv * speed;
        n2 = tmp * n2;
        n2 = n2 * (float)tv * speed;
        n3 = tmp * n3;
        n3 = n3 * (float)tv * speed;
        if (getTastenStand('W'))
        {
            pos += n;
            ret = 1;
        }
        if (getTastenStand('S'))
        {
            pos -= n;
            ret = 1;
        }
        if (getTastenStand('D'))
        {
            pos += n2;
            ret = 1;
        }
        if (getTastenStand('A'))
        {
            pos -= n2;
            ret = 1;
        }
        if (getTastenStand(' '))
        {
            pos += n3;
            ret = 1;
        }
        if (getTastenStand(T_Shift))
        {
            pos -= n3;
            ret = 1;
        }
    }
    updateMatrix();
    if (welt && hatStyle(Style::Tick)) return welt->tick(tv) || ret;
    return ret;
}

// Verarbeitet ein Mausereignis
//  me: Das Mausereignis, das verarbeitet werden soll
void Kam3D::doMausEreignis(MausEreignis& me)
{
    if (me.verarbeitet) return;
    if (me.mx > viewport.x && me.my > viewport.y
        && me.mx < viewport.x + viewport.width
        && me.my < viewport.y + viewport.height)
    {
        MausEreignis3D me3d;
        me3d.id = me.id;
        me3d.verarbeitet = me.verarbeitet;
        Vec3<float> mausP
            = Vec3<float>((me.mx - viewport.x) / (0.5f * viewport.width) - 1,
                (me.my - viewport.y) / (0.5f * viewport.height) - 1,
                0);
        Vec3<float> mausT = Vec3<float>(mausP.x, mausP.y, 1);
        Mat4<float> mat = proj * view;
        mat = mat.getInverse();
        mausP = mat * mausP;
        mausT = mat * mausT;
        me3d.pos = mausP;
        me3d.dir = mausT - mausP;
        me.verarbeitet = 1;
    }
}

// Verarbeitet ein Tastaturereignis
//  te: das Tastaturereignis, das verarbeitet werden soll
void Kam3D::doTastaturEreignis(TastaturEreignis& te) {}

// Gibt zurück, ob bestimmte Styles gesetzt wurden
//  style: Die Styles, die überprüft werden sollen
//  return: 1, falls alle Styles in style gesetzt wurden
bool Kam3D::hatStyle(__int64 style) const
{
    return (this->style | style) == this->style;
}

// Gibt zurück, ob bestimmte Styles nicht gesetzt wurden
//  style: Die Styles, die geprüft werden sollen
//  return: 1, falls alle Styles in style nicht gesetzt wurden
bool Kam3D::hatStyleNicht(__int64 style) const
{
    return (this->style | style) != this->style;
}

// Gibt einen Zeiger auf den Viewport zurück
const ViewPort* Kam3D::zViewPort() const
{
    return &viewport;
}

// Gibt die Position der Kamera in der Welt zurück
const Vec3<float>& Kam3D::getWorldPosition() const
{
    return pos;
}

// Gibt die Position in der Welt zurück
//  screen: die Position auf dem Bildschirm, die übersetzt werden soll
const Vec3<float> Kam3D::getWorldPosition(Punkt screen) const
{
    Vec3<float> point = Vec3<float>(
        (screen.x - viewport.x) / (0.5f * viewport.width) - 1,
        (viewport.height - (screen.y - viewport.y)) / (0.5f * viewport.height)
            - 1,
        0.1f);
    Mat4<float> mat = getProjectionMatrix();
    Mat4<float> inv = getViewMatrix().getInverse();
    point.x = (point.x * 0.1f) / mat.elements[0][0];
    point.y = (point.y * 0.1f) / mat.elements[1][1];
    return inv * point;
}

// Gibt die Richtung der Kamera in der Welt zurück
//  screen: die Position auf dem Bildschirm, die übersetzt werden soll
const Vec3<float> Kam3D::getWorldDirection(Punkt screen) const
{
    Vec3<float> point = Vec3<float>(
        (screen.x - viewport.x) / (0.5f * viewport.width) - 1,
        (viewport.height - (screen.y - viewport.y)) / (0.5f * viewport.height)
            - 1,
        0.1f);
    Vec3<float> pointT = Vec3<float>(point.x, point.y, 1);
    Mat4<float> mat = getProjectionMatrix();
    Mat4<float> inv = getViewMatrix().getInverse();
    point.x = (point.x * 0.1f) / mat.elements[0][0];
    point.y = (point.y * 0.1f) / mat.elements[1][1];
    pointT.x = (pointT.x) / mat.elements[0][0];
    pointT.y = (pointT.y) / mat.elements[1][1];
    point = inv * point;
    pointT = inv * pointT;
    return pointT - point;
}

// Gibt die Projektionsmatrix der Kamera zurück
const Mat4<float>& Kam3D::getProjectionMatrix() const
{
    return proj;
}

// Gibt die Ansichtsmatrix der Kamera zurück
const Mat4<float>& Kam3D::getViewMatrix() const
{
    return view;
}

//! Gibt die Rotation um die einzelnen axen zurück
const Vec3<float> Kam3D::getRotation() const
{
    return {rotX, rotY, rotZ};
}

//! Gibt die Position der Kamera auf dem Bildschirm zurück
const Punkt Kam3D::getScreenPos() const
{
    return Punkt((int)viewport.x, (int)viewport.y);
}

//! Gibt die Größe der Kamera auf dem Bildschirm zurück
const Punkt Kam3D::getScreenSize() const
{
    return Punkt((int)viewport.width, (int)viewport.height);
}

// Gibt die Welt zurück
Welt3D* Kam3D::getWelt() const
{
    return welt ? dynamic_cast<Welt3D*>(welt->getThis()) : 0;
}

// Gibt die Welt zurück
Welt3D* Kam3D::zWelt() const
{
    return welt;
}