//Stub stolen from:
//     klines 0.1.1 - Basic screen saver for KDE
//      by Dirk Staneker 1997

#include <kdebug.h>
#include <tdelocale.h>
#include <tdeconfigdialog.h>

#include <tqdesktopwidget.h>
#include <tqpainter.h>
#include <tqbrush.h>

#include "screen.h"
#include "frame.h"
#include "sprite.h"
#include "aasaver.h"

#include "AASaverConfig.h"
#include "settingswidget.h"

#define ARRAY_SIZE(arr) sizeof(arr)/sizeof(arr[0])

AASaver::AASaver( WId id ): KScreenSaver(id)
{
    screen = new Screen(this);
    addEnvironment();
    addCastle();
    addAllSeaweed();
    addAllFish();
    addRandom(screen);

    setBackgroundMode(NoBackground);
    setWFlags(WNoAutoErase);
    update(rect());
}


TQString AASaver::randColor(TQString color_mask)
{
    char colors[] = {'c','C','r','R','y','Y','b','B','g','G','m','M'};
    for (int i = 1; i <= 9; ++i)
    {
        char color = colors[intRand(ARRAY_SIZE(colors))];
        color_mask.replace('0' + i, color);
    }
    return color_mask;
}
    
void AASaver::addCastle()
{
    TQString castle_image = 
            "               T~~\n"
            "               |\n"
            "              /^\\\n"
            "             /   \\\n"
            " _   _   _  /     \\  _   _   _\n"
            "[ ]_[ ]_[ ]/ _   _ \\[ ]_[ ]_[ ]\n"
            "|_=__-_ =_|_[ ]_[ ]_|_=-___-__|\n"
            " | _- =  | =_ = _    |= _=   |\n"
            " |= -[]  |- = _ =    |_-=_[] |\n"
            " | =_    |= - ___    | =_ =  |\n"
            " |=  []- |-  /| |\\   |=_ =[] |\n"
            " |- =_   | =| | | |  |- = -  |\n"
            " |_______|__|_|_|_|__|_______|\n";


    TQString castle_mask = 
        "                RR\n"
        "\n"
        "              yyy\n"
        "             y   y\n"
        "            y     y\n"
        "           y       y\n"
        "\n"
        "\n"
        "\n"
        "              yyy\n"
        "             yy yy\n"
        "            y y y y\n"
        "            yyyyyyy\n";
        
    Frame f(castle_image, castle_mask, 0x686868/* XXX: why grey? */ );

    Sprite* castle = new Sprite(screen,
        screen->width() - 32, screen->height() - 13, 22);
    castle->addFrame(f);
    screen->addSprite(castle);
}

void AASaver::addEnvironment()
{
    TQString water_line_segment[] = {
        "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",
        "^^^^ ^^^  ^^^   ^^^    ^^^^      ",
        "^^^^      ^^^^     ^^^    ^^     ",
        "^^      ^^^^      ^^^    ^^^^^^  "
    };

    // tile the segments so they stretch across the screen
    int segment_size   = water_line_segment[0].length();
    int segment_repeat = int(screen->width()/segment_size) + 1;
    
    for (unsigned i = 0; i < ARRAY_SIZE(water_line_segment); ++i) {
        //do the tiling
        TQString out;
        for (int r = 0; r < segment_repeat; ++r)
            out += water_line_segment[i];
        
        //create a sprite.
        Sprite* s = new Sprite(screen, 0, i + 5, 22);
        s->addFrame(Frame(out, TQString::null, 0x149494));
        screen->addSprite(s);
    }
}

void AASaver::addAllSeaweed()
{
    // figure out how many seaweed to add by the width of the screen
    int seaweed_count = int(screen->width() / 15.0);
    for (int i = 1; i <= seaweed_count; ++i)
        addSeaweed(screen);
}

/**
 * Special class to represent seaweed.  Seaweed can disappear over time, and
 * when this happens, this class will automatically spawn another one.
 */
class Seaweed: public Sprite
{
    int m_ticks; ///< Number of animation ticks elapsed.
    int m_lifeTimeMS; ///< Life time of seaweed in milliseconds.

public:
    /**
     * Constructor.  The \p x, \p y, and \p z coordinates are all in logical
     * coordinates.
     *
     * @param s The Screen to be created in.
     * @param x The x coordinate to place the seaweed at.
     * @param y The y coordinate to place the seaweed at.
     * @param life The length of time in milliseconds the seaweed will live for.
     */
    Seaweed(Screen* s, int x, int y, int life): Sprite(s, x, y, 21),
        m_ticks(0), m_lifeTimeMS(life)
    {
    }

    /**
     * Reimplemented from Sprite::tickUpdate() to handle keeping track of
     * Seaweed lifetime.  Calls the inherited tickUpdate() as well.
     */
    virtual bool tickUpdate()
    {
        ++m_ticks;
        if (m_ticks * m_screen->msPerTick() > m_lifeTimeMS)
        {
            kill();
            AASaver::addSeaweed(m_screen);
        }

        return Sprite::tickUpdate();
    }
};

void AASaver::addSeaweed(Screen* screen)
{
    TQString seaweed_image[] = {"", ""};
    int height = intRand(5) + 3;
    for (int i = 1; i <= height; ++i)
    {
        int left_side  = i % 2;
        int right_side = !left_side;
        seaweed_image[left_side]  += "(\n";
        seaweed_image[right_side] += " )\n";
    }

    int x = intRand(screen->width() - 2) + 1;
    int y = screen->height() - height;

    Seaweed* s = new Seaweed(screen, x, y,
        intRand(4*60000) + (8*60000)); // seaweed lives for 8 to 12 minutes
    s->addFrame(Frame(seaweed_image[0], TQString::null, 0x18AF18));
    s->addFrame(Frame(seaweed_image[1], TQString::null, 0x18AF18));
    s->setFrameDelay(intRand(50) + 250);
    screen->addSprite(s);
}

/**
 * Class to represent an AirBubble.  The bubble will automatically float up,
 * even though it is not descended from MovingSprite.
 */
class AirBubble : public Sprite
{
    const int m_startY; ///< Y coordinate we started at, needed to choose a frame.

public:
    /**
     * Constructor.  The \p x, \p y, and \p z coordinates are all in logical
     * coordinates.
     *
     * @param screen The Screen to be created in.
     * @param x The x coordinate to start at.
     * @param y The y coordinate to start at.
     * @param z The depth to start at.
     */
    AirBubble(Screen *screen, int x, int y, int z) :
        Sprite(screen, x, y, z), m_startY(y)
    {
        addFrame(Frame(".", TQString(), 0x18B2B2));
        addFrame(Frame("o", TQString(), 0x18B2B2));
        addFrame(Frame("O", TQString(), 0x18B2B2));

        setFrameDelay(100);
    }

    /**
     * Reimplemented from Sprite::tickUpdate() to handle moving the sprite and
     * updating the current frame.  The inherited tickUpdate() is not called.
     */
    virtual bool tickUpdate()
    {
        if (!timerTick())
            return false;
            
        erase();

        m_currentFrame = 0;
        if(m_startY - m_y > 5)
            m_currentFrame = 1;
        if(m_startY - m_y > 11)
            m_currentFrame = 2;

        m_y--;
        if(m_y < 9)
            kill();

        return true;
    }
};

/**
 * Moving sprite, will be killed when it moves off of the screen.
 */
class MovingSprite: public Sprite
{
protected:
    int m_direct; ///< Direction to move in, -1 == left, 1 == right.
    double m_speed; ///< Speed to move at (Currently m_speed per tick).
    double m_realX; ///< Used for accuracy, holds fractional x position.
    int m_ticksSinceLastChange; ///< Number of timer ticks since last frame change.
    int m_frameTime;            ///< Amount of time in milliseconds to show each frame.

public:
    /**
     * Constructor.  The \p x, \p y, and \p z coordinates are all in logical
     * coordinates.
     *
     * @param screen The Screen to be created in.
     * @param direct The direction to move the sprite in along the X axis, either
     *               -1 for the left direction, or 1 for the right direction.
     * @param speed The speed to move the sprite in along the X axis, in
     *              character cells per tick.  Use Screen::msPerTick() to find
     *              out how long a tick takes.  The speed can be fractional
     *              (e.g. 1.5 cells per tick).
     * @param x The x coordinate to start at.
     * @param y The y coordinate to start at.
     * @param z The depth to start at.
     */
    MovingSprite(Screen* screen, int direct, double speed, int x, int y, int z):
        Sprite(screen, x, y, z), m_direct(direct), m_speed(speed), m_realX(x),
        m_ticksSinceLastChange(0), m_frameTime(250)
    {
    }

    /**
     * Sets the amount of time a frame is shown.  Use this function for
     * MovingSprites that are also animated.
     *
     * @param milliseconds Amount of time to show a frame for in milliseconds.
     */
    void setFrameTime(int milliseconds)
    {
        m_frameTime = milliseconds;
    }

    /// Returns the amount of time a frame lasts in milliseconds.
    int frameTime() const { return m_frameTime; }

    /// Returns the direction the sprite travels in.
    int direction() const
    {
        return m_direct;
    }

    /// Returns the fractional speed of the sprite.
    double realSpeed() const
    {
        return m_speed;
    }

    /// Returns the real (fractional) X position of the sprite.
    double realX() const
    {
        return m_realX;
    }

    /**
     * Reimplemented from Sprite::tickUpdate() to handle motion and frame
     * animation.  This function will automatically kill() this sprite when
     * it moves off screen.  The inherited tickUpdate() is not called.
     */
    virtual bool tickUpdate()
    {
        if (!timerTick())
            return false;
            
        erase();
        m_realX += (m_direct * m_speed);
        m_x = (int) m_realX;

        ++m_ticksSinceLastChange;
        if(m_ticksSinceLastChange * m_screen->msPerTick() > m_frameTime)
        {
            m_ticksSinceLastChange = 0;

            ++m_currentFrame;
            if(m_currentFrame == m_frames.size())
                m_currentFrame = 0;
        }

        if((m_x + m_frames[m_currentFrame].width() < 0) || (m_x > m_screen->width()))
            kill();

        return true;
    }
};

/**
 * Will spawn a random sprite when killed, otherwise behaves just like
 * MovingSprite.
 */
class RandomMovingSprite : public MovingSprite
{
public:
    RandomMovingSprite(Screen *screen, int direct, double speed, int x, int y, int z):
        MovingSprite(screen, direct, speed, x, y, z)
    {
    }

    /// Spawns another RandomMovingSprite before dying.
    virtual void kill()
    {
        MovingSprite::kill();
        AASaver::addRandom(m_screen);
    }
};

/**
 * Special subclass that represents a fish.  Used so TeethSprite knows when it
 * has caused a collision, and also to handle air bubble generation.
 */
class FishSprite : public MovingSprite
{
    double m_spacesPerBubble;   ///< Amount of spaces a fish moves for each bubble.
    double m_lastBubbleRelease; ///< Amount of space traveled since the last bubble.

public:
    FishSprite(Screen* screen, int direct, double speed, int x, int y, int z):
        MovingSprite(screen, direct, speed, x, y, z), m_lastBubbleRelease(x)
    {
        m_spacesPerBubble = AASaver::doubleRand(screen->width()) + 12.0;
    }

    /// Spawns another fish before dying.
    virtual void kill()
    {
        MovingSprite::kill();
        AASaver::addFish(m_screen);
    }

    /**
     * Reimplemented from MovingSprite::tickUpdate() to handle creating air
     * bubbles.  Inherited tickUpdate() is still called.
     */
    virtual bool tickUpdate()
    {
        if(!MovingSprite::tickUpdate())
            return false;

        if(isKilled())
            return true;

        if(TQABS(realX() - m_lastBubbleRelease) >= m_spacesPerBubble)
        {
            m_lastBubbleRelease = realX();

            int bubbleX = m_x;
            TQRect geometry = geom();

            if(m_direct > 0) // Moving right
                bubbleX += geometry.width();

            AASaver::addBubble(m_screen, bubbleX, m_y + geometry.height() / 2 - 1, m_z - 1);
        }

        return true;
    }
};

void AASaver::addAllFish()
{
    // Determine how many logical pixels we are dealing with, and find out how
    // many we'd be dealing with in full screen, and then scale the user's
    // number down to adjust so that we look about the same in a window as we
    // do fullscreen. TODO: Xinerama issues?
    TQRect fullScreenGeometry = kapp->desktop()->screenGeometry();

    int full_width = fullScreenGeometry.width() / screen->cellWidth();
    int full_height = fullScreenGeometry.height() / screen->cellHeight() - 9;
    int full_size = full_width * full_height;
    int screen_size = (screen->height() - 9) * screen->width();

    int fish_count = AASaverConfig::fishCount() * screen_size / full_size;
    if(fish_count < 5)
        fish_count = 5;

    for (int i = 1; i <= fish_count; ++ i)
        addFish(screen);
}

Sprite *AASaver::newFish(Screen *screen)
{
    TQString fish_image[] = {
"       \\\n"
"     ...\\..,\n"
"\\" "??" "/'       \\\n" // trigraphs suck
" >=     (  ' >\n"
"/??\\      / /\n"
"    `\"'\"'/''\n",

"       2\n"
"     1112111\n"
"6  11       1\n"
" 66     7  4 5\n"
"6  1      3 1\n"
"    11111311\n",

//////////////////////////////
"      /\n"
"  ,../...\n"
" /       '\\" "??" "/\n" // trigraphs suck
"< '  )     =<\n"
" \\ \\      /??\\\n"
"  `'\\'\"'\"'\n",

"      2\n"
"  1112111\n"
" 1       11  6\n"
"5 4  7     66\n"
" 1 3      1  6\n"
"  11311111\n",
//////////////////////////////
"    \\\n"
"\\?/--\\\n"
">=  (o>\n"
"/?\\__/\n"
"    /\n",

"    2\n"
"6 1111\n"
"66  745\n"
"6 1111\n"
"    3\n",

//////////////////////////////
"  /\n"
" /--\\?/\n"
"<o)  =<\n"
" \\__/?\\\n"
"  \\\n",

"  2\n"
" 1111 6\n"
"547  66\n"
" 1111 6\n"
"  3\n",

//////////////////////////////
"       \\:.\n"
"\\;,???,;\\\\\\\\\\,,\n"
"  \\\\\\\\\\;;:::::o\n"
"  ///;;::::::::<\n"
" /;`?``/////``\n",

"       222\n"
"666   1122211\n"
"  6661111111114\n"
"  66611111111115\n"
" 666 113333311\n",

//////////////////////////////
"      .:/\n"
"   ,,///;,???,;/\n"
" o:::::::;;///\n"
">::::::::;;\\\\\\\n"
"  ''\\\\\\\\\\''?';\\\n",

"      222\n"
"   1122211   666\n"
" 4111111111666\n"
"51111111111666\n"
"  113333311 666\n",

//////////////////////////////
"  __\n"
"><_'>\n"
"   '\n",

"  11\n"
"61145\n"
"   3\n",

//////////////////////////////
" __\n"
"<'_><\n"
" `\n",

" 11\n"
"54116\n"
" 3\n",

//////////////////////////////
"   ..\\,\n"
">='   ('>\n"
"  '''/''\n",

"   1121\n"
"661   745\n"
"  111311\n",

//////////////////////////////
"  ,/..\n"
"<')   `=<\n"
" ``\\```\n",

"  1211\n"
"547   166\n"
" 113111\n", 

//////////////////////////////
"   \\\n"
"  / \\\n"
">=_('>\n"
"  \\_/\n"
"   /\n",

"   2\n"
"  1 1\n"
"661745\n"
"  111\n"
"   3\n",

//////////////////////////////
"  /\n"
" / \\\n"
"<')_=<\n"
" \\_/\n"
"  \\\n",

"  2\n"
" 1 1\n"
"547166\n"
" 111\n"
"  3\n",

//////////////////////////////
"  ,\\\n"
">=('>\n"
"  '/\n",

"  12\n"
"66745\n"
"  13\n",

//////////////////////////////
" /,\n"
"<')=<\n"
" \\`\n",

" 21\n"
"54766\n"
" 31\n",

//////////////////////////////
"  __\n"
"\\/ o\\\n"
"/\\__/\n",

"  11\n"
"61 41\n"
"61111\n",

//////////////////////////////
" __\n"
"/o \\/\n"
"\\__/\\\n",

" 11\n"
"14 16\n"
"11116\n"
};

    // # 1: body
    // # 2: dorsal fin
    // # 3: flippers
    // # 4: eye
    // # 5: mouth
    // # 6: tailfin
    // # 7: gills*
    int fish_num   = intRand(ARRAY_SIZE(fish_image)/2);
    int fish_index = fish_num * 2;

    double speed = doubleRand(2) + 0.25;
    int depth = 3 + intRand(18);

    TQString color_mask = fish_image[fish_index+1];
    color_mask.replace('4', 'W');
    
    color_mask = randColor(color_mask);

    Frame fishFrame(fish_image[fish_index], color_mask, 0);
    int max_height = 9;
    int min_height = screen->height() - fishFrame.height();

    int x, y, dir;
    y = max_height + intRand(min_height - max_height);
    if (fish_num % 2)
    {
        x   = screen->width() - 2;
        dir = -1;
    }
    else
    {
        x   = 1 - fishFrame.width();
        dir = 1;
    }

    Sprite* fish = new FishSprite(screen, dir, speed, x, y, depth);
    fish->addFrame(fishFrame);

    return fish;
}

void AASaver::addFish(Screen* screen)
{
    screen->addSprite(newFish(screen));
}

/**
 * Sprite that represents a blood "splat" in the water.
 */
class Splat : public Sprite
{
public:
    /**
     * Constructor.
     *
     * @param screen The Screen to create the splat in.
     * @param center The point to center the splat around.
     * @param depth  The depth to create the splat at.
     */
    Splat(Screen *screen, TQPoint center, int depth) :
        Sprite(screen, 0, 0, depth, 450 /* frame Delay */)
    {
        TQString splats[] = {
"\n"
"   .\n"
"  ***\n"
"   '\n"
""
,

"\n"
" \",*;`\n"
" \"*,**\n"
" *\"'~'\n"
""
,
"  , ,\n"
" \" \",\"'\n"
" *\" *'\"\n"
"  \" ; .\n"
""
,
"* ' , ' `\n"
"' ` * . '\n"
" ' `' \",'\n"
"* ' \" * .\n"
"\" * ', '"
        };

        for(unsigned i = 0; i < ARRAY_SIZE(splats); ++i)
            addFrame(Frame(splats[i], TQString(), 0xB21818, ' '));

        TQRect r(center, TQSize(9, 5));
        r.moveCenter(center);
        m_x = r.x();
        m_y = r.y();

        setDieAfterLastFrame(true);
    }
};

/**
 * Invisible sprite which are created on a shark's teeth, to handle collisions
 * with fish, creating splats and kill()'ing the fish.
 */
class TeethSprite : public MovingSprite
{
public:
    /**
     * Constructor.  Copied parameters as appropriate from \p shark.
     *
     * @param shark The shark to create the teeth over.
     */
    TeethSprite(MovingSprite *shark) : MovingSprite(shark->screen(), shark->direction(),
            shark->realSpeed(), 2 + shark->geom().left(), shark->geom().top(), shark->depth())
    {
        m_y += 7;
        m_z -= 1;
        m_realX = 2 + shark->realX();

        if(m_direct > 0) // Moving to right.
            m_realX = -10;

        addFrame(Frame("????????", TQString(), 0));
    }

    /// Returns true since we can collide.
    bool canCollide() const { return true; }

    /**
     * Reimplemented in order to handle collisions.  When colliding with a
     * FishSprite, the fish is kill()'ed and a splat is created in its place.
     * Otherwise, nothing is done.
     *
     * @param sprite The Sprite we collided with.
     */
    void collision(Sprite *sprite)
    {
        if(dynamic_cast<FishSprite *>(sprite)) {
            kdDebug() << "A fish just got killinated!\n";

            sprite->erase();
            sprite->kill();

            screen()->addSprite(new Splat(screen(), sprite->geom().center(), depth() - 1));
        }
    }
};

void AASaver::addShark(Screen* screen)
{
    TQString shark_image[] = {
"                              __\n"
"                             ( `\\\n"
"  ,??????????????????????????" ")   `\\\n" // trigraphs suck
";' `.????????????????????????" "(     `\\__\n" // trigraphs suck
" ;   `.?????????????__..---''          `~~~~-._\n"
"  `.   `.____...--''                       (b  `--._\n"
"    >                     _.-'      .((      ._     )\n"
"  .`.-`--...__         .-'     -.___.....-(|/|/|/|/'\n"
" ;.'?????????`. ...----`.___.',,,_______......---'\n"
" '???????????" "'-'\n", // trigraphs suck

"                                                     \n"
"                                                     \n"
"                                                     \n"
"                                                     \n"
"                                                     \n"
"                                           cR        \n"
"                                                     \n"
"                                          cWWWWWWWW  \n"
"                                                     \n"
"                                                     \n",

"                     __\n"
"                    /' )\n"
"                  /'   (??????????????????????????,\n"
"              __/'     )????????????????????????.' `;\n"
"      _.-~~~~'          ``---..__?????????????.'   ;\n"
" _.--'  b)                       ``--...____.'   .'\n"
"(     _.      )).      `-._                     <\n"
" `\\|\\|\\|\\|)-.....___.-     `-.         __...--'-.'.\n"
"   `---......_______,,,`.___.'----... .'?????????`.;\n"
"                                     `-`???????????`\n",

"                                                     \n"
"                                                     \n"
"                                                     \n"
"                                                     \n"
"                                                     \n"
"        Rc                                           \n"
"                                                     \n"
"  WWWWWWWWc                                          \n"
"                                                     \n"
"                                                     \n"
    };

    int shark_num   = intRand(ARRAY_SIZE(shark_image)/2);
    int shark_index = shark_num * 2;
    TQString color_mask = randColor(shark_image[shark_index+1]);
    Frame sharkFrame(shark_image[shark_index], color_mask, 0x18B2B2);

    int x = -53;
    int y = 9 + intRand(screen->height() - (10 + 9));
    int dir = (shark_num % 2) ? -1 : 1;

    if(dir < 0)
        x = screen->width() - 2;

    RandomMovingSprite* shark = new RandomMovingSprite(screen, dir, 2, x, y, 2 /* Always at 2 */);
    shark->addFrame(sharkFrame);
    screen->addSprite(shark);

    TeethSprite *teeth = new TeethSprite(shark);
    screen->addSprite(teeth);
}

void AASaver::addBubble(Screen *screen, int x, int y, int z)
{
    screen->addSprite(new AirBubble(screen, x, y, z));
}

void AASaver::addShip(Screen* screen)
{
    TQString ship_image[] = {
"     |    |    |\n"
"    )_)  )_)  )_)\n"
"   )___))___))___)\\\n"
"  )____)____)_____)\\\\\n"
"_____|____|____|____\\\\\\__\n"
"\\                   /",

"     y    y    y\n"
"                 \n"
"                  w\n"
"                   ww\n"
"yyyyyyyyyyyyyyyyyyyywwwyy\n"
"y                   y",

"         |    |    |\n" 
"        (_(  (_(  (_(\n" 
"      /(___((___((___(\n"
"    //(_____(____(____(\n"
"__///____|____|____|_____\n"
"    \\                   /",

"         y    y    y\n"
"                 \n"
"      w            \n"
"    ww               \n"
"yywwwyyyyyyyyyyyyyyyyyyyy\n"
"    y                   y"
    };

    int ship_num = intRand(17) % 2; // right == 0, left == 1
    int x = -24, dir = 1;

    if(ship_num == 1) {
        x = screen->width() - 2;
        dir = -1;
    }

    RandomMovingSprite *ship = new RandomMovingSprite(screen, dir, 1.0, x, 0, 2);
    ship->addFrame(Frame(ship_image[2 * ship_num], ship_image[2 * ship_num + 1], 0xFFFFFF));
    screen->addSprite(ship);
}

void AASaver::addWhale(Screen* screen)
{
    TQString whale_image[] = {
"        .-----:\n"
"      .'       `.\n"
",????/       (o) \\\n"
"\\`._/          ,__)",

"             C C\n"
"           CCCCCCC\n"
"           C  C  C\n"
"        BBBBBBB\n"
"      BB       BB\n"
"B    B       BWB B\n"
"BBBBB          BBBB",

"    :-----.\n"
"  .'       `.\n"
" / (o)       \\????,\n"
"(__,          \\_.'/",

"   C C\n"
" CCCCCCC\n"
" C  C  C\n"
"    BBBBBBB\n"
"  BB       BB\n"
" B BWB       B    B\n"
"BBBB          BBBBB"
    };

    TQString spouty[] = {
"\n"
"\n"
"   :",

"\n"
"   :\n"
"   :",

"  . .\n"
"  -:-\n"
"   :",

"  . .\n"
" .-:-.\n"
"   :",

"  . .\n"
"'.-:-.`\n"
"'  :  '",

"\n"
" .- -.\n"
";  :  ;",

"\n"
"\n"
";     ;"
    };

    int whale_num = intRand(2); // 0 = right, 1 = left
    int x = -18, spout_align = 11, dir = 1;

    if (whale_num == 1)
    {
        x = screen->width() - 2;
        spout_align = 1; // Waterspout closer to left side now.
        dir = -1;
    }

    TQString mask = whale_image[2 * whale_num + 1];

    RandomMovingSprite *whale = new RandomMovingSprite(screen, dir, 1.0, x, 0, 2);
    whale->setFrameDelay(80);
    whale->setFrameTime(40);

    // We have to add some frames now.  The first five will have no water spout.
    TQString blankWhaleFrame = TQString("\n\n\n") + whale_image[2 * whale_num];

    for(unsigned i = 0; i < 5; ++i)
        whale->addFrame(Frame(blankWhaleFrame, mask, 0xFFFFFF));

    // Now add frames for the animated water spout.
    TQString whaleFrame = whale_image[2 * whale_num];
    for (unsigned i = 0; i < ARRAY_SIZE(spouty); ++i)
    {
        TQStringList spoutLines = TQStringList::split("\n", spouty[i], true);
        TQString spout;
        TQString padding;

        padding.fill(' ', spout_align);

        // Move spout over an appropriate distance to line up right.
        for(TQStringList::ConstIterator it = spoutLines.begin(); it != spoutLines.end(); ++it)
        {
            spout += padding;
            spout += *it;
            spout += "\n";
        }

        // Add spout to whale frame.
        whale->addFrame(Frame(spout + whaleFrame, mask, 0xFFFFFF));
    }

    screen->addSprite(whale);
}

void AASaver::addBigFish(Screen* screen)
{
    TQString big_fish_image[] = {
" ______\n"
"`\"\"-.  `````-----.....__\n"
"     `.  .      .       `-.\n"
"       :     .     .       `.\n"
" ,?????:   .    .          _ :\n"
": `.???:                  (@) `._\n"
" `. `..'     .     =`-.       .__)\n"
"   ;     .        =  ~  :     .-\"\n"
" .' .'`.   .    .  =.-'  `._ .'\n"
": .'???:               .   .'\n"
" '???.'  .    .     .   .-'\n"
"   .'____....----''.'=.'\n"
"   \"\"?????????????.'.'\n"
"               ''\"'`",

" 111111\n"
"11111  11111111111111111\n"
"     11  2      2       111\n"
"       1     2     2       11\n"
" 1     1   2    2          1 1\n"
"1 11   1                  1W1 111\n"
" 11 1111     2     1111       1111\n"
"   1     2        1  1  1     111\n"
" 11 1111   2    2  1111  111 11\n"
"1 11   1               2   11\n"
" 1   11  2    2     2   111\n"
"   111111111111111111111\n"
"   11             1111\n"
"               11111",

"                           ______\n"
"          __.....-----'''''  .-\"\"'\n"
"       .-'       .      .  .'\n"
"     .'       .     .     :\n"
"    : _          .    .   :?????,\n"
" _.' (@)                  :???.' :\n"
"(__.       .-'=     .     `..' .'\n"
" \"-.     :  ~  =        .     ;\n"
"   `. _.'  `-.=  .    .   .'`. `.\n"
"     `.   .               :???`. :\n"
"       `-.   .     .    .  `.???`\n"
"          `.=`.``----....____`.\n"
"            `.`.?????????????\"\"\n"
"              '`\"``",

"                           111111\n"
"          11111111111111111  11111\n"
"       111       2      2  11\n"
"     11       2     2     1\n"
"    1 1          2    2   1     1\n"
" 111 1W1                  1   11 1\n"
"1111       1111     2     1111 11\n"
" 111     1  1  1        2     1\n"
"   11 111  1111  2    2   1111 11\n"
"     11   2               1   11 1\n"
"       111   2     2    2  11   1\n"
"          111111111111111111111\n"
"            1111             11\n"
"              11111"
    };

    int big_fish_num = intRand(2); // right = 0, left = 1

    int maxHeight = 9, minHeight = screen->height() - 15;
    int y = intRand(minHeight - maxHeight) + maxHeight;
    int x = -34, dir = 1;

    if(big_fish_num == 1)
    {
        x = screen->width() - 1;
        dir = -1;
    }

    TQString colors = randColor(big_fish_image[2 * big_fish_num + 1]);
    RandomMovingSprite *bigFish = new RandomMovingSprite(screen, dir, 3.0, x, y, 2);
    bigFish->addFrame(Frame(big_fish_image[2 * big_fish_num], colors, 0xFFFF54));

    screen->addSprite(bigFish);
}

void AASaver::addNessie(Screen* screen)
{
    TQString nessie_image[] = {
"                                                          ____\n"
"            __??????????????????????????????????????????/   o  \\\n"
"          /    \\????????_?????????????????????_???????/     ____ >\n"
"  _??????|  __  |?????/   \\????????_????????/   \\????|     |\n"
" | \\?????|  ||  |????|     |?????/   \\?????|     |???|     |",

"                                                          ____\n"
"                                             __?????????/   o  \\\n"
"             _?????????????????????_???????/    \\?????/     ____ >\n"
"   _???????/   \\????????_????????/   \\????|  __  |???|     |\n"
"  | \\?????|     |?????/   \\?????|     |???|  ||  |???|     |\n",

"                                                          ____\n"
"                                  __????????????????????/   o  \\\n"
" _??????????????????????_???????/    \\????????_???????/     ____ >\n"
"| \\??????????_????????/   \\????|  __  |?????/   \\????|     |\n"
" \\ \\???????/   \\?????|     |???|  ||  |????|     |???|     |",

"                                                          ____\n"
"                       __???????????????????????????????/   o  \\\n"
"  _??????????_???????/    \\????????_??????????????????/     ____ >\n"
" | \\???????/   \\????|  __  |?????/   \\????????_??????|     |\n"
"  \\ \\?????|     |???|  ||  |????|     |?????/   \\????|     |",

"    ____\n"
"  /  o   \\??????????????????????????????????????????__\n"
"< ____     \\???????_?????????????????????_????????/    \\\n"
"      |     |????/   \\????????_????????/   \\?????|  __  |??????_\n"
"      |     |???|     |?????/   \\?????|     |????|  ||  |?????/ |",

"    ____\n"
"  /  o   \\?????????__\n"
"< ____     \\?????/    \\???????_?????????????????????_\n"
"      |     |???|  __  |????/   \\????????_????????/   \\???????_\n"
"      |     |???|  ||  |???|     |?????/   \\?????|     |?????/ |",

"    ____\n"
"  /  o   \\????????????????????__\n"
"< ____     \\???????_????????/    \\???????_??????????????????????_\n"
"      |     |????/   \\?????|  __  |????/   \\????????_??????????/ |\n"
"      |     |???|     |????|  ||  |???|     |?????/   \\???????/ /",

"    ____\n"
"  /  o   \\???????????????????????????????__\n"
"< ____     \\??????????????????_????????/    \\???????_??????????_\n"
"      |     |??????_????????/   \\?????|  __  |????/   \\???????/ |\n"
"      |     |????/   \\?????|     |????|  ||  |???|     |?????/ /"
    };

    TQString nessie_mask[] = {
"\n"
"                                                            W\n"
"\n"
"\n"
"\n"
"",

"\n"
"     W\n"
"\n"
"\n"
"\n"
""
    };

    int nessie_num = intRand(2); // 0 = right, 1 = left.
    int x = -64, dir = 1;

    if(nessie_num == 1) {
        x = screen->width() - 2;
        dir = -1;
    }

    RandomMovingSprite *nessie = new RandomMovingSprite(screen, dir, 1.4, x, 2, 2);
    nessie->setFrameDelay(75);
    nessie->setFrameTime(400);

    for(unsigned i = 0; i < 4; ++i)
        nessie->addFrame(Frame(nessie_image[nessie_num * 4 + i], nessie_mask[nessie_num], 0x18B218));

    screen->addSprite(nessie);
}

void AASaver::addRandom(Screen* screen)
{
    const char *const cute_messages[] = {
        "Quick, someone cue the ominous music!",
        "Her continuing mission... to explore strange new seas...",
        "I caught one that big once, but it got away. :(",
        "Nessie, an Earthbound hero's best friend...",
        "Thar be WHALES, Cap'n!!"
    };
    int choice = intRand(5);

    if(intRand(45) < 7 && choice < ARRAY_SIZE(cute_messages))
        kdDebug() << cute_messages[choice] << endl;

    switch(choice)
    {
        case 0:
            addShark(screen);
            break;
        case 1:
            addShip(screen);
            break;
        case 2:
            addBigFish(screen);
            break;
        case 3:
            addNessie(screen);
            break;
        case 4:
            addWhale(screen);
            break;
    }
}

void AASaver::paintEvent(TQPaintEvent* pe)
{
    screen->paint(pe->region());
}

// libkscreensaver interface
extern "C"
{
    TDE_EXPORT const char *kss_applicationName = "tdeasciiquarium.kss";
    TDE_EXPORT const char *kss_description = I18N_NOOP( "TDEAsciiquarium" );
    TDE_EXPORT const char *kss_version = "0.3.2";

    TDE_EXPORT KScreenSaver *kss_create( WId id )
    {
        return new AASaver( id );
    }

    TDE_EXPORT TQDialog *kss_setup()
    {
        TDEConfigDialog *dialog = TDEConfigDialog::exists("settings");
        if(dialog)
            return dialog;

        dialog = new TDEConfigDialog(0, "settings", AASaverConfig::self());
        SettingsWidget *settings = new SettingsWidget(0, "settings_widget");

        dialog->addPage(settings, i18n("TDEAsciiquarium Settings"), "tdescreensaver");

        return dialog;
    }
}
