using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace MojaDrugaGra;

public class Game1 : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;

    public Game1()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        IsMouseVisible = true;

        _graphics.GraphicsProfile = GraphicsProfile.Reach;
    }

    private BasicEffect efekt;
    private Prostopadloscian prostopadloscian, podloze, cienProstopadloscianu;

    protected override void Initialize()
    {
        // TODO: Add your initialization logic here
        efekt = new BasicEffect(_graphics.GraphicsDevice);
        efekt.VertexColorEnabled = true;
        efekt.Projection = Matrix.CreatePerspective(
            2.0f * _graphics.GraphicsDevice.Viewport.AspectRatio,
            2.0f, 1.0f, 10.0f);
        efekt.View = Matrix.CreateLookAt(
            new Vector3(0, 0, 2.5f),
            new Vector3(0, 0, 0),
            new Vector3(0, 1, 0));
        efekt.World = Matrix.Identity;

        //efekt.EnableDefaultLighting();
        //efekt.AmbientLightColor = Color.DarkGray.ToVector3();

        efekt.EnableDefaultLighting();
         efekt.AmbientLightColor = Color.Gray.ToVector3();

         //cialo doskonale czarne
         efekt.AmbientLightColor = Color.Black.ToVector3();
         efekt.DiffuseColor = Color.Black.ToVector3();
         efekt.SpecularColor = Color.Black.ToVector3();
         efekt.EmissiveColor = Color.Black.ToVector3();

         //zrodla swiatla
         efekt.DirectionalLight0.DiffuseColor = Color.White.ToVector3();
         efekt.DirectionalLight0.SpecularColor = Color.Gray.ToVector3();

        efekt.SpecularColor = Color.White.ToVector3();
        efekt.SpecularPower = 16;
        efekt.DiffuseColor = Color.White.ToVector3();
        efekt.AmbientLightColor = Color.Gray.ToVector3();
        efekt.EmissiveColor = Color.White.ToVector3();

        prostopadloscian = new Prostopadloscian(
            this, efekt, 1.5f, 1.0f, 2.0f, Color.Yellow);
        this.Components.Add(prostopadloscian);        

        //podłoże
        podloze = new Prostopadloscian(this, efekt, 10f, 0.1f, 10f, Color.Green);
        podloze.MacierzSwiata=Matrix.CreateScale(0.75f) * Matrix.CreateTranslation(0, -2, 0);
        this.Components.Add(podloze);

        //cien
        BasicEffect efektCienia = (BasicEffect)efekt.Clone();
        efektCienia.DiffuseColor = Color.Black.ToVector3();
        efektCienia.SpecularColor = Color.Black.ToVector3();
        efektCienia.EmissiveColor = Color.White.ToVector3();

        cienProstopadloscianu = new Prostopadloscian(
            this, efektCienia, 1.5f, 1.5f, 1.5f, Color.DarkGreen);
        this.Components.Add(cienProstopadloscianu);

        this.IsMouseVisible = true;

        base.Initialize();
    }

    static Matrix CreateShadowMatrix(Vector3 lightPosition, Plane plane,
                                     bool infinitelyDistantLightSource = true)
    {
        Vector4 N = new Vector4(plane.Normal, plane.D);
        Vector4 L = new Vector4(lightPosition,
        infinitelyDistantLightSource ? 0 : 1);
        float correction = infinitelyDistantLightSource ? 0.00001f : 0;
        float alfa = Vector4.Dot(N, L);
        Matrix m = new Matrix();
        m.M11 = alfa - N.X * L.X;
        m.M22 = alfa - N.Y * L.Y;
        m.M33 = alfa - N.Z * L.Z;
        m.M44 = alfa - N.W * L.W;
        m.M21 = -N.Y * L.X;
        m.M31 = -N.Z * L.X;
        m.M41 = -N.W * L.X;
        m.M12 = -N.X * L.Y;
        m.M32 = -N.Z * L.Y;
        m.M42 = -N.W * L.Y;
        m.M13 = -N.X * L.Z;
        m.M23 = -N.Y * L.Z;
        m.M43 = -N.W * L.Z;
        m.M14 = -N.X * L.W;
        m.M24 = -N.Y * L.W + correction;
        m.M34 = -N.Z * L.W;
        return m;
    }

    const float d = 2 - 0.05f;
    Vector3 polożenieŹródłaŚwiatła = new Vector3(3, 5, 3);
    bool rzutowanieRównoległe = false;

    Matrix macierzRzutowaniaNaPodłoże
    {
        get
        {
            return CreateShadowMatrix(
                polożenieŹródłaŚwiatła,
                new Plane(Vector3.Up, d),
                rzutowanieRównoległe);
        }
    }

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);

        // TODO: use this.Content to load your game content here
    }

    /*
    protected override void Update(GameTime gameTime)
    {
        KeyboardState stanKlawiatury = Keyboard.GetState();
        GamePadState stanGamepada = GamePad.GetState(PlayerIndex.One);

        if (stanGamepada.Buttons.Back == ButtonState.Pressed || stanKlawiatury.IsKeyDown(Keys.Escape))
            Exit();

        // TODO: Add your update logic here
        float katObrotu = 0.01f;        
        if (stanKlawiatury.IsKeyDown(Keys.LeftShift) || stanKlawiatury.IsKeyDown(Keys.RightShift))
            katObrotu *= 10;

        if (stanKlawiatury.IsKeyDown(Keys.Left) || stanGamepada.IsButtonDown(Buttons.DPadLeft))
            prostopadloscian.MacierzSwiata *= Matrix.CreateRotationY(katObrotu);
        if (stanKlawiatury.IsKeyDown(Keys.Right) || stanGamepada.IsButtonDown(Buttons.DPadRight))
            prostopadloscian.MacierzSwiata *= Matrix.CreateRotationY(-katObrotu);
        if (stanKlawiatury.IsKeyDown(Keys.Up) || stanGamepada.IsButtonDown(Buttons.DPadUp))
            prostopadloscian.MacierzSwiata *= Matrix.CreateRotationX(katObrotu);
        if (stanKlawiatury.IsKeyDown(Keys.Down) || stanGamepada.IsButtonDown(Buttons.DPadDown))
            prostopadloscian.MacierzSwiata *= Matrix.CreateRotationX(-katObrotu);
        if (stanKlawiatury.IsKeyDown(Keys.OemPeriod))
            prostopadloscian.MacierzSwiata *= Matrix.CreateRotationZ(katObrotu);
        if (stanKlawiatury.IsKeyDown(Keys.OemComma))
            prostopadloscian.MacierzSwiata *= Matrix.CreateRotationZ(-katObrotu);        

        Vector2 wychylenieLewegoGrzybka = stanGamepada.ThumbSticks.Left;
        prostopadloscian.MacierzSwiata *= Matrix.CreateRotationY(katObrotu * wychylenieLewegoGrzybka.X);
        prostopadloscian.MacierzSwiata *= Matrix.CreateRotationX(-katObrotu * wychylenieLewegoGrzybka.Y);

        Vector2 wychyleniePrawegoGrzybka = stanGamepada.ThumbSticks.Right;
        prostopadloscian.MacierzSwiata *= Matrix.CreateRotationY(10 * katObrotu * wychyleniePrawegoGrzybka.X);
        prostopadloscian.MacierzSwiata *= Matrix.CreateRotationX(-10 * katObrotu * wychyleniePrawegoGrzybka.Y);
        
        cienProstopadloscianu.MacierzWidoku = efekt.View;
        cienProstopadloscianu.MacierzSwiata = 
            prostopadloscian.MacierzSwiata * macierzRzutowaniaNaPodłoże;

        base.Update(gameTime);

        trzeba poprawić rzeczy z końca rozdziału 6
    }
    */

KeyboardState poprzedniStanKlawiatury = Keyboard.GetState();

private void Update_Keyboard(GameTime gameTime) 
{ 
    KeyboardState stanKlawiatury = Keyboard.GetState(); 

    if (stanKlawiatury.IsKeyDown(Keys.Escape)) Exit(); 

    float szybkość = MathHelper.Pi; //rad/s 
    if (stanKlawiatury.IsKeyDown(Keys.LeftShift) || 
        stanKlawiatury.IsKeyDown(Keys.RightShift)) szybkość /= 10; 
    float kąt = (float)(gameTime.ElapsedGameTime.TotalSeconds * szybkość); 
    bool obrotyWUkladzieLokalnym = 
        stanKlawiatury.IsKeyDown(Keys.LeftAlt) || 
        stanKlawiatury.IsKeyDown(Keys.RightAlt); 

    //ruch aktora (macierz świata) 
    Matrix macierzPrzekształceniaŚwiata = Matrix.Identity; 
    if (stanKlawiatury.IsKeyDown(Keys.Left)) 
        macierzPrzekształceniaŚwiata *= Matrix.CreateRotationY(-kąt); 
    if (stanKlawiatury.IsKeyDown(Keys.Right)) 
        macierzPrzekształceniaŚwiata *= Matrix.CreateRotationY(kąt); 
    if (stanKlawiatury.IsKeyDown(Keys.Up)) 
        macierzPrzekształceniaŚwiata *= Matrix.CreateRotationX(-kąt); 
    if (stanKlawiatury.IsKeyDown(Keys.Down)) 
        macierzPrzekształceniaŚwiata *= Matrix.CreateRotationX(kąt); 
    if (stanKlawiatury.IsKeyDown(Keys.OemComma)) 
        macierzPrzekształceniaŚwiata *= Matrix.CreateRotationZ(kąt); 
    if (stanKlawiatury.IsKeyDown(Keys.OemPeriod)) 
        macierzPrzekształceniaŚwiata *= Matrix.CreateRotationZ(-kąt); 
    if(obrotyWUkladzieLokalnym) 
        efekt.World = macierzPrzekształceniaŚwiata * efekt.World; 
    else 
        efekt.World *= macierzPrzekształceniaŚwiata; 
    
    prostopadloscian.MacierzSwiata = efekt.World; 
    cienProstopadloscianu.MacierzSwiata = 
    prostopadloscian.MacierzSwiata * macierzRzutowaniaNaPodłoże;
            
    //ruch kamery (macierz widoku) 
    Matrix macierzPrzekształceniaWidoku = Matrix.Identity; 
    if (stanKlawiatury.IsKeyDown(Keys.A)) 
        macierzPrzekształceniaWidoku *= Matrix.CreateRotationY(kąt); 
    if (stanKlawiatury.IsKeyDown(Keys.D)) 
     macierzPrzekształceniaWidoku *= Matrix.CreateRotationY(-kąt); 
    if (stanKlawiatury.IsKeyDown(Keys.W)) 
        macierzPrzekształceniaWidoku *= Matrix.CreateRotationX(kąt); 
    if (stanKlawiatury.IsKeyDown(Keys.S)) 
        macierzPrzekształceniaWidoku *= Matrix.CreateRotationX(-kąt); 
    if (stanKlawiatury.IsKeyDown(Keys.Q)) 
        macierzPrzekształceniaWidoku *= Matrix.CreateRotationZ(-kąt); 
    if (stanKlawiatury.IsKeyDown(Keys.E)) 
        macierzPrzekształceniaWidoku *= Matrix.CreateRotationZ(kąt); 
    if (obrotyWUkladzieLokalnym) 
        efekt.View *= macierzPrzekształceniaWidoku; 
    else 
        efekt.View = macierzPrzekształceniaWidoku * efekt.View; 
    
    prostopadloscian.MacierzWidoku = efekt.View; 
    podloze.MacierzWidoku = efekt.View; 
    cienProstopadloscianu.MacierzWidoku = efekt.View;

        if ((stanKlawiatury.IsKeyDown(Keys.LeftControl) ||
        stanKlawiatury.IsKeyDown(Keys.RightControl)) &&
        !poprzedniStanKlawiatury.IsKeyDown(Keys.R) &&
        stanKlawiatury.IsKeyDown(Keys.R))
    {
        PrzywróćPoczątkowąPozycjęAktoraIKamery();
    }
    poprzedniStanKlawiatury = stanKlawiatury;
}

private void PrzywróćPoczątkowąPozycjęAktoraIKamery()
{
    efekt.World = Matrix.Identity;
    prostopadloscian.MacierzSwiata = efekt.World;
    podloze.MacierzSwiata = Matrix.CreateScale(0.75f) * Matrix.
    CreateTranslation(0, -2, 0);
    cienProstopadloscianu.MacierzSwiata = 
    prostopadloscian.MacierzSwiata *macierzRzutowaniaNaPodłoże;
            
    efekt.View = Matrix.CreateLookAt(
        new Vector3(0, 0, 2.5f),
        new Vector3(0, 0, 0),
        new Vector3(0, 1, 0));
            
    prostopadloscian.MacierzWidoku = efekt.View;            
    cienProstopadloscianu.MacierzWidoku = efekt.View;
}

float odległośćKamery  = 2.5f;

private void Update_GamePad(GameTime gameTime, PlayerIndex playerIndex)
{
    GamePadState stanGamepada = GamePad.GetState(playerIndex);

    GamePad.SetVibration(playerIndex,
        stanGamepada.Triggers.Left, //silnik wolnoobrotowy
        stanGamepada.Triggers.Right); //silnik szybkoobrotowy

    if(!stanGamepada.IsConnected) return;
    if (stanGamepada.IsButtonDown(Buttons.Back)) Exit();
    float szybkość = MathHelper.Pi; //rad/s
    if (stanGamepada.IsButtonDown(Buttons.RightShoulder))
        szybkość /= 10;
    float kąt = (float)(gameTime.ElapsedGameTime.TotalSeconds *
    szybkość);
    bool obrotyWUkladzieLokalnym =
        stanGamepada.IsButtonDown(Buttons.LeftShoulder);
    //ruch aktora (macierz świata)
    Vector2 wychylenieLewego = kąt * stanGamepada.ThumbSticks.Left;
    Matrix macierzPrzekształceniaŚwiata =
    Matrix.CreateFromYawPitchRoll(wychylenieLewego.X,
        -wychylenieLewego.Y, 0);
    if (obrotyWUkladzieLokalnym)
        efekt.World = macierzPrzekształceniaŚwiata * efekt.World;
    else
        efekt.World *= macierzPrzekształceniaŚwiata;
    prostopadloscian.MacierzSwiata = efekt.World;
    cienProstopadloscianu.MacierzSwiata = 
    prostopadloscian.MacierzSwiata * macierzRzutowaniaNaPodłoże;
            
    //ruch kamerą (macierz widoku)
    Vector2 wychyleniePrawego = kąt * stanGamepada.ThumbSticks.Right;
    Matrix macierzPrzekształceniaWidoku =
        Matrix.CreateFromYawPitchRoll(wychyleniePrawego.X,
        -wychyleniePrawego.Y, 0);
    if (obrotyWUkladzieLokalnym)
        efekt.View *= macierzPrzekształceniaWidoku;
    else
        efekt.View = macierzPrzekształceniaWidoku * efekt.View;
    prostopadloscian.MacierzWidoku = efekt.View;
    podloze.MacierzWidoku = efekt.View;
    cienProstopadloscianu.MacierzWidoku = efekt.View;
            
    //przesuwanie kamery
    if(stanGamepada.IsButtonDown(Buttons.DPadUp) ||
    stanGamepada.IsButtonDown(Buttons.DPadDown))
    {
        const float czułość = 0.1f;
        float zmianaOdległościOdKamery = czułość;
        if (stanGamepada.IsButtonDown(Buttons.DPadDown))
            zmianaOdległościOdKamery = -zmianaOdległościOdKamery;
        odległośćKamery += zmianaOdległościOdKamery;
        efekt.View *= Matrix.CreateTranslation(0, 0,
        zmianaOdległościOdKamery);
        prostopadloscian.MacierzWidoku = efekt.View;
        podloze.MacierzWidoku = efekt.View;
        cienProstopadloscianu.MacierzWidoku = efekt.View;
    }

    //przywracanie początkowego ustawienia kamery
    if (stanGamepada.IsButtonDown(Buttons.A))
    {
        PrzywróćPoczątkowąPozycjęAktoraIKamery();
    }
}

MouseState poprzedniStanMyszy = Mouse.GetState();


private Vector3 Unproject(Vector3 vector2D)
{
    return _graphics.GraphicsDevice.Viewport.Unproject(
    vector2D, efekt.Projection, efekt.View, efekt.World);
}

private Vector3 Unproject(int X, int Y)
{
    return Unproject(new Vector3(X, Y, 0));
}

private void Update_Mouse(GameTime gameTime)
{
    MouseState stanMyszy = Mouse.GetState();
    if (stanMyszy != poprzedniStanMyszy &&
    (stanMyszy.LeftButton == ButtonState.Pressed ||
    stanMyszy.RightButton == ButtonState.Pressed))
    {
        //wektory wodzące
        Vector3 pozycjaMyszy3D = Unproject(stanMyszy.X, stanMyszy.Y);
        Vector3 poprzedniaPozycjaMyszy3D =
            Unproject(poprzedniStanMyszy.X, poprzedniStanMyszy.Y);
        pozycjaMyszy3D.Normalize();
        poprzedniaPozycjaMyszy3D.Normalize();
                
        //oś i kąt obrotu
        Vector3 ośObrotu3D =
            Vector3.Cross(poprzedniaPozycjaMyszy3D, pozycjaMyszy3D);
        float kątObrotu = (float)Math.Asin(ośObrotu3D.Length());
        kątObrotu /= odległośćKamery / 2.5f;
        ośObrotu3D.Normalize();
        bool obrotyWUkladzieLokalnym =
            Keyboard.GetState().IsKeyDown(Keys.LeftAlt) ||
            Keyboard.GetState().IsKeyDown(Keys.RightAlt);
        if (stanMyszy.LeftButton == ButtonState.Pressed)
        {
            //ruch aktora (macierz świata)
            Matrix macierzPrzekształceniaŚwiata = Matrix.Identity;
            if (kątObrotu != 0)
            macierzPrzekształceniaŚwiata =
            Matrix.CreateFromAxisAngle(ośObrotu3D, kątObrotu);
            if (!obrotyWUkladzieLokalnym)
                efekt.World = macierzPrzekształceniaŚwiata * efekt.World;
            else
                efekt.World *= macierzPrzekształceniaŚwiata;
            prostopadloscian.MacierzSwiata = efekt.World;
            cienProstopadloscianu.MacierzSwiata = 
                prostopadloscian.MacierzSwiata * macierzRzutowaniaNaPodłoże;
        }
        else
        {
            Matrix macierzPrzekształceniaWidoku = Matrix.Identity;
            if (kątObrotu != 0)
                macierzPrzekształceniaWidoku =
                    Matrix.CreateFromAxisAngle(ośObrotu3D, kątObrotu);
                if (obrotyWUkladzieLokalnym)
                    efekt.View *= macierzPrzekształceniaWidoku;
                else
                    efekt.View = macierzPrzekształceniaWidoku * efekt.View;
                prostopadloscian.MacierzWidoku = efekt.View;
                podloze.MacierzWidoku = efekt.View;
                cienProstopadloscianu.MacierzWidoku = efekt.View;
        }
    }

int zmianaRolkiMyszy = stanMyszy.ScrollWheelValue - poprzedniStanMyszy.ScrollWheelValue;
if (zmianaRolkiMyszy != 0)
{
    const float czułośćRolkiMyszy = 0.1f;
    const float skok = 120f;
    float zmianaOdległościOdKamery =
    czułośćRolkiMyszy * zmianaRolkiMyszy / skok;
    odległośćKamery -= zmianaOdległościOdKamery;
    efekt.View *= Matrix.CreateTranslation(0, 0, zmianaOdległościOdKamery);
    prostopadloscian.MacierzWidoku = efekt.View;
    podloze.MacierzWidoku = efekt.View;
    cienProstopadloscianu.MacierzWidoku = efekt.View;
}

        poprzedniStanMyszy = stanMyszy;
}

protected override void Update(GameTime gameTime)
{           
    Update_Keyboard(gameTime); 
    Update_GamePad(gameTime, PlayerIndex.One);
    Update_Mouse(gameTime);            
    base.Update(gameTime); 
}

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.Black);

        // TODO: Add your drawing code here

        base.Draw(gameTime);
    }
}
