using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Drawing;

using AndrewTweddle.Tools.Utilities.CommandLine;

namespace puppyfarm {
    public class Puppy{
        public byte[] dna;
        public string name;
        public int lives;
        public Puppy p0, p1;

        public bool Equals(Puppy o)
        {
            return BitConverter.ToString(this.dna).Replace("-", "").Equals(BitConverter.ToString(o.dna).Replace("-", ""));
        }

        public int GenomeDistance(Puppy that)
        {
            int ret = 0;
            for (int i = 0; i < 32; i++)
            {
                ret += (this.dna[i] == that.dna[i] ? 0 : 1);
            }
            return ret;
        }

        public int LargestChunkInCommon(Puppy that, int ignoreLeadingBytes = 5)
        {
            int cur = 0;
            int max = 0;
            for (int i = ignoreLeadingBytes; i < 32; i++)
            {
                if (this.dna[i] == that.dna[i])
                {
                    cur++;
                }
                else
                {
                    max = Math.Max(cur, max);
                    cur = 0;
                }
            }
            return Math.Max(cur, max);
        }

        public bool PossibleParents(Puppy p0, Puppy p1)
        {

            return false;
        }
    }

    public class PuppyComparer : IEqualityComparer<Puppy>
    {
        public bool Equals(Puppy p0, Puppy p1)
        {
            return BitConverter.ToString(p0.dna).Replace("-", "").Equals(BitConverter.ToString(p1.dna).Replace("-", ""));
        }
        public int GetHashCode(Puppy p)
        {
            string s = BitConverter.ToString(p.dna).Replace("-", "");
            return s.GetHashCode();
        }
    }

    public class Program {
        private const float KENNEL_TOUR_LENGTH_SECS = 10.0f;
        private const float DOG_SHOW_LENGTH_SECS = 5.0f;
        private const int INITIAL_MONEY = 1000;
        private const int PUPPY_LIVES = 4;

        private static List<Puppy> puppies = new List<Puppy>();
        private static Dictionary<Puppy, int> scoretable = new Dictionary<Puppy, int>(new PuppyComparer());
        private static double farmOpenTime = 0;
        private static ulong ticksPerSecond = 1000;
        private static Random random = new Random();
        private static string ourPath = "";
        private static int money = INITIAL_MONEY;
        StringComparer ic = StringComparer.OrdinalIgnoreCase;

        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceCounter(out ulong lpPerformanceCount);
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(out ulong lpFrequency);

        enum State
        {
            ANALYZE, SHOW, SHOW_ALL, BREED, SELECT, TOUR, INIT
        };


        public static double GetTimeInSecs() {
            ulong ticks;
            double time;

            QueryPerformanceCounter(out ticks);

            time = (double)ticks / (double)ticksPerSecond;
            time -= farmOpenTime;
            return time;
        }

        private static void SequenceBreedingStock() {
            string[] filenames = Directory.GetFiles("trophyroom", "*.com");

            Console.WriteLine("Sequencing " + filenames.Length + " pedigree breeding stock puppies...\n");
            foreach (string filename in filenames) {
                FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
                BinaryReader reader = new BinaryReader(fs);

                long bytelength = new FileInfo(filename).Length;
                if (bytelength > 32)
                    Console.WriteLine("! Ignoring puppy " + filename + " (" + bytelength + " bytes)");
                else {
                    byte[] dna = reader.ReadBytes((Int32)bytelength);

                    while (dna.Length < 32) {
                        byte[] copy = new byte[dna.Length + 1];
                        dna.CopyTo(copy, 0);
                        copy[dna.Length] = 0x90;
                        dna = copy;
                    }
                    Puppy p = new Puppy();
                    p.lives = PUPPY_LIVES;
                    p.dna = dna;
                    string s = filename.Substring(filename.LastIndexOf("\\") + 1);
                    p.name = s.Substring(0, s.Length - 4);
                    int pScore = 0;
                    if (!scoretable.TryGetValue(p, out pScore))
                    {
                        puppies.Add(p);
                        scoretable.Add(p, 1);
                    } else {
                        scoretable[p] = pScore + 1; 
                        Console.WriteLine(p.name + " is a duplicate, score increased");
                    }
                }

                fs.Close();
                fs.Dispose();
                reader.Close();
            }
            Console.WriteLine(puppies.Count + " puppies sequenced OK.\n");
        }

        private static Puppy Breed(Puppy parent1, Puppy parent2) {
            int seqLen = random.Next(1, 7);

            Console.WriteLine("Mating " + parent1.name + " & " + parent2.name + "...\n");

            Console.WriteLine("      Byte: 1 2 3 4 5 6 7 8 9 1011121314151617181920212223242526272829303132");
            Console.Write(" Chunk src: ");

            byte[] mutantPuppyDNA = new byte[32];
            
            Puppy currentParent = parent1;
            for (int i = 0; i < 32; i++) {
                if (i % seqLen == 0) {
                    if (random.NextDouble() > 0.5) {
                        currentParent = parent2;
                        Console.Write("|2");
                    } else {
                        currentParent = parent1;
                        Console.Write("|1");
                    }
                    for (int j = 0; j < seqLen - 1; j++)
                        Console.Write("  ");
                }

                mutantPuppyDNA[i] = currentParent.dna[i];
            }

            Puppy newPuppy = new Puppy();
            newPuppy.lives = PUPPY_LIVES;
            newPuppy.dna = mutantPuppyDNA;
            newPuppy.name = "(" + parent1.name + "X" + parent2.name + "@" + seqLen + ")";
            newPuppy.p0 = parent1;
            newPuppy.p1 = parent2;

            Console.WriteLine();
            Console.WriteLine("  Parent 1: " + BitConverter.ToString(parent1.dna).Replace("-", ""));
            Console.WriteLine("  Parent 2: " + BitConverter.ToString(parent2.dna).Replace("-", ""));
            Console.WriteLine(" Offspring: " + BitConverter.ToString(mutantPuppyDNA).Replace("-", "") + "\n");
            
            return newPuppy;
        }

        private static void RenderPuppy(Puppy p, string filename) {
            FileStream outstream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write);
            BinaryWriter writer = new BinaryWriter(outstream);
            writer.Write(p.dna, 0, 32);
            writer.Close();
            writer.Dispose();
            outstream.Close();
        }

        private static int Score(Bitmap screenie) {
            int score = 0;

            for (int x = 30; x < screenie.Width - 30; x++) {
                for (int y = 30; y < screenie.Height - 30; y++) {
                    Color c = screenie.GetPixel(x, y);
                    if (c.R != 0 || c.G != 0 || c.B != 0)
                        score++;
                }
            }

            return score;
        }

        private static Process RunPuppy(string filename, bool autoclose = false, float length = DOG_SHOW_LENGTH_SECS) {
            Process shellProc = Process.Start(ourPath + "\\tools\\DOSBox\\dosbox.exe", ourPath + "\\" + filename + " -noconsole -exit -noautoexec");
            double startTime = GetTimeInSecs();
            while (GetTimeInSecs() < startTime + length && !shellProc.HasExited) ;

            if (autoclose) {
                if (!shellProc.HasExited)
                    shellProc.Kill();

                return null;
            }

            return shellProc;
        }

        private static void Main(string[] args) {

            State state = State.INIT;
            ourPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            Bitmap screenie = new Bitmap(1, 1);
            QueryPerformanceFrequency(out ticksPerSecond);
            farmOpenTime = GetTimeInSecs();

            Console.WriteLine("*******************************************************************************\nWelcome to Puppy Farm Manager\nProgressive Peregrin Patch (Pro 2012 season edition)\nby Fell^RiFT and skomp^dss!\n*******************************************************************************\n");

            // Throw some puppies in the kennel
            SequenceBreedingStock();
            Console.WriteLine("You have $" + money + ".00.");
            //string 
            do
            {
                switch (state)
                {
                    case State.INIT:
                        state = selectActionAfterInit(state);
                        break;
                    case State.TOUR:
                        state = GetTouringAction(state);
                        break;
                    case State.ANALYZE:
                        state = AnalyzePuppy();
                        break;
                    case State.SHOW:
                        state = ShowPuppy();
                        break;
                    case State.SHOW_ALL:
                        state = ShowAll();
                        break;
                }
            } while (state != State.BREED);

            Console.WriteLine("\nPress ENTER to begin mating season!");
            Console.ReadLine();

            while (true) {
                if (puppies.Count < 2) {
                    Console.WriteLine("\nOH NOES -- THE KENNEL'S OUT OF PUPPIES!\n\nGAME OVER.");
                    Console.ReadLine();
                    return;
                }

                Console.WriteLine("\n----------------------------------------------------\nLet's breed some PUPPIES\n(" + puppies.Count + " puppies in the kennel, $" + money + ".00 in the bank)\n----------------------------------------------------\n");

                // Select a breeding pair from the kennel
                int parent1index = random.Next(0, puppies.Count);
                int parent2index = parent1index;
                while (parent2index == parent1index)
                    parent2index = random.Next(0, puppies.Count);

                // Breed em
                Puppy parent1 = puppies[parent1index];
                Puppy parent2 = puppies[parent2index];
                Puppy newPuppy = Breed(parent1, parent2);
                parent1.lives--;
                if (parent1.lives == 0) {
                    Console.WriteLine("OH NOES! " + parent1.name + " has died :(");
                    puppies.Remove(parent1);
                }
                parent2.lives--;
                if (parent2.lives == 0) {
                    Console.WriteLine("OH NOES! " + parent2.name + " has died :(");
                    puppies.Remove(parent2);
                }
                int currentScore = 0;
                bool kennelContains = scoretable.TryGetValue(newPuppy, out currentScore);
                if (kennelContains)
                {
                    continue;
                }
                else if (!kennelContains)
                    scoretable.Add(newPuppy, 1);
                // Render the phenotype
                string filename = "temppup.com";
                RenderPuppy(newPuppy, filename);
                //Console.WriteLine("Rendered puppy as " + filename);

                // Run the puppy for a few secs
                Console.WriteLine("Taking the puppy to the dogshow (cost: $100)...");
                money -= 100;
                if (money <= 0) {
                    Console.WriteLine("\nOH NOES -- YOU'RE SKINT!\n\nGAME OVER.");
                    Console.ReadLine();
                    return;
                }
                Process shellProc = RunPuppy(filename);

                // Did it crash?
                if (shellProc.HasExited) {
                    File.Delete(filename);
                    Console.WriteLine("\nPUPPY SHAT ON THE STAGE -- DISQUALIFIED FROM DOGSHOW :(");
                    scoretable[newPuppy] = -1;
                    puppies.RemoveAll(newPuppy.Equals);
                    continue;
                }

                // Capture bitmap
                ScreenCapture sc = new ScreenCapture();
                Image result = sc.CaptureWindow(shellProc.MainWindowHandle);
                screenie = new Bitmap(result);

                // A bad man comes along and drowns the puppy :(
                if (!shellProc.HasExited)
                    shellProc.Kill();

                // Score the result
                int score = Score(screenie);    // Here be magic heuristic function ^_^
                money += (int)(score/250);
                Console.WriteLine("\nThat puppy scored " + score + " at the dogshow!");
                Console.WriteLine("You have $" + money + ".00.");

                // Throw the puppy in the kennel if good enough (also keep the genotype and save the screenshot)...
                if (score > 50)
                {
                    //Console.Beep();
                    string s = "R";
                    while (s.StartsWith("R") || s.StartsWith("r"))
                    {
                        Console.Write("\nAdd " + newPuppy.name + " to the kennel?\n\nOptions:\n\nYes and send to [T]rophyroom ($1000)\n[Y]es ($200)\n[N]o (also removes all future clones)\nView [D]isassembler\n[R]epeat dogshow\n\n> ");
                        s = Console.ReadLine();
                        if (s.StartsWith("T") || s.StartsWith("t"))
                        {
                            if (money < 1200)
                            {
                                Console.WriteLine("You can't afford the trophy!!");
                                continue;
                            }
                            money -= 1200;
                            Console.WriteLine("Trophy + kennelling fees paid -- $1200");
                            bool written;
                            string trophyFilename;
                            string newname = "";
                            do
                            {
                                written = false;
                                newname = Utils.GetElitishName(random.Next());
                                newPuppy.name = newname;
                                trophyFilename = "trophyroom\\" + newname + ".com";
                                try {
                                    File.Copy(filename, trophyFilename);
                                    written = true;
                                }
                                catch (Exception e)
                                {
                                }
                            } while (!written);
                            screenie.Save("trophyroom\\" + newPuppy.name + ".png", System.Drawing.Imaging.ImageFormat.Png);
                            string asm = "A 32-byte DOS intro from the Puppy Farm\r\n\r\n" + newPuppy.name + (random.Next(2) ==1?", Son" : ", Daughter of ") + newPuppy.p0.name + " and " + newPuppy.p1.name + "\r\n\r\n";
                            asm += "Source:\r\n\r\n";
                            asm += CommandLineHelper.Run(ourPath + "\\tools\\nbasm\\nbdisasmw.exe ", ourPath + "\\" + trophyFilename);
                            File.WriteAllText("trophyroom\\" + newPuppy.name + ".txt", asm);
                            if (kennelContains)
                            {
                                scoretable[newPuppy]++;
                            }
                            else
                                puppies.Add(newPuppy);
                            Console.WriteLine("\nPuppy renamed " + newname + " and added to kennel!\n\nProgram, source and pic copied to trophy room ready to upload to pouet.net ;)\n");
                            Console.WriteLine("Press ENTER to continue...");
                            Console.ReadLine();
                        }
                        else if (s.StartsWith("Y") || s.StartsWith("y"))
                        {
                            if (money < 200)
                            {
                                Console.WriteLine("You can't afford the kennelling fees!!");
                                continue;
                            }
                            money -= 200;
                            Console.WriteLine("Kennelling fees paid -- $200");
                            puppies.Add(newPuppy);
                        }
                        else if (s.StartsWith("R") || s.StartsWith("r"))
                            RunPuppy(filename, true);
                        else if (s.StartsWith("D") || s.StartsWith("d"))
                        {
                            string asm = CommandLineHelper.Run(ourPath + "\\tools\\nbasm\\nbdisasmw.exe ", ourPath + "\\" + filename);
                            Console.WriteLine(asm);
                            s = "R";    // Keep it going...
                        }
                        else if (kennelContains)
                        {
                            puppies.RemoveAll(newPuppy.Equals);
                            scoretable[newPuppy] = -1;
                        }
                    }
                }
                else
                {
                    Console.WriteLine("Puppy didn't make the cut, moving on...");
                    scoretable[newPuppy] = -1;
                    puppies.RemoveAll(newPuppy.Equals);
                }

                File.Delete(filename);
            }
        }

        private static State selectActionAfterInit(State state)
        {
            Console.Write("Tour/Analize the kennels before mating season? ([Y]es/[N]o) > ");
            string tour = Console.ReadLine();
            Console.WriteLine();
            if (tour == "Y" || tour == "y")
            {
                return State.TOUR;
            }
            return State.BREED;
        }

        private static State GetTouringAction(State state)
        {
            for (int i = 0; i < puppies.Count; i++)
            {
                Console.WriteLine((i + 1) + ": " + puppies[i].name);
            }
            Console.WriteLine("[A]nalyze/[V]iew specific? [S]how all puppies for " + KENNEL_TOUR_LENGTH_SECS + "s");
            string tour = Console.ReadLine();
            if (tour == "S" || tour == "s")
                return State.SHOW_ALL;
            else
                return tour == "a" || tour == "A" ? State.ANALYZE : State.SHOW;;

        }

        private static State ShowAll()
        {
            for (int i = 0; i < puppies.Count; i++)
            {
                Console.WriteLine("Viewing puppy " + puppies[i].name + "...");
                string filename = puppies[i].name + ".com";
                RenderPuppy(puppies[i], filename);
                //Console.WriteLine("Dumping puppy " + puppies[i].name + "..."); 
                //string asm = CommandLineHelper.Run(ourPath + "\\tools\\nbasm\\nbdisasmw.exe ", ourPath + "\\" + filename);
                //Console.WriteLine(asm);
                RunPuppy(filename, true, KENNEL_TOUR_LENGTH_SECS);
                File.Delete(filename);
            }
            return State.TOUR;
        }

        private static State AnalyzePuppy()
        {
            int index = -1;
            do
            {
                try
                {
                    Console.WriteLine("Which one? [number]/[C]ancel");
                    string pToShow = Console.ReadLine();
                    if (pToShow == "C" || pToShow == "c")
                        return State.INIT;
                    else
                        index = int.Parse(pToShow) -1;
                }
                catch (Exception e)
                { Console.WriteLine("Enter valid number or [C]ancel!"); }
            } while (index == -1);
            Puppy p0 = puppies[index];
            Console.WriteLine("Looking for largest common chunks and calculating DNA similarities...");
            List<PuppyCompared> commons = new List<PuppyCompared>();
                
            foreach(Puppy p1 in puppies)
            {
                if (p0 == p1)
                    continue;
                PuppyCompared cmp = new PuppyCompared(p0, p1);
                cmp.Analyze();
                commons.Add(cmp);
            }
            commons.Sort();
            commons.Reverse();
            Console.WriteLine("Analysis result:\n+---------------------------------------------------------------------+\n"
                + "|Original\n|Name: " + p0.name + "\n|DNA: "
                + BitConverter.ToString(p0.dna).Replace("-", ""));
            Console.WriteLine("+---------------------------------------------------------------------+\n" + 
            "Most similar (Top 10)\n+---------------------------------------------------------------------+\n");
            //for (int i = commons.Count - 1; i >= commons.Count - 10; i--)
            for(int i = 0; i < 10; i++)
            {
                Console.WriteLine("|Name: " + commons[i].p1.name);
                Console.WriteLine("|DNA: " + BitConverter.ToString(commons[i].p1.dna).Replace("-", ""));
                Console.WriteLine("|LCC: " + commons[i].largestCommonChunk);
                Console.WriteLine("|DST: " + commons[i].distance);
                Console.WriteLine("+---------------------------------------------------------------------+");

            }
            return State.ANALYZE;
        }

        private static State ShowPuppy()
        {
            int index = -1;
            do
            {
                try
                {
                    Console.WriteLine("Which one? (number)/[C]ancel");
                    string pToShow = Console.ReadLine();
                    if (pToShow == "C" || pToShow == "c")
                        return State.INIT;
                    else
                        index = int.Parse(pToShow) - 1;
                }
                catch (Exception e)
                { Console.WriteLine("Enter valid number or [C]ancel!"); }
            } while (index == -1);
            string filename = puppies[index].name + ".com";
            RenderPuppy(puppies[index], filename);
            RunPuppy(filename, true, KENNEL_TOUR_LENGTH_SECS);
            return State.SHOW;
        }
    }

    public class PuppyCompared : IComparable<PuppyCompared>
    {
        public PuppyCompared(Puppy p0, Puppy p1)
        {
            this.p0 = p0;
            this.p1 = p1;
        }
        public Puppy p0, p1;
        public int largestCommonChunk = 0;
        public int distance = 32;

        public PuppyCompared Analyze(bool fullGenome = false)
        {
            largestCommonChunk = p0.LargestChunkInCommon(p1, fullGenome ? 0 : 5);
            distance = p0.GenomeDistance(p1);
            return this;
        }

        public int CompareTo(PuppyCompared that)
        {
            if (p0.Equals(that.p0) && p1.Equals(that.p1))
            {
                return 0;
            }
            else
            {
                int cc = this.largestCommonChunk - that.largestCommonChunk;
                int dc = that.distance - this.distance;
                if (this.largestCommonChunk == that.largestCommonChunk)
                {
                    if (this.distance == that.distance)
                    {
                        return 1;
                    }
                    else return that.distance.CompareTo(this.distance);
                }
                else return this.largestCommonChunk.CompareTo(that.largestCommonChunk);
            }
        }
    }
}

