using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using static CanonBallProblemCalculator2D;
using Unity.Mathematics;
using System;
using REST_JSON_API;
using MoreLinq;

public class GenerateDemoFiles
{
    public static void GenerateAll()
    {
        if (UnityEngine.Object.FindObjectOfType<GadgetBehaviour>(true) == null)
        {
            Debug.LogError("Cannot GenerateDemoFiles without populated GadgetManager");
            return;
        }

        Debug.LogWarning("Generating and Overwriting Stage Files");

        Action[] DemoGeneration = new Action[]{
            GenerateTreeStage,
            GenerateRiverStage,
            GenerateCanonBallStage2D,
            GenerateCanonBallStage3D
        };

        foreach (Action action in DemoGeneration)
            try
            {
                action();
                Debug.Log("Successfully executed " + action.Method.Name);
            }
            catch (Exception ex)
            {
                Debug.LogError("Exception while executing " + action.Method.Name);
                Debug.LogException(ex);
            }

        StageStatic.StageOfficial = null;
        StageStatic.StageLocal = null;
    }

    public static void GenerateTreeStage()
    {
        // Params
        float minimalSolutionHight = 6;

        // Generate Stage
        Stage demo = new Stage
        (
            number: 1,
            category: "Demo Category",
            name: "TechDemo A",
            scene: "RiverWorld",
            description: "Tree Stage",
            local: false
        );

        // needed to generate facts
        StageStatic.StageOfficial = new Dictionary<string, Stage>
        {
            { demo.name, demo },
        };
        StageStatic.SetStage(demo.name, false);

        // Populate Solution
        PointFact
            buttom = new PointFact(Vector3.zero, Vector3.up, StageStatic.stage.solution),
            top = new PointFact(Vector3.zero + Vector3.up * minimalSolutionHight, Vector3.up, StageStatic.stage.solution);

        StageStatic.stage.solution.Add(buttom, out _, false, null, null);
        StageStatic.stage.solution.Add(top, out _, true, null, null);

        LineFact target = new LineFact(buttom.Id, top.Id, StageStatic.stage.solution);
        var target_Id = StageStatic.stage.solution.Add(target, out _, true, null, null);

        // Set Solution
        StageStatic.stage.solution.ValidationSet =
            new List<SolutionRecorder.SubSolution>
            { new SolutionRecorder.SubSolution(new HashSet<string> { target_Id }, null, null, new LineFactHightDirectionComparer()) };

        // Set Gadgets/ Scrolls
        StageStatic.stage.AllowedGadgets = null;
        StageStatic.stage.AllowedScrolls = null;

        // Save
        StageStatic.SetMode(StageStatic.Mode.Create);
        StageStatic.stage.store(false, true);
    }

    public static void GenerateRiverStage()
    {
        // Params
        float minimalSolutionHight = 6;

        // Generate Stage
        Stage demo = new Stage
        (
            number: 2,
            category: "Demo Category",
            name: "TechDemo B",
            scene: "RiverWorld",
            description: "River Stage",
            local: false
        );

        // needed to generate facts
        StageStatic.StageOfficial = new Dictionary<string, Stage>
        {
            { demo.name, demo },
        };
        StageStatic.SetStage(demo.name, false);

        // Populate Solution
        PointFact
            buttom = new PointFact(Vector3.zero, Vector3.up, StageStatic.stage.solution),
            top = new PointFact(Vector3.zero + Vector3.up * minimalSolutionHight, Vector3.up, StageStatic.stage.solution);

        StageStatic.stage.solution.Add(buttom, out _, false, null, null);
        StageStatic.stage.solution.Add(top, out _, true, null, null);

        LineFact target = new LineFact(buttom.Id, top.Id, StageStatic.stage.solution);
        var target_Id = StageStatic.stage.solution.Add(target, out _, true, null, null);

        // Set Solution
        StageStatic.stage.solution.ValidationSet =
            new List<SolutionRecorder.SubSolution> {
                new SolutionRecorder.SubSolution(new HashSet<string> { target_Id }, null, null, new LineFactHightDirectionComparer()),
                new SolutionRecorder.SubSolution(new HashSet<string> { target_Id }, null, null, new LineSpanningOverRiverWorldComparer()),
                new SolutionRecorder.SubSolution(null, new List<int> { 1 }, new List<int> { 0 }, new LineFactHightComparer()),
            };

        // Set Gadgets/ Scrolls
        StageStatic.stage.AllowedGadgets = new() { new Pointer(), new Tape(), new AngleTool(), new LineTool(), new LotTool(), new Pendulum(), new Remover() }; //, new EqualCircleGadget(), new TestMiddlePoint() };
        StageStatic.stage.AllowedScrolls = new() {
            "http://mathhub.info/FrameIT/frameworld?OppositeLen",
            //"http://mathhub.info/FrameIT/frameworld?SupplementaryAngles",
            //"http://mathhub.info/FrameIT/frameworld?AngleSum",
            //"http://mathhub.info/FrameIT/frameworld?Pythagoras",
            //"http://mathhub.info/FrameIT/frameworld?CylinderVolumeScroll",
            //"http://mathhub.info/FrameIT/frameworld?CircleLineAngleToAngleScroll",
            //"http://mathhub.info/FrameIT/frameworld?Midpoint",
            //"http://mathhub.info/FrameIT/frameworld?CircleScroll",
            //"http://mathhub.info/FrameIT/frameworld?CircleLineAngleScroll",
            //"http://mathhub.info/FrameIT/frameworld?CircleAreaScroll",
            //"http://mathhub.info/FrameIT/frameworld?ConeVolumeScroll",
            //"http://mathhub.info/FrameIT/frameworld?TruncatedConeVolumeScroll",
        };

        // Save
        StageStatic.SetMode(StageStatic.Mode.Create);
        StageStatic.stage.store(false, true);
    }

    public static void GenerateCanonBallStage2D()
    {
        // Params //List<Wall> walls, T starPos, T starVec, T gravity, int dimension
        int
            dim_const = 0,
            dim_A = 1,
            dim_B = 2;

        int Py_factor = 1;// 0.04905f;
        float Py_bounce = 0.1f;

        float3 StartPos_py = new(0.0f, 300.0f, 380.0f);
        float3 StartVec_py = new(0.0f, 150.0f, -490.0f);
        float3 Gravity_py = new(0.0f, -200f, 0.0f);

        (double x1, double x2, double y1, double y2)[] PythonParams = {
            (-1.0, 0.0, 401.0, 0.0 ),
            (0.0, -1.0, 0.01, 401.0 ),
            (400.0, -1.0, 400.01, 401.0),
            (-1.0, 400.0, 401.0, 400.0),
            (50.0, 200.0, 120.0, 270.0),
            (150.0, 100.0, 200.0, 100.0),
            (150.0, 285.0, 200.0, 200.0),
            (230.0, 360.0, 300.0, 300.0),
            (300.0, 100.0, 385.0, 150.0),
            (50.0, 50.0, 100.0, 135.0),
        };

        //Parse PythonParams
        Vector3
            StartPos = Vector3.zero,
            StartVec = Vector3.zero,
            Gravity = Vector3.zero;


        StartPos[0] = StartPos_py[0];
        StartPos[1] = StartPos_py[1];
        StartPos[2] = StartPos_py[2];

        StartVec[0] = StartVec_py[0];
        StartVec[1] = StartVec_py[1];
        StartVec[2] = StartVec_py[2];

        Gravity[0] = Gravity_py[0];
        Gravity[1] = Gravity_py[1];
        Gravity[2] = Gravity_py[2];

        float[,,] Wall_parameter = new float[PythonParams.Length, 2, 2];
        for (uint i = 0; i < PythonParams.Length; i++)
        {
            Wall_parameter[i, 0, 0] = (float)(Py_factor * (PythonParams[i].x1));
            Wall_parameter[i, 0, 1] = (float)(Py_factor * (PythonParams[i].x2));

            Wall_parameter[i, 1, 0] = (float)(Py_factor * (PythonParams[i].y1));
            Wall_parameter[i, 1, 1] = (float)(Py_factor * (PythonParams[i].y2));
        }

        // Generate Stage
        Stage demo = new Stage
        (
            number: 1,
            category: "CanonBall",
            name: "CanonBall 2D",
            scene: "RiverWorld",
            description: "CanonBall 2D Test",
            local: false
        );

        // needed to generate facts
        StageStatic.StageOfficial = new Dictionary<string, Stage>
        {
            { demo.name, demo },
        };
        StageStatic.SetStage(demo.name, false);

        // Populate Solution

        string BallURI = StageStatic.stage.solution.Add(
            new PointFact(StartPos, Vector3.up, StageStatic.stage.solution),
            out _, false, null, null);
        StageStatic.stage.solution.ExposedSolutionFacts.Add(BallURI);

        string VecURI = StageStatic.stage.solution.Add(
            new PointFact(StartVec, StartVec.normalized, StageStatic.stage.solution),
            out _, false, null, null);
        StageStatic.stage.solution.ExposedSolutionFacts.Add(VecURI);

        string GravURI = StageStatic.stage.solution.Add(
            new PointFact(Gravity, Gravity.normalized, StageStatic.stage.solution),
            out _, false, null, null);
        StageStatic.stage.solution.ExposedSolutionFacts.Add(GravURI);

        string BounceURI = StageStatic.stage.solution.Add(
            new RealLitFact(Py_bounce, StageStatic.stage.solution),
            out _, false, null, null);
        StageStatic.stage.solution.ExposedSolutionFacts.Add(BounceURI);

        List<LineFact> Walls = new();
        for (int i = 0; i < PythonParams.Length; i++)
        {
            Vector3 tmpVec = Vector3.zero;

            tmpVec[dim_A] = Wall_parameter[i, 0, 1];
            tmpVec[dim_B] = Wall_parameter[i, 0, 0];
            PointFact topA = new(tmpVec, Vector3.up, StageStatic.stage.solution);
            string topAURI = StageStatic.stage.solution.Add(topA, out _, false, null, null);

            tmpVec[dim_A] = Wall_parameter[i, 1, 1];
            tmpVec[dim_B] = Wall_parameter[i, 1, 0];
            PointFact topB = new(tmpVec, Vector3.up, StageStatic.stage.solution);
            string topBURI = StageStatic.stage.solution.Add(topB, out _, true, null, null);

            LineFact topology = new(topAURI, topBURI, StageStatic.stage.solution);
            string lineURI = StageStatic.stage.solution.Add(topology, out _, true, null, null);
            StageStatic.stage.solution.ExposedSolutionFacts.Add(lineURI);

            Walls.Add(topology);
        }

        // Set Solution
        #region  CannonBallScroll
        //StageStatic.stage.solution.Add( // for CannonBallScroll
        //    new ListFact(Walls.Select(w => w.Topology.Id).ToArray(), null, new OMS(MMTConstants.TYPE_TO_OMS[typeof(LineFact)]), StageStatic.stage.solution),
        //    out bool _, true, null, null
        //);

        // special case for Prototype
        SOMDoc[] RRRRTupel = new SOMDoc[Walls.Count];
        for (int i = 0; i < Walls.Count; i++)
        {
            RRRRTupel[i] =
                SOMDoc.MakeTupel(
                    new[] {
                        SOMDoc.MakeTupel(new OMF[] { new((float)PythonParams[i].x1), new((float)PythonParams[i].x2) }),
                        SOMDoc.MakeTupel(new OMF[] { new((float)PythonParams[i].y1), new((float)PythonParams[i].y2) }),
                });
        }
        StageStatic.stage.solution.Add(
            new ListFact(null, RRRRTupel, null, StageStatic.stage.solution),
            out bool _, true, null, null
        );
        #endregion CannonBallScroll

        CanonBallProblemCalculator2D calc = new(Walls, StartPos, StartVec, Gravity, Py_bounce, dim_const, dim_A, dim_B, StageStatic.stage.solution);

        string attacheFactURI = StageStatic.stage.solution.Add(
            new AttachedPositionFunction(BallURI, calc.Result_FuncCall_Id.ToArray(), StageStatic.stage.solution),
            out _, true, null, null);
        StageStatic.stage.solution.ExposedSolutionFacts.Add(attacheFactURI);

        //TODO: ~attach funcs + ~cheat?
        //StageStatic.stage.solution.ValidationSet =
        //        new List<SolutionOrganizer.SubSolution> {
        //        new SolutionOrganizer.SubSolution(new HashSet<string> { target_Id }, null, null, new LineFactHightDirectionComparer()),
        //        new SolutionOrganizer.SubSolution(new HashSet<string> { target_Id }, null, null, new LineSpanningOverRiverWorldComparer()),
        //        new SolutionOrganizer.SubSolution(null, new List<int> { 1 }, new List<int> { 0 }, new LineFactHightComparer()),
        //        };

        // Set Gadgets/ Scrolls
        StageStatic.stage.AllowedGadgets = null;
        StageStatic.stage.AllowedScrolls = null;

        // Save
        StageStatic.SetMode(StageStatic.Mode.Create);
        StageStatic.stage.store(false, true);
    }

    public static void GenerateCanonBallStage3D()
    {
        // Params //List<Wall> walls, T starPos, T starVec, T gravity, int dimension
        int
            dim_G = 1,
            dim_A = 2,
            dim_B = 0;

        float Py_factor = 0.04905f;
        float Py_bounce = 0.1f;

        float3 StartPos_py = new(380, 0, 300);
        float3 StartVec_py = new(-490, 100, 150);
        float3 Gravity_py = new(0, 0, -200);

        double[,,] PythonWalls = new double[,,] {
            {
                { 0, 0, 0 },
                { 400, 0, 0 },
                { 400, 0, 400 },
                { 0, 0, 400 },
            },{
                { 0, 400, 0 },
                { 400, 400, 0 },
                { 400, 400, 400 },
                { 0, 400, 400 },
            },{
                { 250, 0, 0 },
                { 300, 0, 0 },
                { 300, 400, 0 },
                { 250, 400, 0 },
            },{
                { 0, 0, 0 },
                { 0, 0, 400 },
                { 0, 400, 400 },
                { 0, 400, 0 },
            },{
                { 400, 0, 0 },
                { 400, 0, 400 },
                { 400, 400, 400 },
                { 400, 400, 0 },
            },{
                { 0, 0, 400 },
                { 400, 0, 400 },
                { 400, 400, 400 },
                { 0, 400, 400 },
            },{
                { 300, 0, 0 },
                { 400, 0, 0 },
                { 400, 400, 0},
                { 300, 400, 0},
            },{
                { 0, 0, 0 },
                { 250, 0, 0 },
                { 250, 400, 0 },
                { 0, 400, 0 },
            },{
                { 50, 0, 200 },
                { 120.7, 0, 270.7 },
                { 120.7, 200, 270.7 },
                { 50, 200, 200 },
            },{
                { 150, 0, 100 },
                { 200, 0, 100 },
                { 200, 200, 100 },
                { 150, 200, 100 },
            },{
                { 150, 0, 286.6 },
                { 200, 0, 200 },
                { 200, 200, 200 },
                { 150, 200, 286.6 },
            },{
                { 230.7, 0, 340 },
                { 300, 0, 300 },
                { 300, 200, 300 },
                { 230.7, 200, 340 },
            },{
                { 300, 0, 100 },
                { 386.6, 0, 150 },
                { 386.6, 200, 150 },
                { 300, 200, 100 },
            },{
                { 50, 0, 50 },
                { 100, 0, 136.6 },
                { 100, 200, 136.6 },
                { 50, 200, 50 },
        }};

        //Parse PythonParams
        Vector3
            StartPos = Vector3.zero,
            StartVec = Vector3.zero,
            Gravity = Vector3.zero;

        StartPos[dim_A] = StartPos_py[0] * Py_factor;
        StartPos[dim_B] = StartPos_py[1] * Py_factor;
        StartPos[dim_G] = StartPos_py[2] * Py_factor;

        StartVec[dim_A] = StartVec_py[0] * Py_factor;
        StartVec[dim_B] = StartVec_py[1] * Py_factor;
        StartVec[dim_G] = StartVec_py[2] * Py_factor;

        Gravity[dim_A] = Gravity_py[0] * Py_factor;
        Gravity[dim_B] = Gravity_py[1] * Py_factor;
        Gravity[dim_G] = Gravity_py[2] * Py_factor;

        float[,,] Wall_parameter = new float[PythonWalls.GetLength(0), PythonWalls.GetLength(1), PythonWalls.GetLength(2)];
        for (int i = 0; i < PythonWalls.GetLength(0); i++)
            for (int j = 0; j < PythonWalls.GetLength(1); j++)
                for (int k = 0; k < PythonWalls.GetLength(2); k++)
                    Wall_parameter[i, j, k] = (float)(PythonWalls[i, j, k] * Py_factor);

        // Generate Stage
        Stage demo = new Stage
        (
            number: 2,
            category: "CanonBall",
            name: "CanonBall 3D",
            scene: "RiverWorld",
            description: "CanonBall 3D Test",
            local: false
        );

        // needed to generate facts
        StageStatic.StageOfficial = new Dictionary<string, Stage>
        {
            { demo.name, demo },
        };
        StageStatic.SetStage(demo.name, false);

        // Populate Solution

        string BallURI = StageStatic.stage.solution.Add(
            new PointFact(StartPos, Vector3.up, StageStatic.stage.solution),
            out _, false, null, null);
        StageStatic.stage.solution.ExposedSolutionFacts.Add(BallURI);

        string VecURI = StageStatic.stage.solution.Add(
            new PointFact(StartVec, StartVec.normalized, StageStatic.stage.solution),
            out _, false, null, null);
        StageStatic.stage.solution.ExposedSolutionFacts.Add(VecURI);

        string GravURI = StageStatic.stage.solution.Add(
            new PointFact(Gravity, Gravity.normalized, StageStatic.stage.solution),
            out _, false, null, null);
        StageStatic.stage.solution.ExposedSolutionFacts.Add(GravURI);

        string BounceURI = StageStatic.stage.solution.Add(
            new RealLitFact(Py_bounce, StageStatic.stage.solution),
            out _, false, null, null);
        StageStatic.stage.solution.ExposedSolutionFacts.Add(BounceURI);

        List<QuadFact> Walls = new();
        for (int i = 0; i < Wall_parameter.GetLength(0); i++)
        {
            Vector3 tmpVec = Vector3.zero;
            string[] edge = new string[4];

            for (int j = 0; j < Wall_parameter.GetLength(1); j++)
            {
                tmpVec[dim_A] = Wall_parameter[i, j, 0];
                tmpVec[dim_B] = Wall_parameter[i, j, 1];
                tmpVec[dim_G] = Wall_parameter[i, j, 2];

                PointFact edge_point = new(tmpVec, Vector3.up, StageStatic.stage.solution);
                edge[j] = StageStatic.stage.solution.Add(edge_point, out _, false, null, null);
            }

            QuadFact topology = new(edge, StageStatic.stage.solution);
            string quadURI = StageStatic.stage.solution.Add(topology, out _, true, null, null);
            StageStatic.stage.solution.ExposedSolutionFacts.Add(quadURI);

            Walls.Add(topology);
        }

        // Set Solution
        #region  CannonBallScroll
        //StageStatic.stage.solution.Add( // for CannonBallScroll
        //    new ListFact(Walls.Select(q => q.Id).ToArray(), null, new OMS(MMTConstants.TYPE_TO_OMS[typeof(QuadFact)]), StageStatic.stage.solution),
        //    out bool _, true, null, null
        //);

        // special case for Prototype
        SOMDoc[] RRRRTupel = new SOMDoc[Walls.Count];
        for (int i = 0; i < Walls.Count; i++)
        {
            RRRRTupel[i] = SOMDoc.MakeTupel(
                Walls[i].Pids.Select(p => new OMS(p)).ToArray()
            );
        }
        StageStatic.stage.solution.Add(
            new ListFact(null, RRRRTupel, null, StageStatic.stage.solution),
            out bool _, true, null, null
        );
        #endregion CannonBallScroll

        CanonBallProblemCalculator3D calc = new(Walls, StartPos, StartVec, Gravity, Py_bounce, dim_G, dim_A, dim_B, StageStatic.stage.solution);

        string attacheFactURI = StageStatic.stage.solution.Add(
            new AttachedPositionFunction(BallURI, calc.Result_FuncCall_Id.ToArray(), StageStatic.stage.solution),
            out _, true, null, null);
        StageStatic.stage.solution.ExposedSolutionFacts.Add(attacheFactURI);

        //TODO: ~attach funcs + ~cheat?
        //StageStatic.stage.solution.ValidationSet =
        //        new List<SolutionOrganizer.SubSolution> {
        //        new SolutionOrganizer.SubSolution(new HashSet<string> { target_Id }, null, null, new LineFactHightDirectionComparer()),
        //        new SolutionOrganizer.SubSolution(new HashSet<string> { target_Id }, null, null, new LineSpanningOverRiverWorldComparer()),
        //        new SolutionOrganizer.SubSolution(null, new List<int> { 1 }, new List<int> { 0 }, new LineFactHightComparer()),
        //        };

        // Set Gadgets/ Scrolls
        StageStatic.stage.AllowedGadgets = null;
        StageStatic.stage.AllowedScrolls = null;

        // Save
        StageStatic.SetMode(StageStatic.Mode.Create);
        StageStatic.stage.store(false, true);
    }
}