// Burn a hole straight to hell!

#include <stdlib.h>
#include <time.h>

#include <iostream>
#include <vector>
#include <map>
#include <stddef.h>
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
using namespace std;

#include <DFHack.h>
#include <dfhack/DFTileTypes.h>
#include <dfhack/modules/Gui.h>
using namespace DFHack;


#ifdef LINUX_BUILD
#include <unistd.h>
void waitmsec (int delay)
{
    usleep(delay);
}
#else
#include <windows.h>
void waitmsec (int delay)
{
    Sleep(delay);
}
#endif

#define minmax(MinV,V,MaxV) (max((MinV),min((MaxV),(V))))

//User interaction enums.
//Pit Type (these only have meaning within hellhole, btw)
#define PITTYPEMACRO \
    X(pitTypeChasm,"Bottomless Chasm" ) \
    X(pitTypeEerie,"Bottomless Eerie Pit" ) \
    X(pitTypeFloor,"Pit with floor" ) \
    X(pitTypeSolid,"Solid Pillar" ) \
    X(pitTypeOasis,"Oasis Pit (ends at magma, no hell access)" ) \
    X(pitTypeOPool,"Oasis Pool, with partial aquifer (default 5 z-levels)" ) \
    X(pitTypeMagma,"Magma Pit (similar to volcano, no hell access)" ) \
    X(pitTypeMPool,"Magma Pool (default 5 z-levels)" )
//end PITTYPEMACRO

#define X(name,desc) name,
enum e_pitType
{
    pitTypeInvalid=-1,
    PITTYPEMACRO
    pitTypeCount,
};
#undef X


#define X(name,desc) desc,
const char * pitTypeDesc[pitTypeCount+1] =
{
    PITTYPEMACRO
    ""
};
#undef X




int getyesno( const char * msg , int default_value )
{
    const int bufferlen=4;
    static char buf[bufferlen];
    memset(buf,0,bufferlen);
    while (-1)
    {
        if (msg) printf("\n%s (default=%s)\n:" , msg , (default_value?"yes":"no") );
        fflush(stdin);
        fgets(buf,bufferlen,stdin);
        switch (buf[0])
        {
            case 0:
            case 0x0d:
            case 0x0a:
                return default_value;
            case 'y':
            case 'Y':
            case 'T':
            case 't':
            case '1':
                return -1;
            case 'n':
            case 'N':
            case 'F':
            case 'f':
            case '0':
                return 0;
        }
    }
    return 0;
}

int getint( const char * msg , int min, int max, int default_value ) {
    const int bufferlen=16;
    static char buf[bufferlen];
    int n=0;
    memset(buf,0,bufferlen);
    while (-1)
    {
        if (msg) printf("\n%s (default=%d)\n:" , msg , default_value);
        fflush(stdin);
        fgets(buf,bufferlen,stdin);
        if ( !buf[0] || 0x0a==buf[0] || 0x0d==buf[0] )
        {
            return default_value;
        }
        if ( sscanf(buf,"%d", &n) )
        {
            if (n>=min && n<=max )
            {
                return n;
            }
        }
    }
}

int getint( const char * msg , int min, int max )
{
    const int bufferlen=16;
    static char buf[bufferlen];
    int n=0;
    memset(buf,0,bufferlen);
    while (-1)
    {
        if (msg)
        {
            printf("\n%s \n:" , msg );
        }
        fflush(stdin);
        fgets(buf,bufferlen,stdin);

        if ( !buf[0] || 0x0a==buf[0] || 0x0d==buf[0] )
        {
            continue;
        }
        if ( sscanf(buf,"%d", &n) )
        {
            if (n>=min && n<=max )
            {
                return n;
            }
        }
    }
}



//Interactive, get pit type from user
e_pitType selectPitType()
{
    while ( -1 )
    {
        printf("Enter the type of hole to dig:\n" );
        for (int n=0;n<pitTypeCount;++n)
        {
            printf("%2d) %s\n", n, pitTypeDesc[n] );
        }
        printf(":");
        return (e_pitType)getint(NULL, 0, pitTypeCount-1 );
    }
}


void drawcircle(const int radius, unsigned char pattern[16][16], unsigned char v )
{
    //Small circles get better randomness if handled manually
    if ( 1==radius )
    {
        pattern[7][7]=v;
        if ( (rand()&1) ) pattern[6][7]=v;
        if ( (rand()&1) ) pattern[8][7]=v;
        if ( (rand()&1) ) pattern[7][6]=v;
        if ( (rand()&1) ) pattern[7][8]=v;

    }
    else if ( 2==radius )
    {
        pattern[7][7]=v;
        pattern[7][5]=v;
        pattern[7][6]=v;
        pattern[7][8]=v;
        pattern[7][9]=v;
        pattern[6][7]=v;
        pattern[8][7]=v;
        pattern[9][7]=v;
        pattern[6][6]=v;
        pattern[6][8]=v;
        pattern[8][6]=v;
        pattern[8][8]=v;
        pattern[5][7]=v;

        if ( (rand()&1) ) pattern[6][5]=v;
        if ( (rand()&1) ) pattern[5][6]=v;
        if ( (rand()&1) ) pattern[8][5]=v;
        if ( (rand()&1) ) pattern[9][6]=v;
        if ( (rand()&1) ) pattern[6][9]=v;
        if ( (rand()&1) ) pattern[5][8]=v;
        if ( (rand()&1) ) pattern[8][9]=v;
        if ( (rand()&1) ) pattern[9][8]=v;
    }
    else
    {
        //radius 3 or larger, simple circle calculation.
        int x,y;
        for (y=0-radius; y<=radius; ++y)
        {
            for (x=0-radius; x<=radius; ++x)
            {
                if (x*x+y*y <= radius*radius + (rand()&31-8) )
                {
                    pattern[ minmax(0,7+x,15) ][ minmax(0,7+y,15) ]=v;
                }
            }
        }
        //Prevent boxy patterns with a quick modification on edges
        if (rand()&1) pattern[ 7 ][ minmax(0,7+radius+1,15) ] = v;
        if (rand()&1) pattern[ 7 ][ minmax(0,7-radius-1,15) ] = v;
        if (rand()&1) pattern[ minmax(0,7+radius+1,15) ][ 7 ] = v;
        if (rand()&1) pattern[ minmax(0,7-radius-1,15) ][ 7 ] = v;
    }
}

//Check all neighbors for a given value n.
//If found, and v>=0, replace with v.
//Returns number of neighbors found.
int checkneighbors(unsigned char pattern[16][16], int x, int y, unsigned char n , char v )
{
    int r=0;
    if ( x>0  && y>0  && n==pattern[x-1][y-1] )
    {
        ++r;
        if (v>-1) pattern[x][y]=v;
    }
    if ( x>0          && n==pattern[x-1][y  ] )
    {
        ++r;
        if (v>-1) pattern[x][y]=v;
    }
    if (         y>0  && n==pattern[x  ][y-1] )
    {
        ++r;
        if (v>-1) pattern[x][y]=v;
    }
    if ( x<15         && n==pattern[x+1][y  ] )
    {
        ++r;
        if (v>-1) pattern[x][y]=v;
    }
    if ( x<15 && y>0  && n==pattern[x+1][y-1] )
    {
        ++r;
        if (v>-1) pattern[x][y]=v;
    }
    if ( x<15 && y<15 && n==pattern[x+1][y+1] )
    {
        ++r;
        if (v>-1) pattern[x][y]=v;
    }
    if (         y<15 && n==pattern[x  ][y+1] )
    {
        ++r;
        if (v>-1) pattern[x][y]=v;
    }
    if ( x>0  && y<15 && n==pattern[x-1][y+1] )
    {
        ++r;
        if (v>-1) pattern[x][y]=v;
    }
    return r;
}
//convenience
int checkneighbors(unsigned char pattern[16][16], int x, int y, unsigned char n )
{
    return checkneighbors(pattern,x,y,n,-1);
}

void settileat(unsigned char pattern[16][16], const unsigned char needle, const unsigned char v, const int index )
{
    int ok=0;
    int safety=256*256;
    int y,x,i=0;
    //Scan for sequential index
    while ( !ok && --safety )
    {
        for (y=0 ; !ok && y<16 ; ++y )
        {
            for (x=0 ; !ok && x<16 ; ++x )
            {
                if ( needle==pattern[x][y] )
                {
                    ++i;
                    if ( index==i )
                    {
                        //Got it!
                        pattern[x][y]=v;
                        ok=-1;
                    }
                }
            }
        }
    }
}


//FIXME: good candidate for adding to dfhack. Maybe the Maps should have those cached so they can be queried?
//Is a given feature present at the given tile?
int isfeature(
    vector<DFHack::t_feature> global_features,
    std::map <DFHack::DFCoord, std::vector<DFHack::t_feature *> > local_features,
    const mapblock40d &block, const DFCoord &pc, const int x, const int y, const e_feature Feat
)
{
    //const TileRow * tp;
    //tp = getTileTypeP(block.tiletypes[x][y]);
    const t_designation * d;
    d = &block.designation[x][y];

    if ( block.local_feature > -1 && d->bits.feature_local ) {
        if ( Feat==local_features[pc][block.local_feature]->type ) return Feat;
    }
    if ( block.global_feature > -1 && d->bits.feature_global ) {
        if ( Feat==global_features[block.global_feature].type ) return Feat;
    }

    return 0;
}

// FIXME: use block cache, break into manageable bits
int main (void)
{
    srand ( (unsigned int)time(NULL) );

    //Message of intent
    cout <<
         "DF Hole" << endl <<
         "This tool will instantly dig a chasm, pit, pipe, etc through hell, wherever your cursor is." << endl <<
         "This can not be undone!  End program now if you don't want hellish fun." << endl
         ;

    //User selection of settings should have it own routine, a structure for settings, I know
    //sloppy mess, but this is just a demo utility.

    //Pit Types.
    e_pitType pittype = selectPitType();

    //Here are all the settings.
    //Default values are set here.
    int pitdepth=0;
    int roof=-1;
    int holeradius=6;
    int wallthickness=1;
    int wallpillar=1;
    int holepillar=1;
    int exposehell = 0;
    int fillmagma=0;
    int fillwater=0;
    int stopatmagma=0;
    int exposemagma=0;
    int aquify=0;

    //The Tile Type to use for the walls lining the hole
    //263 is semi-molten rock, 331 is obsidian
    uint32_t whell=263, wmolten=263, wmagma=331, wcave=331;
    //The Tile Type to use for the hole's floor at bottom of the map
    //35 is chasm, 42 is eerie pit , 340 is obsidian floor, 344 is featstone floor, 264 is 'magma flow' floor
    uint32_t floor=35, cap=340;
    int floorvar=0;


    //Modify default settings based on pit type.
    switch ( pittype )
    {
        case pitTypeChasm:
            floor=35;
            break;
        case pitTypeEerie:
            floor=42;
            break;
        case pitTypeFloor:
            floor=344;
            floorvar=3;
            break;
        case pitTypeSolid:
            holeradius=0;
            wallthickness=7;
            wallpillar=4;
            break;
        case pitTypeOasis:
            stopatmagma=-1;
            fillwater=-1;
            holeradius=5;
            wallthickness=2;
            //aquify=-1;
            floor=340;
            floorvar=3;
            break;
        case pitTypeOPool:
            pitdepth=5;
            fillwater=-1;
            holeradius=5;
            wallthickness=2;
            //aquify=-1;
            floor=340;
            floorvar=3;
            break;
        case pitTypeMagma:
            stopatmagma=-1;
            exposemagma=-1;
            wallthickness=2;
            fillmagma=-1;
            floor=264;
            break;
        case pitTypeMPool:
            pitdepth=5;
            wallthickness=2;
            fillmagma=-1;
            floor=340;
            floorvar=3;
            break;
    }


    //Should tiles be revealed?
    int reveal=0;


    int accept = getyesno("Use default settings?",1);

    while ( !accept )
    {
        //Pit Depth
        pitdepth = getint( "Enter max depth (0 for bottom of map)", 0, INT_MAX, pitdepth );

        //Hole Size
        holeradius = getint( "Enter hole radius, 0 to 16", 0, 16, holeradius );

        //Wall thickness
        wallthickness = getint( "Enter wall thickness, 0 to 16", 0, 16, wallthickness );

        //Obsidian Pillars
        holepillar = getint( "Number of Obsidian Pillars in hole, 0 to 255", 0, 255, holepillar );
        wallpillar = getint( "Number of Obsidian Pillars in wall, 0 to 255", 0, 255, wallpillar );

        //Open Hell?
        exposehell=getyesno("Expose the pit to hell (no walls in hell)?",exposehell);

        //Stop when magma sea is hit?
        stopatmagma=getyesno("Stop at magma sea?",stopatmagma);
        exposemagma=getyesno("Expose magma sea (no walls in magma)?",exposemagma);

        //Fill?
        fillmagma=getyesno("Fill with magma?",fillmagma);
        if (fillmagma) aquify=fillwater=0;
        fillwater=getyesno("Fill with water?",fillwater);
        //aquify=getyesno("Aquifer?",aquify);


        ///////////////////////////////////////////////////////////////////////////////////////////////
        //Print settings.
        //If a settings struct existed, this could be in a routine
        printf("Using Settings:\n");
        printf("Pit Type......: %d = %s\n", pittype, pitTypeDesc[pittype]);
        printf("Depth.........: %d\n",  pitdepth);
        printf("Hole Radius...: %d\n",  holeradius);
        printf("Wall Thickness: %d\n",  wallthickness);
        printf("Pillars, Hole.: %d\n",  holepillar);
        printf("Pillars, Wall.: %d\n",  wallpillar);
        printf("Expose Hell...: %c\n", (exposehell?'Y':'N') );
        printf("Stop at Magma.: %c\n", (stopatmagma?'Y':'N') );
        printf("Expose Magma..: %c\n", (exposemagma?'Y':'N') );
        printf("Magma Fill....: %c\n", (fillmagma?'Y':'N') );
        printf("Water Fill....: %c\n", (fillwater?'Y':'N') );
        printf("Aquifer.......: %c\n", (aquify?'Y':'N') );

        accept = getyesno("Accept these settings?",1);
    }


    int64_t n;
    uint32_t x_max,y_max,z_max;


    //Pattern to dig
    unsigned char pattern[16][16];


    for (int regen=1;regen; )
    {
        regen=0;

        memset(pattern,0,sizeof(pattern));

        //Calculate a randomized circle.
        //These values found through experimentation.
        int x=0, y=0, n=0;

        //Two concentric irregular circles
        //Outer circle, solid.
        if ( wallthickness )
        {
            drawcircle(holeradius+wallthickness, pattern, 2);
        }
        //Inner circle, hole.
        if ( holeradius )
        {
            drawcircle(holeradius, pattern, 1);
        }


        //Post-process to be certain the wall totally encloses hole.
        if (wallthickness)
        {
            for (y=0;y<16;++y)
            {
                for (x=0;x<16;++x)
                {
                    if ( 1==pattern[x][y] )
                    {
                        //No hole at edges.
                        if ( x<1 || x>14 || y<1 || y>14 )
                        {
                            pattern[x][y]=2;
                        }
                    }
                    else if ( 0==pattern[x][y] )
                    {
                        //check neighbors
                        checkneighbors( pattern , x,y, 1, 2);
                    }
                }
            }
        }

        //Makes sure that somewhere random gets a vertical pillar of rock which is safe
        //to dig stairs down, to permit access to anywhere within the pit from the top.
        for (n=holepillar; n ; --n)
        {
            settileat( pattern , 1 , 3 , rand()&255 );
        }
        for (n=wallpillar; n ; --n)
        {
            settileat( pattern , 2 , 3 , rand()&255 );
        }

        //Note:
        //At this point, the pattern holds:
        //0 for all tiles which will be ignored.
        //1 for all tiles set to empty pit space.
        //2 for all normal walls.
        //3 for the straight obsidian top-to-bottom wall.
        //4 is randomized between wall or floor (!not implemented!)

        printf("\nPattern:\n");
        const char patternkey[] = ".cW!?567890123";

        //Print the pattern
        for (y=0;y<16;++y)
        {
            for (x=0;x<16;++x)
            {
                cout << patternkey[ pattern[x][y] ];
            }
            cout << endl;
        }
        cout << endl;

        regen = !getyesno("Acceptable Pattern?",1);
    }

    //Post-process settings to fix problems here
    if (pitdepth<1)
    {
        pitdepth=INT_MAX;
    }


    ///////////////////////////////////////////////////////////////////////////////////////////////


    cerr << "Loading memory map..." << endl;

    //Connect to DF!
    DFHack::ContextManager DFMgr("Memory.xml");
    DFHack::Context *DF = DFMgr.getSingleContext();



    //Init
    cerr << "Attaching to DF..." << endl;
    try
    {
        DF->Attach();
    }
    catch (exception& e)
    {
        cerr << e.what() << endl;
        #ifndef LINUX_BUILD
            cin.ignore();
        #endif
        return 1;
    }

    // init the map
    DFHack::Maps *Mapz = DF->getMaps();
    if (!Mapz->Start())
    {
        cerr << "Can't init map.  Exiting." << endl;
        #ifndef LINUX_BUILD
            cin.ignore();
        #endif
        return 1;
    }

    Mapz->getSize(x_max,y_max,z_max);


    //Get cursor
    int32_t cursorX, cursorY, cursorZ;
    DFHack::Gui *Gui = DF->getGui();
    Gui->getCursorCoords(cursorX,cursorY,cursorZ);
    if (-30000==cursorX)
    {
        cout << "No cursor position found.  Exiting." << endl;
        #ifndef LINUX_BUILD
            cin.ignore();
        #endif
        return 1;
    }

    //Block coordinates
    int32_t bx=cursorX/16, by=cursorY/16, bz=cursorZ;
    //Tile coordinates within block
    int32_t tx=cursorX%16, ty=cursorY%16, tz=cursorZ;

    /*
    //Access the DF interface to pause the game.
    //Copied from the reveal tool.
    DFHack::Gui *Gui =DF->getGui();
    cout << "Pausing..." << endl;
    Gui->SetPauseState(true);
    DF->Resume();
    waitmsec(1000);
    DF->Suspend();
    */

    //Verify that every z-level at this location exists.
    for (int32_t Z = 0; Z<= bz ;Z++)
    {
        if ( ! Mapz->isValidBlock(bx,by,Z) )
        {
            cout << "This block does't exist!  Exiting." << endl;
            #ifndef LINUX_BUILD
                cin.ignore();
            #endif
            return 1;
        }
    }

    //Get all the map features.
    vector<DFHack::t_feature> global_features;
    if (!Mapz->ReadGlobalFeatures(global_features))
    {
        cout << "Couldn't load global features! Probably a version problem." << endl;
        #ifndef LINUX_BUILD
            cin.ignore();
        #endif
        return 1;
    }

    std::map <DFHack::DFCoord, std::vector<DFHack::t_feature *> > local_features;
    if (!Mapz->ReadLocalFeatures(local_features))
    {
        cout << "Couldn't load local features! Probably a version problem." << endl;
        #ifndef LINUX_BUILD
            cin.ignore();
        #endif
        return 1;
    }

    //Get info on current tile, to determine how to generate the pit
    mapblock40d topblock;
    Mapz->ReadBlock40d( bx, by, bz , &topblock );
    //Related block info
    DFCoord pc(bx,by);
    mapblock40d block;
    const TileRow * tp;
    t_designation * d;

    //////////////////////////////////////
    //From top to bottom, dig this thing.
    //////////////////////////////////////

    //Top level, cap.
    //Might make this an option in the future
    //For now, no wall means no cap.
    if (wallthickness)
    {
        Mapz->ReadBlock40d( bx, by, bz , &block );
        for (uint32_t x=0;x<16;++x)
        {
            for (uint32_t y=0;y<16;++y)
            {
                if ( (pattern[x][y]>1) || (roof && pattern[x][y]) )
                {
                    tp = getTileTypeP(block.tiletypes[x][y]);
                    d = &block.designation[x][y];
                    //Only modify this level if it's 'empty'
                    if ( EMPTY != tp->c && RAMP_TOP != tp->c && STAIR_DOWN != tp->c && DFHack::TILE_STREAM_TOP != tp->s)
                    {
                        continue;
                    }

                    //Need a floor for empty space.
                    if (reveal)
                    {
                        d->bits.hidden = 0; //topblock.designation[x][y].bits.hidden;
                    }
                    //Always clear the dig designation.
                    d->bits.dig = designation_no;
                    //unlock fluids, so they fall down the pit.
                    d->bits.flow_forbid = d->bits.liquid_static=0;
                    block.blockflags.bits.liquid_1 = block.blockflags.bits.liquid_2 = 1;
                    //Remove aquifer, to prevent bugginess
                    d->bits.water_table=0;
                    //Set the tile.
                    block.tiletypes[x][y] = cap + rand()%4;
                }
            }
        }
        //Write the block.
        Mapz->WriteBlockFlags(bx,by,bz, block.blockflags );
        Mapz->WriteDesignations(bx,by,bz, &block.designation );
        Mapz->WriteTileTypes(bx,by,bz, &block.tiletypes );
        Mapz->WriteDirtyBit(bx,by,bz,1);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    //All levels in between.
    int done=0;
    uint32_t t,v;
    int32_t z = bz-1;
    int32_t bottom = max(0,bz-pitdepth-1);
    assert( bottom>=0 && bottom<=bz );
    for ( ; !done && z>=bottom ; --z)
    {
        int watercount=0;
        int magmacount=0;
        int moltencount=0;
        int rockcount=0;
        int veincount=0;
        int emptycount=0;
        int hellcount=0;
        int templecount=0;
        int adamcount=0;
        int featcount=0;
        int tpat;

        cout << z << endl;
        assert( Mapz->isValidBlock(bx,by,z) );
        if (!Mapz->ReadBlock40d( bx, by, z , &block ))
        {
            cout << "Bad block! " << bx << "," << by << "," << z << endl;
        }

        //Pre-process this z-level, to get some tile statistics.
        for (int32_t x=0;x<16;++x)
        {
            for (int32_t y=0;y<16;++y)
            {
                t=0;
                tp = getTileTypeP(block.tiletypes[x][y]);
                d = &block.designation[x][y];
                tpat=pattern[x][y];

                //Tile type material categories
                switch ( tp->m )
                {
                    case AIR:
                        ++emptycount;
                        break;
                    case MAGMA:
                        ++moltencount;
                        break;
                    case VEIN:
                        ++veincount;
                        break;
                    case FEATSTONE:
                    case HFS:
                    case OBSIDIAN:
                        //basicly, ignored.
                        break;
                    default:
                        if ( EMPTY == tp->c || RAMP_TOP == tp->c || STAIR_DOWN == tp->c )
                        {
                            ++emptycount;
                        }
                        else
                        {
                            ++rockcount;
                        }
                        break;
                }

                //Magma and water
                if ( d->bits.flow_size )
                {
                    if (d->bits.liquid_type)
                    {
                        ++magmacount;
                    }
                    else
                    {
                        ++watercount;
                    }
                }


                //Check for Features
                if ( block.local_feature > -1 || block.global_feature > -1 )
                {
                    //Count tiles which actually are in the feature.
                    //It is possible for a block to have a feature, but no tiles to be feature.
                    if ( d->bits.feature_global || d->bits.feature_local )
                    {
                        //All features
                        ++featcount;

                        if ( d->bits.feature_global && d->bits.feature_local )
                        {
                            cout << "warn:tile is global and local at same time!" << endl;
                        }

                        n=0;
                        if ( block.global_feature > -1 && d->bits.feature_global )
                        {
                            n=global_features[block.global_feature].type;
                            switch ( n )
                            {
                                case feature_Other:
                                    //no count
                                    break;
                                case feature_Adamantine_Tube:
                                    ++adamcount;
                                    break;
                                case feature_Underworld:
                                    ++hellcount;
                                    break;
                                case feature_Hell_Temple:
                                    ++templecount;
                                    break;
                                default:
                                    //something here. for debugging, it may be interesting to know.
                                    if (n) cout << '(' << n << ')';
                            }
                        }

                        n=0;
                        if ( block.local_feature > -1 && d->bits.feature_local )
                        {
                            n=local_features[pc][block.local_feature]->type;
                            switch ( n )
                            {
                                case feature_Other:
                                    //no count
                                    break;
                                case feature_Adamantine_Tube:
                                    ++adamcount;
                                    break;
                                case feature_Underworld:
                                    ++hellcount;
                                    break;
                                case feature_Hell_Temple:
                                    ++templecount;
                                    break;
                                default:
                                    //something here. for debugging, it may be interesting to know.
                                    if (n) cout << '[' << n << ']';
                            }
                        }
                    }
                }
            }
        }


        //If stopping at magma, and no no non-feature stone in this layer, and magma found, then we're either at
        //or below the magma sea / molten rock.
        if ( stopatmagma && (moltencount || magmacount) && (!exposemagma || !rockcount) )
        {
            //If not exposing magma, quit at the first sign of magma.
            //If exposing magma, quite once magma is exposed.
            done=-1;
        }


        /////////////////////////////////////////////////////////////////////////////////////////////////
        //Some checks, based on settings and stats collected
        //First check, are we at illegal depth?
        if ( !done && hellcount && stopatmagma )
        {
            //Panic!
            done=-1;
            tpat=0;
            cout << "error: illegal breach of hell!" << endl;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////
        //Actually process the current z-level.
        //These loops do the work.
        for (int32_t x=0;!done && x<16;++x)
        {
            for (int32_t y=0;!done && y<16;++y)
            {
                t=0;
                tp = getTileTypeP(block.tiletypes[x][y]);
                d = &block.designation[x][y];
                tpat=pattern[x][y];

                //Up front, remove aquifer, to prevent bugginess
                //It may be added back if aquify is set.
                d->bits.water_table=0;

                //Change behaviour based on settings and stats from this z-level

                //In hell?
                if ( tpat && tpat!=3 && isfeature(global_features, local_features,block,pc,x,y,feature_Underworld ) )
                {
                    if ( exposehell )
                    {
                        tpat=0;
                    }
                }

                //Expose magma?
                if ( tpat && tpat!=3 && exposemagma )
                {
                    //Leave certain tiles unchanged.
                    switch ( tp->m )
                    {
                        case HFS:
                        case FEATSTONE:
                        case MAGMA:
                            tpat=0;
                        default:
                            break;
                    }
                    //Adamantine may be left unchanged...
                    if ( isfeature(global_features, local_features,block,pc,x,y,feature_Adamantine_Tube ) )
                    {
                        tpat=0;
                    }
                    //Leave magma sea unchanged.
                    if ( d->bits.flow_size && d->bits.liquid_type)
                    {
                        tpat=0;
                    }
                }


                //For all situations...
                //Special modification for walls, always for adamantine.
                if ( isfeature(global_features, local_features,block,pc,x,y,feature_Adamantine_Tube ) )
                {
                    if ( 2==pattern[x][y] || 3==pattern[x][y] )
                    {
                        tpat=2;
                    }
                }


                //Border or space?
                switch (tpat)
                {
                    case 0:
                        continue;
                        break;
                    case 1:
                        //Empty Space
                        t=32;
                        //d->bits.light = topblock.designation[x][y].bits.light;
                        //d->bits.skyview = topblock.designation[x][y].bits.skyview;
                        //d->bits.subterranean = topblock.designation[x][y].bits.subterranean;

                        //Erase special markers?
                        //d->bits.feature_global = d->bits.feature_local = 0;

                        //Water? Magma?
                        if (fillmagma || fillwater)
                        {
                            d->bits.flow_size=7;
                            d->bits.water_stagnant = false;
                            d->bits.water_salt = false;
                            if (fillmagma)
                            {
                                d->bits.liquid_type=liquid_magma;
                            }
                            else
                            {
                                d->bits.liquid_type=liquid_water;
                            }
                        }
                        else
                        {
                            //Otherwise, remove all liquids.
                            d->bits.flow_size=0;
                            d->bits.water_stagnant = false;
                            d->bits.water_salt = false;
                            d->bits.liquid_type = liquid_water;
                        }

                        break;
                    case 2:
                        //Wall.
                        //First guess based on current material
                        switch ( tp->m )
                        {
                            case OBSIDIAN:
                                t=wmagma;
                                break;
                            case MAGMA:
                                t=wmolten;
                                break;
                            case HFS:
                                //t=whell;
                                break;
                            case VEIN:
                                t=440; //Solid vein block
                                break;
                            case FEATSTONE:
                                t=335; //Solid feature stone block
                                break;
                            default:
                                t=wcave;
                        }
                        //Adamantine (a local feature) trumps veins.
                        {
                            //Local Feature?
                            if ( block.local_feature > -1  )
                            {
                                switch ( n=local_features[pc][block.local_feature]->type )
                                {
                                    case feature_Underworld:
                                    case feature_Hell_Temple:
                                        //Only adopt these if there is no global feature present
                                        if ( block.global_feature >-1 )
                                        {
                                            break;
                                        }
                                    case feature_Adamantine_Tube:
                                        //Always for adamantine, sometimes for others
                                        //Whatever the feature is made of. "featstone wall"
                                        d->bits.feature_global = 0;
                                        d->bits.feature_local = 1;
                                        t=335;
                                        break;
                                }
                            }
                            //Global Feature?
                            else if (block.global_feature > -1 && !d->bits.feature_local )
                            {
                                switch ( n=global_features[block.global_feature].type )
                                {
                                    case feature_Adamantine_Tube:
                                    case feature_Underworld:
                                    case feature_Hell_Temple:
                                        //Whatever the feature is made of. "featstone wall"
                                        d->bits.feature_global = 1;
                                        t=335;
                                        break;
                                }
                            }
                        }

                        //Erase any liquids, as they cause problems.
                        d->bits.flow_size=0;
                        d->bits.water_stagnant = false;
                        d->bits.water_salt = false;
                        d->bits.liquid_type=liquid_water;

                        //Placing an aquifer?
                        //(bugged, these aquifers don't generate water!)
                        if ( aquify )
                        {
                            //Only normal stone types can be aquified
                            if ( tp->m!=MAGMA && tp->m!=FEATSTONE && tp->m!=HFS  )
                            {
                                //Only place next to the hole.
                                //If no hole, place in middle.
                                if ( checkneighbors(pattern,x,y,1) || (7==x && 7==y) )
                                {
                                    d->bits.water_table = 1;
                                    //t=265; //soil wall
                                }
                            }
                        }
                        break;
                    case 3:
                        ////No obsidian walls on bottom of map!
                        //if(z<1 && (d->bits.feature_global || d->bits.feature_local) ) {
                        //  t=335;
                        //}

                        //Special wall, always sets to obsidian, to give a stairway
                        t=331;

                        //Erase special markers
                        d->bits.feature_global = d->bits.feature_local = 0;

                        //Erase any liquids, as they cause problems.
                        d->bits.flow_size=0;
                        d->bits.water_stagnant = false;
                        d->bits.water_salt = false;
                        d->bits.liquid_type=liquid_water;
                        break;
                    default:
                        cout << ".error,bad pattern.";
                }

                //For all tiles.
                if (reveal)
                {
                    d->bits.hidden = 0; //topblock.designation[x][y].bits.hidden;
                }
                //Always clear the dig designation.
                d->bits.dig=designation_no;
                //Make it underground, because it is capped
                d->bits.subterranean=1;
                d->bits.light=0;
                d->bits.skyview=0;
                //unlock fluids, so they fall down the pit.
                d->bits.flow_forbid = d->bits.liquid_static=0;
                block.blockflags.bits.liquid_1 = block.blockflags.bits.liquid_2 = 1;
                //Set the tile.
                block.tiletypes[x][y] = t;

            }
        }

        //Write the block.
        Mapz->WriteBlockFlags(bx,by,z, block.blockflags );
        Mapz->WriteDesignations(bx,by,z, &block.designation );
        Mapz->WriteTileTypes(bx,by,z, &block.tiletypes );
        Mapz->WriteDirtyBit(bx,by,z,1);

    }

    //Re-process the last z-level handled above.
    z++;
    assert( z>=0 );


    ///////////////////////////////////////////////////////////////////////////////////////////////
    //The bottom level is special.
    if (-1)
    {
        if (!Mapz->ReadBlock40d( bx, by, z , &block ))
        {
            cout << "Bad block! " << bx << "," << by << "," << z << endl;
        }
        for (uint32_t x=0;x<16;++x)
        {
            for (uint32_t y=0;y<16;++y)
            {
                t=floor;
                v=floorvar;
                tp = getTileTypeP(block.tiletypes[x][y]);
                d = &block.designation[x][y];

                if ( exposehell )
                {
                    //Leave hell tiles unchanged when exposing hell.
                    if ( isfeature(global_features,local_features,block,pc,x,y,feature_Underworld) )
                    {
                        continue;
                    }
                }

                //Does expose magma need anything at this level?
                if ( exposemagma && stopatmagma )
                {
                    continue;
                }

                switch (pattern[x][y])
                {
                    case 0:
                        continue;
                        break;
                    case 1:
                        //Empty becomes floor.

                        //Base floor type on the z-level first, features, then tile type.
                        if (!z) {
                            //Bottom of map, use the floor specified, always.
                            break;
                        }

                        ////Only place floor where ground is already solid when exposing
                        //if( EMPTY == tp->c || RAMP_TOP == tp->c || STAIR_DOWN == tp->c ){
                        //  continue;
                        //}

                        if ( d->bits.feature_global || d->bits.feature_global ) {
                            //Feature Floor!
                            t=344;
                            break;
                        }

                        //Tile material check.
                        switch ( tp->m )
                        {
                            case OBSIDIAN:
                                t=340;
                                v=3;
                                break;
                            case MAGMA:
                                v=0;
                                t=264; //magma flow
                                break;
                            case HFS:
                                //should only happen at bottom of map
                                break;
                            case VEIN:
                                t=441;  //vein floor
                                v=3;
                                break;
                            case FEATSTONE:
                                t=344;
                                v=3;
                                break;
                        }

                        break;
                    case 2:
                    case 3:
                        //Walls already drawn.
                        //Ignore.
                        continue;
                        break;
                }

                //For all tiles.
                if (reveal) d->bits.hidden = 0; //topblock.designation[x][y].bits.hidden;
                //Always clear the dig designation.
                d->bits.dig=designation_no;
                //unlock fluids
                d->bits.flow_forbid = d->bits.liquid_static=0;
                block.blockflags.bits.liquid_1 = block.blockflags.bits.liquid_2 = 1;

                //Set the tile.
                block.tiletypes[x][y] = t + ( v ? rand()&v : 0 );
            }
        }
        //Write the block.
        Mapz->WriteBlockFlags(bx,by,z, block.blockflags );
        Mapz->WriteDesignations(bx,by,z, &block.designation );
        Mapz->WriteTileTypes(bx,by,z, &block.tiletypes );
        Mapz->WriteDirtyBit(bx,by,z,1);
    }

    DF->Detach();
#ifndef LINUX_BUILD
    cout << "Done. Press any key to continue" << endl;
    cin.ignore();
#endif
    return 0;
}