-- MANDELBROT SET GENERATOR
-- DAVID HANLEY 11/03/1992
-- BUILDS A MAND-TREE, which contains, at its leaves, a colour.
-- Tree is then traversed and points along with their colour are output

module Main where

import Control.Monad
import System.Environment

a `par` b = b
--1.3 a `seq` b = b

-- MandTree - either contains a NS node with two subtrees
--                   contains a EW node with two subtrees
--                   a leaf holding a colour
data MandTree = NS   MandTree MandTree
               |EW   MandTree MandTree
               |Leaf Colour

-- Colour - colour of a point or rectangle
type Colour = Int

-- Point - 2-Tuple of integers, (Int1,Int2),
--         where Int1 - x-coord of point,
--               Int2 - y-coord of point.
type Point = (Int,Int)

-- MandTuple
type MandTuple = (Int,Int,Int,Int,Colour)

-- size: Size of window - Square 400x400
size :: Int
size =  200

-- build_tree - Constructs mandtree
build_tree :: Point -> Point -> MandTree
build_tree p1@(x1,y1)
           p2@(x2,y2)
           =
            if rec_col /= -1 then   -- All points in currnet rectangle are same colour
              Leaf rec_col
            else
              if split == "NS" then
                 btns1 `par` (btns2 `seq` (NS btns1 btns2))
              else
                 btew1 `par` (btew2 `seq` (EW btew1 btew2))
            where
                 rec_col = check_perim p1 p2
                 split   = if (x2-x1) >= (y2-y1) then  -- x-axis longer, NS split
                              "NS"
                           else                        -- y-axis longer, EW split
                              "EW"
                 nsp1    = p1
                 nsp2    = (split_x, y2)
                 nsp3    = (split_x+1, y1)
                 nsp4    = p2
                 ewp1    = p1
                 ewp2    = (x2, split_y)
                 ewp3    = (x1, split_y+1)
                 ewp4    = p2
                 split_x = (x2+x1) `div` 2
                 split_y = (y2+y1) `div` 2
                 btns1   = build_tree nsp1 nsp2
                 btns2   = build_tree nsp3 nsp4
                 btew1   = build_tree ewp1 ewp2
                 btew2   = build_tree ewp3 ewp4


check_perim :: Point -> Point -> Colour
check_perim p1@(x1,y1)
            p2@(x2,y2)
            = if (equalp p1 p2) then  -- single point, just return its colour
                 point_colour p1
              else if corners_diff then  -- check corners of current rectangle aren't same
                 -1
              else
                check_sides
              where
              corners_diff = if col1 == col2 then
                               if col1 == col3 then
                                 if col1 == col4 then
                                   False
                                 else
                                   True
                               else
                                 True
                             else
                               True

              col1         = point_colour p1
              col2         = point_colour (x2,y1)
              col3         = point_colour (x2,y2)
              col4         = point_colour (x1,y2)

              check_sides  = if (check_line (x1+1,y1) right) then
                              if (check_line (x2,y1+1) down) then
                               if (check_line (x2-1,y2) left) then
                                if (check_line (x1,y2-1) up) then
                                  col1
                                else
                                  -1
                               else
                                 -1
                              else
                               -1
                             else
                              -1

                             where
                             check_line pc@(xc,yc) pd@(xd,yd)
                               =
                               if finished then
                                 True
                               else if (point_colour pc) /= col1 then
                                 False
                               else
                                 check_line (xc+xd, yc+yd) pd
                               where
                                    finished  = if (equalp pd right) then
                                                  (xc >= x2)
                                                else if (equalp pd down) then
                                                  (yc <= y2)
                                                else if (equalp pd left) then
                                                  (xc <= x1)
                                                else
                                                  (yc >= y1)


-- Evaluate the color index of a point
-- This is the algoritm described on page 121 of "The Beauty of Fractals"
-- The code is commented with the step numbers from the algorithm.

-- point_colour - Calculates the dwell value of a point.
point_colour :: Point -> Colour
point_colour (x, y)
             = check_radius (np x) (nq y) 0 0.0 0.0 -- step1


-- check_radius - Calculates the escape radius of a point
check_radius :: Float -> Float -> Int -> Float -> Float -> Colour
check_radius p q k x y = if kp == num_cols then
                           0                             -- step 3.ii
                         else
                           if r > (fromIntegral m ) then
                            kp -- step 3.i
                           else
                             check_radius p q (kp) xn yn -- step 3.iii
                                  where xn = new_x x y p -- step 2
                                        yn = new_y x y q -- step 2
                                        r = radius xn yn -- step 3
                                        kp = k + 1       -- step 2


-- M Set Properties.

pmn :: Float -- Min p value.
pmn =  -2.25

pmx :: Float -- Max p value.
pmx =   0.75

qmn :: Float -- Min q value.
qmn = -1.5

qmx :: Float -- Max q value
qmx = 1.5

m :: Int      -- The escape radius, M.
m =  20

--- Misc functions.

equalp :: Point -> Point -> Bool
equalp (x1, y1) (x2, y2) = ((x1 == x2) && (y1 == y2))


-- Set calculation functions.

num_cols :: Int -- The number of colors; num_cols+1 = length (the_colors).
num_cols = 26

delta_p :: Float      -- The change in p per pixel.
delta_p =  (pmx - pmn) / fromIntegral (size - 1)

delta_q :: Float      -- The change in q per pixel.
delta_q =  (qmx - qmn) / fromIntegral (size - 1)

np :: Int -> Float     -- Calculate a new value of p.
np x = pmn + (fromIntegral x) * delta_p

nq :: Int -> Float     -- Calculate a new value of q.
nq y = qmn + (fromIntegral y) * delta_q

radius :: Float -> Float -> Float       -- Current radius of apoint (x,y).
radius x y = x*x + y*y

new_x :: Float -> Float -> Float -> Float       -- iterate new x value.
new_x x y p = x*x - y*y + p

new_y :: Float -> Float -> Float -> Float       -- iterate new y value.
new_y x y q = 2.0 * x * y + q


-- Misc. functions for traversing perimeter of rectangle.

up,down,left,right :: Point
up    = ( 0,-1)
down  = ( 0, 1)
left  = (-1, 0)
right = ( 1, 0)

-- finite: forces evaluation of a tree
finite            :: MandTree -> Bool
finite (Leaf c)   =  (c == c)
finite (NS t1 t2) =  (finite t1 && finite t2)
finite (EW t1 t2) =  (finite t1 && finite t2)

main = do
  [n] <- getArgs
  replicateM_ (read n) $ do
    -- m should always be smaller than size, but the compiler can't know that
    m <- length <$> getArgs
    let size' = max m size
    finite (build_tree (0,0) (size',size' `div` 2)) `seq` return ()

