
struct UserFunction : Shared
{
    Node *body;
    PlaceHolder *phlist;

    int xs, ys;
    wxString signature;

    #define loopphs(ph)             for(PlaceHolder  *ph =  phlist;  ph; ph =    ph ->sibling)
    #define loopphsindir(ph)        for(PlaceHolder **ph = &phlist; *ph; ph = &(*ph)->sibling)
    #define loopphsindirremove(ph)  for(PlaceHolder **ph = &phlist; *ph;                     )
    #define loopphsnext(ph)                                             (ph = &(*ph)->sibling)

    UserFunction(Node *_b, PlaceHolder *_phl) : body(_b), phlist(_phl)
    {
        body->fparent = this;
    }

    ~UserFunction()
    {
        DELETEP(body);
        while(phlist)
        {
            PlaceHolder *sib = phlist->sibling;
            ASSERT(!phlist->occurrences);
            delete phlist;
            phlist = sib;
        }
    }

    int NumPHs() { int i = 0; loopphs(ph) i++; return i; }

    int Layout(wxDC &dc)
    {
        signature = name+L"(";
        for(PlaceHolder *ph = phlist; ph; ph = ph->sibling)
        {
            signature += ph->name;
            if(ph->sibling) signature += L",";
        }
        signature += L") = ";
        sys->PickFont(dc, 0, STYLE_FIXED);
        dc.GetTextExtent(signature, &xs, &ys);
        return xs;
    }

    int Render(wxDC &dc, int x, int y, int yspace)
    {
        dc.SetTextForeground(*wxBLUE);
        sys->PickFont(dc, 0, STYLE_FIXED);
        dc.DrawText(signature, x, y+(yspace-ys)/2);
        dc.SetTextForeground(*wxBLACK);
        return xs;
    }

    bool FindXY(wxDC &dc, int x, int y)
    {
        return x>=0 && y>=0 && x<xs && y<ys;
    }

    bool FindNameInPHs(wxString &name)
    {
        loopphs(ph) if(ph->name==name) return true;
        return false;
    }
    /*
    bool RemovePH(PlaceHolder *ph)
    {
        loopphsindir(php) if(*php==ph)
        {
            *php = (*php)->sibling;
            return true;
        }
        return false;
    }
    */
    void RenameClashingVars()
    {
        loopphs(ph)
        {
            wxString name = ph->name;
            ph->name = wxEmptyString;
            if(PlaceHolder::NameExists(name, body)) ph->CreateUniqueName();
            else ph->name = name;
        }
    }

    void CreateUniqueFunctionName()
    {
        for(int i = 0; ; i++)
        {
            wxString newname = Identifier::GenName(true, i);
            if(sys->ProgramRoot()->FindFunction(newname)) continue;
            name = newname;
            return;
        }
    }

    void FunctionMerger()
    {
        body->FunctionMerger();

        restartmerge:

        int numc = NumPHs();

        loop(i, numc)
        {
            UserFunction *same = NULL;
            loopoccurrences(n)                          // see if for this arg, all occurrences are a call to the same function
            {
                Node *ch = n->GetChild(i);
                if(!ch) return;                         // one of the calls has too few args, no futher matches possible
                if(ch->type!=T_FUNCTIONCALL) goto nextarg;
                if(same) { if(same!=ch->uf) goto nextarg; } else same = ch->uf;
            }

            if(NumOcc()!=same->NumOcc()) goto nextarg;    // FIXME: should we still merge? (if so, do not delete "same" below)

            int j = 0;
            loopphsindir(php) if(j++==i)                // if yes, find matching placeholder
            {
                PlaceHolder *ph = *php;
                if(!ph->IsUsed() || ph->IsShared()) goto nextarg;        // if not, we'd have to inline the inner functions' call or body more than once, which would then be undone by 2a/2b, and cause a loop with this function

                *php = same->phlist;                    // replace placeholder by list of placeholders from the function we're merging in
                same->phlist = NULL;
                while(*php) loopphsnext(php);
                *php = ph->sibling;
                ph->sibling = NULL;

                loopoccurrences(n)                      // replace inner function calls by their own arguments
                {
                    Node *fc = n->RemoveNthChild(i);
                    int j = i;
                    while(fc->children) n->InsertAt(fc->RemoveNthChild(0), j++);
                    fc->uf = NULL;
                    delete fc;
                }

                same->body->fparent = NULL;
                delete ph->occurrences->parent->Replace(ph->occurrences, same->body);
                same->body = NULL;
                delete same;
                delete ph;

                RenameClashingVars();       // OPT: need to do this just for inlined vars

                sys->codechanged = true;

                goto restartmerge;      // can't just iterate in loop, since number of args may have changed
            }

            nextarg:;
        }
    }

    void InlineSingleUsePHs(Node *fc)
    {
        int i = 0;
        loopphsindirremove(php)
        {
            PlaceHolder *ph = *php;
            if(ph->IsUsed() && !ph->IsShared())
            {
                Node *val = fc->RemoveNthChild(i);
                Node *toreplace = ph->occurrences;
                ASSERT(toreplace->parent);
                delete toreplace->parent->Replace(toreplace, val);
                *php = ph->sibling;
                delete ph;
                sys->codechanged = true;
            }
            else
            {
                loopphsnext(php);
                i++;
            }
        }
    }

    void RemovePHandArgs(int i, PlaceHolder **php)
    {
        loopoccurrences(n) if(n->NumChildren()>i) delete n->RemoveNthChild(i);
        PlaceHolder *ph = *php;
        *php = ph->sibling;
        delete ph;
        sys->codechanged = true;
    }

    void InlineUnusedAndMappedPHs()
    {
        int i = 0;
        loopphsindirremove(php)
        {
            if(!(*php)->IsUsed() && sys->aggressivefactor)
            {
                RemovePHandArgs(i, php);
            }
            else
            {
                // FIXME: can't think of a test case where this is needed that doesn't just also work with regular inlining

                /*
                PlaceHolder *nph = NULL;
                loopoccurrences(n)              // find out if ALL values to this PH happen to be the same other PH
                {
                    if(n->NumChildren()<=i) { nph = NULL; break; };
                    Node *ni = n->GetChild(i);
                    if(ni->type!=T_PLACEHOLDER || (nph && ni->ph!=nph)) { nph = NULL; break; }
                    else nph = ni->ph;
                }

                if(nph)
                {
                    (*php)->Recast(nph);
                    RemovePHandArgs(i, php);
                }
                else
                {*/
                    loopphsnext(php);
                    i++;
                //}
            }
        }
    }

    void InlineOverAbstract()
    {
        int numc = NumPHs();

        loopoccurrences(n)
        {
            Vector<Node *> children;
            while(n->children) children.push() = n->RemoveNthChild(0);      // need to reinsert children in order of placeholder use

            loop(i, numc)
            {
                PlaceHolder *ph = body->GetChild(i)->ph;
                int j = 0;
                loopphs(phl) if(phl==ph) break; else j++;                   // find placeholder location
                n->InsertAt(children[j], i);
                children[j] = NULL;
            }

            n->uf = body->uf;                                               // now simply call new function
        }

        loopoccurrencesindirendless(n) if(!*n)                              // find end of list of occurrences, merge both together
        {
            *n = body->uf->occurrences;
            body->uf->occurrences = occurrences;
            occurrences = NULL;
            delete this;                                                    // all references to it are gone, deleting body will remove one caller of new target function, and all placeholders
            return;
        }
    }

    bool Equal(UserFunction *o)
    {
        if(NumPHs()!=o->NumPHs()) return false;
        return body->Equal(*o->body, phlist, o->phlist);
    }

    void MergeInto(UserFunction *destuf)
    {
        Node **last = NULL;
        loopoccurrences(n)
        {
            n->uf = destuf;
            if(!n->nextoccurrence) last = &n->nextoccurrence;
        }
        *last = destuf->occurrences;
        destuf->occurrences = occurrences;
        occurrences = NULL;
        delete this;
    }
};

