
_CallFunction := function(fun,args)

  if _Size_list(args)=0 then
    return fun();
  fi;
  if _Size_list(args)=1 then
    return fun(args[1]);
  fi;
  if _Size_list(args)=2 then
    return fun(args[1],args[2]);
  fi;
  if _Size_list(args)=3 then
    return fun(args[1],args[2],args[3]);
  fi;
  if _Size_list(args)=4 then
    return fun(args[1],args[2],args[3],args[4]);
  fi;
  if _Size_list(args)=5 then
    return fun(args[1],args[2],args[3],args[4],args[5]);
  fi;
  if _Size_list(args)=6 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6]);
  fi;
  if _Size_list(args)=7 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6],args[7]);
  fi;
  if _Size_list(args)=8 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8]);
  fi;
  if _Size_list(args)=9 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8],args[9]);
  fi;
  if _Size_list(args)=10 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8],args[9],args[10]);
  fi;
  if _Size_list(args)=11 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8],args[9],args[10],args[11]);
  fi;
  if _Size_list(args)=12 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8],args[9],args[10],args[11],args[12]);
  fi;
  if _Size_list(args)=13 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8],args[9],args[10],args[11],args[12],args[13]);
  fi;
  if _Size_list(args)=14 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8],args[9],args[10],args[11],args[12],args[13],args[14]);
  fi;
  if _Size_list(args)=15 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8],args[9],args[10],args[11],args[12],args[13],args[14],args[15]);
  fi;
  if _Size_list(args)=16 then
    return fun(args[1],args[2],args[3],args[4],args[5],args[6],args[7],args[8],args[9],args[10],args[11],args[12],args[13],args[14],args[15],args[16]);
  fi;
  Error("too many arguments in call of method.");
end;



QPrint:=
  function(atom)
    if IsString(atom) then
      return "\""+atom+"\"";
    else
      return Replace(Replace(SPrint(atom),"\n"," "),";$","");
    fi;
  end;


if not(IsBound(method)) then
  NewType("method",0);
fi;

_DisplayMethod:=
  hash->DisplayDocSig(RetrieveDocumentation(hash));
_DisplayMethodFuns:=
  function(funs)
    local hashdry;
    hashdry:=Dry([]);
    Mapc(
      function(fun)
        local hash,i;
        hash:=_GetEntry_rec_string(fun,"hash",rec(Fail:=[]));
        if Is(Type(hash),list) then
          Mapc(i->DryAdd_(hashdry,i),hash);
        else
          DryAdd_(hashdry,hash);
        fi;
      end,
      funs);
    Mapc(_DisplayMethod,hashdry);
  end;
_PrintMethod:=
  function(meth)
    local name;
    name:=_GetEntry_rec_string(meth,"name",rec(Fail:=""));
    Print("Method `",name,"', installed signatures:\n\n");
    _DisplayMethodFuns(_GetEntry_rec_string(meth,"funs",rec(Fail:=[])));
  end;

_EvalMethod:=
  function(name,wrapper)
    return
      function(arg)
        local art,oamatch,oahere,wrmatch,ret,args,wrrec,optargs,optargrec,defvals,i,lim,
          _min_of_art_or,_min_of_art\-1_or;

        _min_of_art_or:=
          function(i)
            if _Size_list(art)<_Size_list(_GetEntry_rec_string(i,"sin",rec(Fail:=[]))) then
              return _Size_list(i.("sin"));
            else
              return _Size_list(art);
            fi;
          end;

        _min_of_art\-1_or:=
          function(i)
            if _Size_list(art)-1<_Size_list(_GetEntry_rec_string(i,"sin",rec(Fail:=[]))) then
              return _Size_list(i.("sin"));
            else
              return _Size_list(art)-1;
            fi;
          end;

        ## first of all examine the arguments
        art:=ShallowCopy(arg);
        _Apply__func_list(Type,art);
        ret:=VOID;
        oahere:=FALSE;

        if _Size_list(arg)=0 then
          wrrec:=_First_list_func(wrapper,i->_Size_list(i.("sin"))=0,rec(Fail:=FAILURE));
          wrmatch:=IsSuccess(wrrec);
        else
          wrrec:=
            _First_list_func(wrapper,
              i->_ForAll_func_list(
                j->Is(_GetEntry_list_eor(art,j,rec(Fail:=void)),
                      _GetEntry_list_eor(
                        _GetEntry_rec_string(i,"sin",rec(Fail:=[])),
                        j,rec(Fail:=void))),
                     [1.._min_of_art_or(i)]),rec(Fail:=FAILURE));
          wrmatch:=IsSuccess(wrrec);
        fi;

        ## last argument could be an optarg record
        if not(wrmatch) and Type(Last(arg))=record then
          wrrec:=
            _First_list_func(wrapper,
              i->_ForAll_func_list(
                j->Is(_GetEntry_list_eor(Butlast(art),j,rec(Fail:=void)),
                      _GetEntry_list_eor(
                        _GetEntry_rec_string(i,"sin",rec(Fail:=[])),
                        j,rec(Fail:=void))),
                     [1.._min_of_art\-1_or(i)]),rec(Fail:=FAILURE));
          wrmatch:=IsSuccess(wrrec);
          oahere := wrmatch;
        fi;

        ## what's this case? :o
        if not(wrmatch) then
          wrrec:=_First_list_func(wrapper,i->i.("sin")[1]=unknown,rec(Fail:=FAILURE));
          #wrmatch:=IsSuccess(FAILURE);
          ## with this the fallthrough to formerly known functions works
          wrmatch:=IsSuccess(wrrec);
        fi;

        if wrmatch then
          optargs:=wrrec.("opt");
          defvals:=wrrec.("DefaultValues");
          if _Size_list(Flat(optargs))>0 then
            oamatch:=1;
          else
            oamatch:=0;
          fi;
          if _Size_list(arg)<_Size_list(wrrec.sin)+oamatch then
            ## apparently the optarg was omitted
	    if (_Size_list(RecFields(defvals)) > 0) then
              ret := _CallFunction(wrrec,_Add_list_any(arg,defvals));
            else
              ret := _CallFunction(wrrec,arg);
	    fi;
          elif oamatch>0 then
            ## the last arg should contain optargs
            optargrec:=Last(arg);
            if Type(optargrec)=record then
              arg:=_Add_list_any(Butlast(arg),MergeRecords(defvals,optargrec));
            fi;
            ret := _CallFunction(wrrec,arg);
          else
            ret := _CallFunction(wrrec,arg);
          fi;

          ## now handle the case when the function in wrrec does not support
          ## optargs but this wrapper should handle them
          if oahere and ret=FAILURE then
            ret := _GetEntry_rec_string(Last(arg),"Fail",rec(Fail:=ret));
          fi;

          return ret;

        else
          Print("`"+name+"' called with ",art,", but only applicable as:\n\n");
          _DisplayMethodFuns(wrapper);
          Print("\n");
          return VOID;
        fi;
      end;
    end;

_Wrapper:=
  function(name,wrapper)
    return
      rec(
        base:=_EvalMethod(name,wrapper),
        funs:=wrapper,
        type:=method,
        name:=name,
        operations:=
          rec(
            Print:=_PrintMethod));
  end;


#############################################################################
##
#F  InstallMethod( <record>, <func> )
##
## now behold this cruft of bootstrapping :)
_InstallMethod_rec_func_rec:=
  function(docrec, func,optargs)
    local drname,wr,wrrec,sin,opt,DefaultValues,WrapFormerFuns,pos,f,hash,former,typ,
      ## Profile
      stime,etime;

    stime:=UTime();
    ## check if we are installing a function
    if not(IsBound(docrec.("kind"))) then
      docrec.kind:="FUNCTION";
    fi;
    if not(IsBound(docrec.("sou"))) then
      docrec.sou:=[[any]];
    fi;
    if not(docrec.kind="FUNCTION") then
      return Error("InstallMethod may only be used to install functions.");
    fi;

    _BlowUpDocumentationHash(docrec);
    f :=MergeDocumentation(docrec,rec(Fail:=FALSE,Success:=TRUE,Add:=TRUE));
    if not(f) then
      Print("MergeDocumentation failed.\nProceeding nervously ...\n");
    fi;

    ## check if we requested not to initialise former functions
    WrapFormerFuns := _GetEntry_rec_string(optargs,"WrapFormerFuns",rec(Fail:=TRUE));

    drname := docrec.name;
    hash := docrec.hash;
    if EvalString("IsBound(" + drname + ")") then
      ## now check if the bound object is already a wrapper
      typ:=Name(EvalString("Type(" +drname+")"));
      if typ="method" then
        wr := EvalString(drname+".funs");
      elif typ="func" and WrapFormerFuns then
        former := EvalString("Copy("+drname+")");
        wr := VOID;
      elif typ="func" and not(WrapFormerFuns) then
        wr := VOID;
      else
        Unbind(drname);
        wr := VOID;
      fi;
    else
      wr := VOID;
      typ := VOID;
    fi;
    f:=Copy(func);
    if IsBound(docrec.sin) then
      sin:=_FlattenSin(docrec.sin);
    else
      return Error("sin is mandatory");
    fi;
    ## now check if sin has `unknown' types in it
    if _ForAny_func_list(i->i=unknown,sin) then
      return Error("Must not use type `unknown' in sin.");
    fi;

    ## check if there's an optional arg
    opt := _GetEntry_rec_string(docrec,"opt",rec(Fail:=[]));

    ## now check the defaultvals
    DefaultValues := _GetEntry_rec_string(optargs,"DefaultValues",rec(Fail:=rec()));

    if wr=VOID then
      ## create a new wrapper
      wr:=[];
      _Add__list_any(wr,rec(base:=f,sin:=sin,hash:=hash,opt:=opt,DefaultValues:=DefaultValues));
      if typ="func" and WrapFormerFuns then
        ## then, wrap former funs to some kinda fallback
        ## Type is unknown since we do not know the signature
        _Add__list_any(wr,rec(base:=former,sin:=[unknown],hash:=_Apply_func_list(j->j.hash,_Filtered_func_list(i->not(i.hash=hash),DocFindNamesCS(drname))),
             opt:=opt,DefaultValues:=DefaultValues));
      fi;
      Bind(drname,_Wrapper(drname,wr));
    else
      ## we already have a wrapper
      ## thus install the fun given
      wrrec:=rec(base:=f,sin:=sin,hash:=hash,opt:=opt,DefaultValues:=DefaultValues);
      opt:=optargs.Position;
      if opt<0 then
        opt:=_Size_list(wr)+opt;
      fi;
      if _Size_list(wrrec.("sin"))>0 then
        if wrrec.("sin")[1]=nof(any) then
          _Add__list_any(wr,wrrec);
        else
          Insert_(wr,wrrec,optargs.Position);
        fi;
      else
        Insert_(wr,wrrec,optargs.Position);
      fi;
      Bind(drname,_Wrapper(drname,wr));
    fi;
    etime := UTime();
    ##Print("\n",docrec.name,": ",etime-stime);
  end;
_InstallMethod_rec_func:=
  function(docrec, func)
    _InstallMethod_rec_func_rec(docrec,func,rec(Position:=1));
  end;
_InstallMethod_rec_func_rec(
  rec(
    kind:="FUNCTION",
    name:="InstallMethod",
    sin:=[[record,"docrec"],[func,"body"]],
    opt:=[[elt-ord^rat,"Position","Default: 1, negative numbers are ..."]],
    sou:=[[]],
    short:="Install the function `body' to be executed when called with "+
      "arguments as specified by `docrec'.sin.\n"+
      "Note: `docrec' has to be a fully qualified documentation record, "+
      "`MergeDocumentation' is called automatically.",
    see:=[DocHash("MergeDocumentation(record)")]), _InstallMethod_rec_func_rec,
    rec(DefaultValues:=rec(Position:=1)));
