internal static long Part1(string input)
        {
            string[]         lines    = input.Split('\n');
            List <ImageTile> tileList = new List <ImageTile>();

            for (int l = 0; l < lines.Length; l++)
            {
                string line = lines[l];
                if (line.Contains("Tile"))
                {
                    int i = Array.IndexOf(lines, line);
                    int j = FindNextEmpty(i, lines);
                    char[,] grid = new char[lines[l + 1].Length, j - i - 1];
                    for (int q = 0; q < grid.GetLength(0); q++)
                    {
                        for (int w = 0; w < grid.GetLength(1); w++)
                        {
                            grid[w, q] = lines[i + q + 1][w];
                        }
                    }
                    string    nn   = line.Substring(5, line.Length - 6);
                    int       id   = int.Parse(nn);
                    ImageTile tile = new ImageTile(grid, id);
                    tileList.Add(tile);
                }
            }
            List <Tuple <ImageTile, int> > possibilities = FindPossibleCorners(tileList);
            long a = possibilities[0].Item1.id;
            long b = possibilities[1].Item1.id;
            long c = possibilities[2].Item1.id;
            long d = possibilities[3].Item1.id;

            return(a * b * c * d);
        }
        private static bool TryToFill(ImageTile[,] fullImage, Vector2Int pos, List <ImageTile> list)
        {
            int size = fullImage.GetLength(0);
            List <Tuple <ImageTile, Orrientation> > allowed = new List <Tuple <ImageTile, Orrientation> >();

            foreach (ImageTile img in list)
            {
                img.rotation = ImageTile.Rotation.NORTH;
                img.flipped  = false;
                foreach (bool fl in flips)
                {
                    foreach (ImageTile.Rotation rot in rots)
                    {
                        bool doesFit = true;
                        img.rotation = rot;
                        img.flipped  = fl;
                        Orrientation o = new Orrientation {
                            rot = img.rotation,
                            fl  = img.flipped
                        };
                        foreach (Vector2Int offset in dirs)
                        {
                            if (pos.x + offset.x < 0 || pos.y + offset.y < 0)
                            {
                                continue;
                            }
                            if (pos.x + offset.x >= size || pos.y + offset.y >= size)
                            {
                                continue;
                            }
                            if (fullImage[pos.x + offset.x, pos.y + offset.y] == null)
                            {
                                continue;
                            }
                            ImageTile e = fullImage[pos.x + offset.x, pos.y + offset.y];
                            doesFit &= CheckPlacementWithOrrientation(e, img, offset);
                        }
                        if (doesFit)
                        {
                            allowed.Add(new Tuple <ImageTile, Orrientation>(img, o));
                        }
                    }
                }
            }
            if (allowed.Count == 1)
            {
                Tuple <ImageTile, Orrientation> tt = allowed[0];
                tt.Item1.rotation       = tt.Item2.rot;
                tt.Item1.flipped        = tt.Item2.fl;
                fullImage[pos.x, pos.y] = tt.Item1;
                return(true);
            }
            return(false);
        }
 private static bool CheckPlacementWithOrrientation(ImageTile existing, ImageTile newTile, Vector2Int dir)
 {
     char[] edge1 = newTile.GetEdge(dir.x, dir.y, newTile.rotation, newTile.flipped);
     char[] edge2 = existing.GetEdge(-dir.x, -dir.y, existing.rotation, existing.flipped);
     for (int i = 0; i < edge1.Length; i++)
     {
         if (edge1[i] != edge2[i])
         {
             return(false);
         }
     }
     return(true);
 }
 private static bool CheckPlacement(ImageTile existing, ImageTile newTile, Vector2Int dir)
 {
     foreach (ImageTile.Rotation rot in rots)
     {
         foreach (Vector2Int dir2 in dirs)
         {
             foreach (bool fl in flips)
             {
                 char[] edge1 = newTile.GetEdge(dir2.x, dir2.y, rot, fl);
                 if (existing.MatchEdge(edge1, dir.x, dir.y))
                 {
                     return(true);
                 }
             }
         }
     }
     return(false);
 }
 private static ImageTile GetSingleOption(ImageTile start, List <ImageTile> list)
 {
     foreach (Vector2Int dir in dirs)
     {
         List <ImageTile> neighbors = new List <ImageTile>();
         foreach (ImageTile t2 in list)
         {
             if (start == t2)
             {
                 continue;
             }
             if (CheckPlacement(start, t2, dir))
             {
                 neighbors.Add(t2);
             }
         }
         if (neighbors.Count == 1)
         {
             return(neighbors[0]);
         }
     }
     return(null);
 }
        internal static long Part2(string input)
        {
            string[]         lines    = input.Split('\n');
            List <ImageTile> tileList = new List <ImageTile>();

            for (int l = 0; l < lines.Length; l++)
            {
                string line = lines[l];
                if (line.Contains("Tile"))
                {
                    int i = Array.IndexOf(lines, line);
                    int j = FindNextEmpty(i, lines);
                    char[,] grid = new char[lines[l + 1].Length, j - i - 1];
                    for (int q = 0; q < grid.GetLength(0); q++)
                    {
                        for (int w = 0; w < grid.GetLength(1); w++)
                        {
                            grid[w, q] = lines[i + q + 1][w];
                        }
                    }
                    string    nn   = line.Substring(5, line.Length - 6);
                    int       id   = int.Parse(nn);
                    ImageTile tile = new ImageTile(grid, id);
                    tileList.Add(tile);
                }
            }
            ImageTile starting = null;
            List <Tuple <ImageTile, int> > possibilities = FindPossibleCorners(tileList);

            starting = possibilities[0].Item1;

            int size = (int)Math.Sqrt(tileList.Count);

            ImageTile[,] fullImage = new ImageTile[size, size];

            List <Vector2Int> possibleDirs = new List <Vector2Int>();
            Vector2Int        cell         = new Vector2Int(0, 0);

            foreach (Vector2Int dir in dirs)
            {
                Vector2Int off = new Vector2Int(cell.x + dir.x, cell.y + dir.y);
                foreach (ImageTile t2 in tileList)
                {
                    if (t2 == starting)
                    {
                        continue;
                    }
                    if (CheckPlacement(starting, t2, new Vector2Int(-dir.x, -dir.y)))
                    {
                        possibleDirs.Add(dir);
                    }
                }
            }
            int        ax       = possibleDirs[0].x + possibleDirs[1].x;
            int        ay       = possibleDirs[0].y + possibleDirs[1].y;
            Vector2Int startPos = new Vector2Int((ax == 1 ? size - 1 : 0), (ay == 1 ? size - 1 : 0));

            fullImage[startPos.x, startPos.y] = starting;
            tileList.Remove(starting);
            while (tileList.Count > 0)
            {
                List <Tuple <Vector2Int, int> > expansions = FindOpenNeighbors(fullImage);
                bool changed = false;
                do
                {
                    Tuple <Vector2Int, int> npos = expansions.OrderByDescending(a => a.Item2 * 1000 - a.Item1.y - a.Item1.x).First();
                    expansions.Remove(npos);
                    if (TryToFill(fullImage, npos.Item1, tileList))
                    {
                        changed = true;
                        tileList.Remove(fullImage[npos.Item1.x, npos.Item1.y]);
                        break;
                    }
                } while(expansions.Count > 0);
                if (!changed)
                {
                    return(-1);
                }
            }
            // make the pattern look nice in code
            string pattern = @"
                  # 
#    ##    ##    ###
 #  #  #  #  #  #   ";

            //strip off the \r\n at the front...
            pattern = pattern.Substring(2, pattern.Length - 2);
            int s = fullImage[0, 0].grid.GetLength(0);

            char[,] renderedImage = new char[fullImage.GetLength(0) * (s - 2), fullImage.GetLength(1) * (s - 2)];
            for (int y = renderedImage.GetLength(0) - 1; y >= 0; y--)
            {
                for (int x = 0; x < renderedImage.GetLength(0); x++)
                {
                    renderedImage[x, y] = GetCharFromFull(fullImage, x, y, s - 2);
                }
            }
            string[] patterns = GeneratePatterns(pattern);
            int      answer   = SearchForSeaMonster(patterns, renderedImage);

            return(answer);
        }