/*****
* animation.asy
* Andy Hammerlindl and John Bowman 2005/11/06
*
* Produce GIF, inline PDF, or other animations.
*****/

// animation delay is in milliseconds
real animationdelay=50;

typedef frame enclosure(frame);

frame NoBox(frame f) {
 return f;
}

enclosure BBox(real xmargin=0, real ymargin=xmargin,
              pen p=currentpen, filltype filltype=NoFill) {
 return new frame(frame f) {
   box(f,xmargin,ymargin,p,filltype,above=false);
   return f;
 };
}

struct animation {
 picture[] pictures;
 string[] files;
 int index;

 string prefix;
 bool global; // If true, use a global scaling for all frames; this requires
 // extra memory since the actual shipout is deferred until all frames have
 // been generated.

 void operator init(string prefix="", bool global=true) {
   prefix=replace(stripdirectory(outprefix(prefix))," ","_");
   this.prefix=prefix;
   this.global=global;
 }

 string basename(string prefix=stripextension(prefix)) {
   return "_"+prefix;
 }

 string name(string prefix, int index) {
   return stripextension(prefix)+"+"+string(index);
 }

 private string nextname() {
   string name=basename(name(prefix,index));
   ++index;
   return name;
 }

 void shipout(string name=nextname(), frame f) {
   string format=nativeformat();
   plain.shipout(name,f,format=format,view=false);
   files.push(name+"."+format);
 }

 void add(picture pic=currentpicture, enclosure enclosure=NoBox) {
   if(global) {
     ++index;
     pictures.push(pic.copy());
   } else this.shipout(enclosure(pic.fit()));
 }

 void purge(bool keep=settings.keep) {
   if(!keep) {
     for(int i=0; i < files.length; ++i)
       delete(files[i]);
   }
 }

 int merge(int loops=0, real delay=animationdelay, string format="gif",
           string options="", bool keep=settings.keep) {
   string args="-loop " +(string) loops+" -delay "+(string)(delay/10);
   for(int i=0; i < files.length; ++i)
     args += " "+files[i];
   args += " -alpha Off -dispose Background "+options;
   int rc=convert(args,prefix+"."+format,format=format);
   this.purge(keep);
   if(rc == 0) animate(file=prefix+"."+format);
   else abort("merge failed");
   return rc;
 }

 void glmovie(string prefix=prefix, projection P=currentprojection) {
   if(!view() || settings.render == 0 || settings.outformat == "html") return;
   fit(prefix,pictures,view=true,P);
 }

 // Export all frames with the same scaling.
 void export(string prefix=prefix, enclosure enclosure=NoBox,
             bool multipage=false, bool view=false,
             projection P=currentprojection) {
   if(pictures.length == 0) return;
   if(!global) multipage=false;
   bool inlinetex=settings.inlinetex;
   if(multipage)
     settings.inlinetex=false;
   frame multi;
   frame[] fits=fit(prefix,pictures,view=false,P);
   for(int i=0; i < fits.length; ++i) {
     string s=name(prefix,i);
     if(multipage) {
       add(multi,enclosure(fits[i]));
       newpage(multi);
       files.push(s+"."+nativeformat());
     } else {
       if(pictures[i].empty3() || settings.render <= 0)
         this.shipout(s,enclosure(fits[i]));
       else // 3D frames
         files.push(s+"."+nativeformat());
     }
   }
   if(multipage) {
     plain.shipout(prefix,multi,view=view);
     settings.inlinetex=inlinetex;
   }
 }

 string load(int frames, real delay=animationdelay, string options="",
             bool multipage=false) {
   if(!global) multipage=false;
   string s="\animategraphics["+options+"]{"+format("%.18f",1000/delay,"C")+
     "}{"+basename();
   if(!multipage) s += "+";
   s += "}{0}{"+string(frames-1)+"}";
   return s;
 }

 bool pdflatex()
 {
   return latex() && pdf();
 }

 string pdf(enclosure enclosure=NoBox, real delay=animationdelay,
            string options="", bool keep=settings.keep, bool multipage=true) {
   settings.twice=true;
   if(settings.inlinetex) multipage=true;
   if(!global) multipage=false;
   if(!pdflatex())
     abort("inline pdf animations require -tex pdflatex or -tex xelatex");
   if(settings.outformat != "") settings.outformat="pdf";

   string filename=basename();
   string pdfname=filename+".pdf";

   if(global)
     export(filename,enclosure,multipage=multipage);

   if(!keep) {
     exitfcn currentexitfunction=atexit();
     void exitfunction() {
       if(currentexitfunction != null) currentexitfunction();
       if(multipage || !settings.inlinetex)
         this.purge();
       if(multipage && !settings.inlinetex)
         delete(pdfname);
     }
     atexit(exitfunction);
   }

   if(!multipage)
     delete(pdfname);

   return load(index,delay,options,multipage);
 }

 int movie(enclosure enclosure=NoBox, int loops=0, real delay=animationdelay,
           string format=settings.outformat == "" ? "gif" : settings.outformat,
           string options="", bool keep=settings.keep) {
   if(global) {
     if(format == "pdf") {
       export(enclosure,multipage=true,view=true);
       return 0;
     }
     export(enclosure);
   }
   return merge(loops,delay,format,options,keep);
 }
}

animation operator init() {
 animation a=animation();
 return a;
}