// Catsplosion
// By Zhentar , Further modified by dark_rabite, peterix, belal
// This work of evil makes animals pregnant
// and due within 2 in-game hours...

#include <iostream>
#include <integers.h>
#include <cstdlib>
#include <assert.h>
#include <climits>
#include <stdlib.h> // for rand()
#include <algorithm> // for std::transform
#include <vector>
#include <list>
#include <iterator>
using namespace std;

#include <DFError.h>
#include <DFTypes.h>
#include <DFHackAPI.h>
#include <DFMemInfo.h>
#include <DFProcess.h>
#include <argstream.h>


vector<DFHack::t_matgloss> creaturestypes;
DFHack::memory_info *mem;
DFHack::Process *proc;
uint32_t creature_pregnancy_offset;

bool femaleonly = 0;
bool showcreatures = 0;
int maxpreg = 1000; // random start value, since its not required and im not sure how to set it to infinity
list<string> s_creatures;

int main ( int argc, char** argv )
{
    // parse input, handle this nice and neat before we get to the connecting
    argstream as(argc,argv);
    as  >>option('f',"female",femaleonly,"Impregnate females only")
        >>option('s',"show",showcreatures,"Show creature list (read only)")
        >>parameter('m',"max",maxpreg,"The maximum limit of pregnancies ", false)
        >>values<string>(back_inserter(s_creatures), "any number of creatures")
        >>help();

    if (!as.isOk())
    {
        cout << as.errorLog();
        return(0);
    }
    else if (as.helpRequested())
    {
        cout<<as.usage()<<endl;
        return(1);
    }
    else if(showcreatures==1)
    {
    }
    else if (s_creatures.size() == 0 && showcreatures != 1)
    {
        cout << as.usage();
        return(1);
    }

    DFHack::API DF("Memory.xml");
    try
    {
        DF.Attach();
    }
    catch (exception& e)
    {
        cerr << e.what() << endl;
        #ifndef LINUX_BUILD
            cin.ignore();
        #endif
        return 1;
    }

    proc = DF.getProcess();
    mem = DF.getMemoryInfo();
    creature_pregnancy_offset = mem->getOffset("creature_pregnancy");

    if(!DF.ReadCreatureMatgloss(creaturestypes))
    {
        cerr << "Can't get the creature types." << endl;
        #ifndef LINUX_BUILD
            cin.ignore();
        #endif
        return 1;
    }

    uint32_t numCreatures;
    if(!DF.InitReadCreatures(numCreatures))
    {
        cerr << "Can't get creatures" << endl;
        #ifndef LINUX_BUILD
            cin.ignore();
        #endif
        return 1;
    }

    int totalcount=0;
    int totalchanged=0;
    string sextype;

    // shows all the creatures and returns.
    if (showcreatures == 1)
    {
        int maxlength = 0;
        map<string,uint32_t> male_counts;
        map<string,uint32_t> female_counts;
        for(uint32_t i =0;i < numCreatures;i++)
        {
            DFHack::t_creature creature;
            DF.ReadCreature(i,creature);
            if(creature.sex == 1){
                male_counts[creaturestypes[creature.type].id]++;
                female_counts[creaturestypes[creature.type].id]+=0; //auto initialize the females as well
            }
            else{
                female_counts[creaturestypes[creature.type].id]++;
                male_counts[creaturestypes[creature.type].id]+=0;
            }
        }
        cout << "Type\t\t\tMale #\tFemale #" << endl;
        for(map<string, uint32_t>::iterator it1 = male_counts.begin();it1!=male_counts.end();it1++)
        {
            cout << it1->first << "\t\t" << it1->second << "\t" << female_counts[it1->first] << endl;
        }
        return(1);
    }

    for(uint32_t i = 0; i < numCreatures && totalchanged != maxpreg; i++)
    {
        DFHack::t_creature creature;
        DF.ReadCreature(i,creature);
        if (showcreatures == 1)
        {
            if (creature.sex == 0) { sextype = "Female"; } else { sextype = "Male";}
            cout << string(creaturestypes[creature.type].id) << ":" << sextype << "" << endl;
        }
        else
        {
            s_creatures.unique();
            for (list<string>::iterator it = s_creatures.begin(); it != s_creatures.end(); ++it)
            {
                std::string clinput = *it;
                std::transform(clinput.begin(), clinput.end(), clinput.begin(), ::toupper);
                if(string(creaturestypes[creature.type].id) == clinput)
                {
                    if((femaleonly == 1 && creature.sex == 0) || (femaleonly != 1))
                    {
                        proc->writeDWord(creature.origin + creature_pregnancy_offset, rand() % 100 + 1);
                        totalchanged+=1;
                        totalcount+=1;
                    }
                    else
                    {
                        totalcount+=1;
                    }
                }
            }
        }
    }

    cout << totalchanged << " animals impregnated out of a possible " << totalcount << "." << endl;

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