using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Timer = System.Windows.Forms.Timer;
namespace Moving_Particles
{
    public partial class Form1 : Form
    {
        BackgroundWorker worker = new BackgroundWorker();
        Timer workerLoop = new Timer();
        Timer renderLoop = new Timer();
        private Bitmap renderBitmap;
        private Graphics finalGraphics;
      
        List<DrawElement> ElementsList = new List<DrawElement>();
        Random randomVar = new Random();
        private int minElements = 20;
        private int currMaxElements = 40;
        private int maxElement = 150;
        private int timeToUpMaxElement = 10;
        private int currTime = 0;
        private string CharactersToChoose = "ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘヲイクコソチトノフヤヨルレロン";
        List<char> getKanas()
        {
            return CharactersToChoose.ToCharArray().ToList();
        }
        Char getRandomKana()
        {
            List<char> kanas = getKanas();
            char result = kanas[randomVar.Next(0, kanas.Count - 1)];
            return result;
        }
        public Form1()
        {
            SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true);
            UpdateStyles();
            InitializeComponent();
            worker.DoWork += Worker_DoWork;
            worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
            workerLoop.Tick += WorkerLoop_Tick;
            renderLoop.Tick += RenderLoop_Tick;
            workerLoop.Interval = 50;
            renderLoop.Interval = 60;
            panel1.MouseDoubleClick += Panel1_MouseDoubleClick;
        }
        private void Panel1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                finalGraphics = panel1.CreateGraphics();
                renderBitmap = new Bitmap(panel1.Width, panel1.Height);
                workerLoop.Start();
                renderLoop.Start();
                //addCursorTracker();
                AddUnlimitedMatrixChars(100);
            }
        }
        async private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Bitmap bitRender = e.Result as Bitmap;
            await DrawRenderPic(bitRender);
        }
        async Task DrawRenderPic(Bitmap referPic)
        {
            Graphics rGraph = Graphics.FromImage(renderBitmap);
            rGraph.DrawImageUnscaled(referPic, new Point(0, 0));
            rGraph.Dispose();
            referPic.Dispose();
        }
        private void RenderLoop_Tick(object sender, EventArgs e)
        {
            finalGraphics.DrawImageUnscaled(renderBitmap,new Point(0,0));
        }
        private void WorkerLoop_Tick(object sender, EventArgs e)
        {
            if (!worker.IsBusy)
            {
                worker.RunWorkerAsync();
                if (currTime < timeToUpMaxElement)
                {
                    currTime += 1;
                }
                else
                {
                    currTime = 0;
                    if (currMaxElements < maxElement)
                    {
                        currMaxElements += 1;
                    }
                }
            }
            else
            {
                if (currMaxElements > minElements)
                {
                    currMaxElements -= 1;
                    currTime = 0;
                }
            }
        }
        private void Worker_DoWork(object sender, DoWorkEventArgs e)
        {
            Bitmap tempPic = new Bitmap(panel1.Width,panel1.Height);
            List<DrawElement> tempList = new List<DrawElement>();
            Graphics tempPicGraphs = Graphics.FromImage(tempPic);
            tempList.AddRange(ElementsList);
            tempPicGraphs.Clear(panel1.BackColor);
            foreach (DrawElement drawElement in tempList)
            {
                drawElement.Draw(tempPicGraphs);
                if (drawElement.DeleteMySelf)
                {
                    ElementsList.Remove(drawElement);
                }
            }
            e.Result = tempPic;
            tempPicGraphs.Dispose();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
          
        }
        void AddMatrixChars(int count)
        {
            Timer t = new Timer();
            t.Interval = 500;
            int curr = 0;
            t.Tick += delegate(object sender, EventArgs args)
            {
                if (curr < count)
                {
                    curr += 1;
                    addMatrixChar();
                }
                else
                {
                    t.Stop();
                }
            };
            t.Start();
        }
        void AddUnlimitedMatrixChars(int interval)
        {
            Timer t = new Timer();
            t.Interval = interval;
            int curr = 0;
            t.Tick += delegate (object sender, EventArgs args)
            {
                if (ElementsList.Count < currMaxElements)
                {
                    addMatrixChar();
                }
                this.Text = ElementsList.Count + "/" + currMaxElements;
            };
            t.Start();
        }
        void addCursorTracker()
        {
            DrawElement el = new DrawElement();
            el.Location = new PointF(10,10);
            el.Size = new SizeF(20,20);
            el.BackColor = Color.Red;
            float x = 10;
            float y = 10;
            panel1.MouseMove += delegate(object sender, MouseEventArgs args)
            {
                x = args.X;
                y = args.Y;
            };
            Action<Graphics> draw = delegate(Graphics graphics)
            {
              
            };
            Action update = delegate
            {
                el.Location = new PointF(x,y);
            };
            el.DrawAction = draw;
            el.Update = update;
            ElementsList.Add(el);
        }
        void addAutoMovingBall()
        {
            DrawElement el = new DrawElement();
            el.Location = getRandomPoint();
            int randomSize = new Random().Next(10,30);
            el.Size = new SizeF(randomSize, randomSize);
            el.BackColor = getRandomColor(0,255);
            int timeToCChange = 1;
            int currentTime = 0;
            int timeToBlink = 2;
            int currBlink = 0;
            bool blink = false;
            bool follow = false;
            Color desiredColor = getRandomColor(155,255);
            float Speed = new Random().Next(1,2);
            PointF Destination = getRandomPoint();
            panel1.MouseDoubleClick += delegate(object sender, MouseEventArgs args)
            {
                if (args.Button == MouseButtons.Left)
                {
                    //Destination = new PointF(args.X,args.Y);
                    follow = !follow;
                }
            };
            panel1.MouseMove += delegate(object sender, MouseEventArgs args)
            {
                if (follow)
                {
                    Destination = new PointF(args.X,args.Y);
                }
            };
            Action<Graphics> draw = delegate (Graphics graphics)
            {
                graphics.SmoothingMode = SmoothingMode.AntiAlias;
                float step = el.Size.Width / 2;
                int count = 0;
                int minAlpha = 10;
                int checkAlpha = 100;
                bool reverse = false;
                Color baseColor = el.BackColor;
                for (float i = 0; i < step; i+=1)
                {
                    RectangleF r = new RectangleF((el.Location.X)+(el.Size.Width / 2) - i, (el.Location.Y)+(el.Size.Height / 2) - i, (i * 2), (i * 2));
                    int iInt32 = (int) i;
                    int alpha = fixColor(150)-((150/((int)step))*((int)i));
                    int red = fixColor(baseColor.R - iInt32 * 2);
                    int green = fixColor(baseColor.G - iInt32 * 2);
                    int blue = fixColor(baseColor.B - iInt32 * 2);
                    Color toApply = Color.FromArgb(alpha, red, green, blue);
                    //g.FillEllipse(new SolidBrush(toApply), r);
                    graphics.DrawEllipse(new Pen(toApply, 1.9f), r);
                }
            };
            Action update = delegate
            {
                bool xOk = false;
                bool yOk = false;
                bool redOk = false;
                bool greenOk = false;
                bool blueOk = false;
                int newRed = el.BackColor.R;
                int newGreen = el.BackColor.G;
                int newBlue = el.BackColor.B;
                int StepC = 1;
                if (currentTime < timeToCChange)
                {
                    currentTime += 1;
                }
                else
                {
                    currentTime = 0;
                    if (el.BackColor.R > desiredColor.R)
                    {
                        newRed = el.BackColor.R - StepC;
                    }
                    else
                    {
                        if (el.BackColor.R < desiredColor.R)
                        {
                            newRed = el.BackColor.R + StepC;
                        }
                        else
                        {
                            if (el.BackColor.R == desiredColor.R)
                            {
                                redOk = true;
                            }
                        }
                    }
                    if (el.BackColor.G > desiredColor.G)
                    {
                        newGreen = el.BackColor.G - StepC;
                    }
                    else
                    {
                        if (el.BackColor.G < desiredColor.G)
                        {
                            newGreen = el.BackColor.G + StepC;
                        }
                        else
                        {
                            if (el.BackColor.G == desiredColor.G)
                            {
                                greenOk = true;
                            }
                        }
                    }
                    if (el.BackColor.B > desiredColor.B)
                    {
                        newBlue = el.BackColor.B - StepC;
                    }
                    else
                    {
                        if (el.BackColor.B < desiredColor.B)
                        {
                            newBlue = el.BackColor.B + StepC;
                        }
                        else
                        {
                            if (el.BackColor.B == desiredColor.B)
                            {
                                blueOk = true;
                            }
                        }
                    }
                    el.BackColor = Color.FromArgb(newRed, newGreen, newBlue);
                    if (redOk && greenOk && blueOk)
                    {
                        if (currBlink < timeToBlink)
                        {
                            currBlink += 1;
                            desiredColor = getRandomColor(155, 255);
                        }
                        else
                        {
                            currBlink = 0;
                            desiredColor = Color.Black;
                        }
                      
                    }
                }
                if (el.Location.X < Destination.X)
                {
                    el.Location.X += Speed;
                }
                else
                {
                    if (el.Location.X > Destination.X)
                    {
                        el.Location.X -= Speed;
                    }
                    else
                    {
                        if (el.Location.X == Destination.X)
                        {
                            xOk = true;
                        }
                    }
                }
                if (el.Location.Y < Destination.Y)
                {
                    el.Location.Y += Speed;
                }
                else
                {
                    if (el.Location.Y > Destination.Y)
                    {
                        el.Location.Y -= Speed;
                    }
                    else
                    {
                        if (el.Location.Y == Destination.Y)
                        {
                            yOk = true;
                        }
                    }
                }
                if (xOk && yOk)
                {
                    Destination = getRandomPoint();
                }
            };
            el.DrawAction = draw;
            el.Update = update;
            ElementsList.Add(el);
        }
        void addMatrixChar()
        {
            DrawElement el = new DrawElement();
            el.Location = new PointF(randomVar.Next(0,panel1.Width), -350);
            el.Size = new SizeF(20, 20);
            el.BackColor = Color.White;
            int step = randomVar.Next(3,15);
            float fStep = (float) (step/10);
            int kanaChangeTime = 2;
            int currKanaChange = 0;
            float textWidth = randomVar.Next(8, 32);
            int shadowCount = randomVar.Next(6,31);
          
            Action<Graphics> draw = delegate (Graphics graphics)
            {
                string character = el.Tag.ToString();
                GraphicsPath gp = new GraphicsPath();
                gp.AddString(character, this.Font.FontFamily, 0, textWidth, el.Location, StringFormat.GenericDefault);
                for (int i = 1; i < shadowCount; i++)
                {
                    string shadowChar = "";
                    int shadowTagIndex = (el.TagHistory.Count - 1) - i;
                    if (shadowTagIndex >= 0)
                    {
                        shadowChar = el.TagHistory[shadowTagIndex].ToString();
                    }
                    GraphicsPath gpShadow = new GraphicsPath();
                    int alpha = 255 - ((230/shadowCount)*i);
                    float yLocation = el.Location.Y - (((float)(textWidth*1.2))*i);
                    gpShadow.AddString(shadowChar, this.Font.FontFamily, 0, textWidth, new PointF(el.Location.X,yLocation), StringFormat.GenericDefault);
                    graphics.FillPath(new SolidBrush(Color.FromArgb(alpha,Color.Green)), gpShadow);
                }
                int maxHalo = ((int)(textWidth / 2));
                if (maxHalo > 9)
                {
                    maxHalo = 9;
                }
                for (int i = 1; i < maxHalo; ++i)
                {
                    Pen pen = new Pen(Color.FromArgb(32, el.BackColor), i);
                    pen.LineJoin = LineJoin.Round;
                    graphics.DrawPath(pen, gp);
                    pen.Dispose();
                }
                graphics.FillPath(new SolidBrush(el.BackColor), gp);
            };
            Action update = delegate
            {
                if (el.Location.Y < panel1.Height + ((float)(textWidth * 1.2) * shadowCount))
                {
                    el.Location.Y += step;
                    if (currKanaChange < kanaChangeTime)
                    {
                        currKanaChange += 1;
                    }
                    else
                    {
                        currKanaChange = 0;
                        el.AddTag(getRandomKana().ToString());
                    }
                }
                else
                {
                    el.DeleteMySelf = true;
                }
            };
            el.AddTag(getRandomKana().ToString());
            el.DrawAction = draw;
            el.Update = update;
            ElementsList.Add(el);
        }
        void addErrorMatrixChar()
        {
            DrawElement el = new DrawElement();
            el.Location = new PointF(randomVar.Next(0, panel1.Width), -350);
            el.Size = new SizeF(20, 20);
            el.BackColor = Color.DarkRed;
            int step = randomVar.Next(3, 15);
            float fStep = (float)(step / 10);
            int kanaChangeTime = 2;
            int currKanaChange = 0;
            float textWidth = randomVar.Next(8, 32);
            int shadowCount = randomVar.Next(6, 31);
            Action<Graphics> draw = delegate (Graphics graphics)
            {
                string character = el.Tag.ToString();
                GraphicsPath gp = new GraphicsPath();
                gp.AddString(character, this.Font.FontFamily, 0, textWidth, el.Location, StringFormat.GenericDefault);
                for (int i = 1; i < shadowCount; i++)
                {
                    string shadowChar = "";
                    int shadowTagIndex = (el.TagHistory.Count - 1) - i;
                    if (shadowTagIndex >= 0)
                    {
                        shadowChar = el.TagHistory[shadowTagIndex].ToString();
                    }
                    GraphicsPath gpShadow = new GraphicsPath();
                    int alpha = 255 - ((230 / shadowCount) * i);
                    float yLocation = el.Location.Y - (((float)(textWidth * 1.2)) * i);
                    gpShadow.AddString(shadowChar, this.Font.FontFamily, 0, textWidth, new PointF(el.Location.X, yLocation), StringFormat.GenericDefault);
                    graphics.FillPath(new SolidBrush(Color.FromArgb(alpha, Color.Green)), gpShadow);
                }
                for (int i = 1; i < 14; ++i)
                {
                    Pen pen = new Pen(Color.FromArgb(16, el.BackColor), i);
                    pen.LineJoin = LineJoin.Round;
                    graphics.DrawPath(pen, gp);
                    pen.Dispose();
                }
                graphics.FillPath(new SolidBrush(el.BackColor), gp);
            };
            Action update = delegate
            {
                if (el.Location.Y < panel1.Height + ((float)(textWidth * 1.2) * shadowCount))
                {
                    el.Location.Y += step;
                    if (currKanaChange < kanaChangeTime)
                    {
                        currKanaChange += 1;
                    }
                    else
                    {
                        currKanaChange = 0;
                        el.AddTag(getRandomKana().ToString());
                    }
                }
                else
                {
                    el.DeleteMySelf = true;
                }
            };
            el.AddTag(getRandomKana().ToString());
            el.DrawAction = draw;
            el.Update = update;
            ElementsList.Add(el);
        }
        PointF getRandomPoint()
        {
            return new PointF(randomVar.Next(0,panel1.Width),randomVar.Next(0,panel1.Height));
        }
        Color getRandomColor(int min,int max)
        {
            return Color.FromArgb(randomVar.Next(min,max),randomVar.Next(min,max),randomVar.Next(min,max));
        }
        int fixColor(int input)
        {
            int result = 0;
            if (input < 0)
            {
                result = 0;
            }
            else
            {
                if (input > 254)
                {
                    result = 254;
                }
                else
                {
                    result = input;
                }
            }
            return result;
        }
        private void panel1_Paint(object sender, PaintEventArgs e)
        {
        }
    }
    public class DrawElement
    {
        public PointF Location;
        public SizeF Size;
        public Color BackColor;
        public Object Tag;
        public Action<Graphics> DrawAction;
        public Action Update;
        public List<Object> TagHistory = new List<object>();
        public bool DeleteMySelf = false;
        public DrawElement()
        {
          
        }
        public void AddTag(string tagString)
        {
            if (Tag != null)
            {
                TagHistory.Add(Tag);
                Tag = tagString;
            }
            else
            {
                Tag = tagString;
            }
        }
        async public void Draw(Graphics g)
        {
            DrawAction(g);
            Update();
        }
    }
}