import java.awt.*;
import java.awt.image.*;
import java.awt.image.PixelGrabber;
import java.awt.image.MemoryImageSource; 
import java.applet.*;

import Globals.*;
import Approximations.*;

/* TODO
      Questions marked as do1 and do2
      Make GUI to enter parameters in one place
*/


public class Editor extends Applet implements Runnable{


   //===============================================
   //Special sprite: rings:
   //-----------------------------------------------
     int speRadius    = -1; //Flag. -1 disables spe.
     int speRingWidth = 4;
     int speFrequency = 5;
     int speDensity   = 4096;
     int speColorMaxR = 234;
     int speColorMaxG = 244;
     int speColorMaxB = 250;
     int speColorMinR = 157;
     int speColorMinG = 185;
     int speColorMinB = 199;
     //Derivative:
     int speAR = speColorMaxR - speColorMinR;
     int speAG = speColorMaxG - speColorMinG;
     int speAB = speColorMaxB - speColorMinB;
   //-----------------------------------------------
   //Special sprite: rings:
   //===============================================


   //Control parameters:
   static final int TRANSPARENT_COLOR = 0;

   //"Yes" means ... dimensions of both images
   //are equal, and they are coisided:
   boolean spriteFitsBase;

   int animationFramesNumber;
   int rotationCenterOnBaseX;
   int rotationCenterOnBaseY;
   int leftTopOfSpriteOnBaseX;
   int leftTopOfSpriteOnBaseY;

   Image baseIm;
   Image spriteIm;
   //topIm should have have transparent parts
   //or transparent color or both:
   Image topIm;

   Image noAnimationIm;



   //Auxiliary:

   MediaTracker mt;

   Graphics baseG;
   Graphics spriteG;
   PixelGrabber spritePG;
   PixelGrabber basePG; 
   PixelGrabber topPG; 
   int[] spriteArr;
   int[] baseArr;
   int[] baseArrBuff;
   int[] topArr;
   Image baseWithSprite;

   Image resultIm;
   Graphics resultG;
   Image[][] resultArr;

   Image     spriteImNew;
   Graphics  spriteGNew;

   String baseFName;
   String spriteFName;
   String topFName;
   String noAnimationFName;

   int columnNumber;
   int rowNumber;
   int resultX;
   int resultY;

   int baseW;
   int baseH;
   int spriteW;
   int spriteH;
   int topW; //Redundant
   int topH; //Redundant

   //Auxiliary members:
   private static boolean imageIsReady;
   private static       int frameNumber = 0;

   private static    Thread calculateThread;
   private static       long timeToPrintNewFrame;
   private static       long timeLeftAfterCalculations;

   Dimension scrDimension;
   private static long frameDelay = 200;

   boolean checkPassed = false;


   public void init() {

         //Control parameters:
         checkPassed = true;
         spriteFitsBase = true;
         columnNumber = 4;
         rowNumber    = 4;

         String ws = getParameter("columnNumber");
         try { if (ws != null ) columnNumber = Integer.parseInt(ws);
         } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

         ws = getParameter("rowNumber");
         try { if (ws != null ) rowNumber = Integer.parseInt(ws);
         } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

         ws = getParameter("frameDelay");
         try { if (ws != null ) frameDelay = (int) (Integer.parseInt(ws));
         } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

         baseFName = getParameter("baseFName");
         spriteFName = getParameter("spriteFName");
         topFName = getParameter("topFName");
         noAnimationFName = getParameter("noAnimationFName");

         animationFramesNumber = columnNumber*rowNumber - 2;
         resultX      = -1; //Flag.
         scrDimension = getSize();
   
         imageIsReady = false;
         frameNumber = 0;

         //Principals:
         mt = new MediaTracker(this);

         baseIm   = getImage( getCodeBase(), "Resources/" + baseFName );
         spriteIm = getImage( getCodeBase(), "Resources/" + spriteFName );
         topIm    = getImage( getCodeBase(), "Resources/" + topFName );
         noAnimationIm = getImage( getCodeBase(), "Resources/" + noAnimationFName );

         mt.addImage( baseIm,   0 );
         mt.addImage( spriteIm, 1 );
         mt.addImage( topIm,    2 );
         mt.addImage( noAnimationIm,    3 );

         //do2 Why mt does not load without these?:
         mt.statusID(0, true); 
         mt.statusID(1, true); 
         mt.statusID(2, true); 
         mt.statusID(3, true); 

         //do1 infinite loops:
         try {
             while( !mt.checkAll()  ) {
                    GS.con("Still downloading images ... " + 
                           "base="    + trackerStatus(mt,0) + 
                           " sprite=" + trackerStatus(mt,1)   );
                    Thread.sleep(1000);
             }

             while( baseIm.getWidth(this)    <=0 || 
                    baseIm.getHeight(this)   <=0 ||
                    spriteIm.getWidth(this)  <=0 || 
                    spriteIm.getHeight(this) <=0 ||
                    topIm.getWidth(this)     <=0 || 
                    topIm.getHeight(this)    <=0  ) {
                    GS.con("Still negative dimensions ... "); 
                    Thread.sleep(1000);
             }

             baseW  =baseIm.getWidth(this);
             baseH  =baseIm.getHeight(this);
             spriteW=baseIm.getWidth(this);
             spriteH=baseIm.getHeight(this);
             topW   =topIm.getWidth(this); //Redundant
             topH   =topIm.getHeight(this); //Redundant

             //Do control:
             rotationCenterOnBaseX =  20; //baseW/2;
             rotationCenterOnBaseY =  19; //baseH/2;
             leftTopOfSpriteOnBaseX = 0;
             leftTopOfSpriteOnBaseY = 0;
             if( spriteFitsBase ) {
                 leftTopOfSpriteOnBaseX = 0;
                 leftTopOfSpriteOnBaseY = 0;
             }

         } catch (Exception e) {
             e.printStackTrace();
         }
         if( mt.isErrorAny() ) {
             GS.con("Problems with some images ... ");
             //int st = mt.statusID(0, false); 
         } else {
           GS.con( "All images downloaded successfully." );

           //do2: Strangerly: these calls "shakes" dimensions and
           //enables them later:
           //GS.con( "Width=" + baseIm.getWidth(this) + 
           //        " Height=" + baseIm.getHeight(this)   );

           resultX = -2; //Success.
         }   


     //===============================================
     //Special sprite: rings:
     //-----------------------------------------------
     ws = getParameter("speRadius");
     try { if (ws != null ) speRadius = (int) (Integer.parseInt(ws));
     } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }
     GS.c("speRadius" + speRadius);

     ws = getParameter("speRingWidth");
     try { if (ws != null ) speRingWidth = (int) (Integer.parseInt(ws));
     } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

     ws = getParameter("speFrequency");
     try { if (ws != null ) speFrequency = (int) (Integer.parseInt(ws));
     } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

     ws = getParameter("speDensity");
     try { if (ws != null ) speDensity = (int) (Integer.parseInt(ws));
     } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

     ws = getParameter("speColorMaxR");
     try { if (ws != null ) speColorMaxR = (int) (Integer.parseInt(ws));
     } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

     ws = getParameter("speColorMaxG");
     try { if (ws != null ) speColorMaxG = (int) (Integer.parseInt(ws));
     } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

     ws = getParameter("speColorMaxB");
     try { if (ws != null ) speColorMaxB = (int) (Integer.parseInt(ws));
     } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

     ws = getParameter("speColorMinR");
     try { if (ws != null ) speColorMinR = (int) (Integer.parseInt(ws));
     } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

     ws = getParameter("speColorMinG");
     try { if (ws != null ) speColorMinG = (int) (Integer.parseInt(ws));
     } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }

     ws = getParameter("speColorMinB");
     try { if (ws != null ) speColorMinB = (int) (Integer.parseInt(ws));
     } catch (Exception e) { checkPassed = false; GS.c(e.toString()); }
     //-----------------------------------------------
     //Special sprite: rings:
     //===============================================


         ICos.generate();

         if( !checkPassed ) GS.c("Check not passed"); 
         GS.con("Init finished");       


   } //init()


   private String trackerStatus( MediaTracker mt, int index ) {
           String s = "";
           int st = mt.statusID(index, false); 
           if(  0 == st ) { s = "NOT STARTED"; return s; }
           if(  (st & MediaTracker.ABORTED)  !=0  )  s = s + "ABORTED ";
           if(  (st & MediaTracker.COMPLETE) !=0  )  s = s + "COMPLETE ";
           if(  (st & MediaTracker.ERRORED)  !=0  )  s = s + "ERRORED ";
           if(  (st & MediaTracker.LOADING)  !=0  )  s = s + "LOADING ";
           return s;
   }

   public void start() {
      timeToPrintNewFrame = System.currentTimeMillis();
      if( calculateThread == null ) {
          calculateThread = new Thread(this);
          calculateThread.setName("Calc");
          calculateThread.start();
          GS.con( calculateThread.getName() + " initiated." );
      }
   }



   public void run() {

     try {
       while( Thread.currentThread() == calculateThread &&
              checkPassed ) {
          timeLeftAfterCalculations = timeToPrintNewFrame - System.currentTimeMillis();
          repaint();
          Thread.sleep(Math.max(0, timeLeftAfterCalculations));
          frameNumber++;
          //GS.con( "Finished sleeping..." );
          timeToPrintNewFrame = System.currentTimeMillis() + frameDelay;

          if( resultX == -2 ) {
                    resultX      = columnNumber * baseW;
                    resultY      = rowNumber * baseH;
                    GS.con ("w=" + resultX + " h=" + resultY);
                    resultIm = createImage(resultX,resultY);
                    resultG  = resultIm.getGraphics();
                    GS.con("Result image graphics created.");
                    resultArr = new Image[columnNumber][rowNumber];

                    spriteArr   = new int[spriteW*spriteH];
                    baseArr     = new int[baseW*baseH];
                    baseArrBuff = new int[baseW*baseH];
                    topArr      = new int[topW*topH];

                    //if( -1 != speRadius ) makeSpriteCircle();

                    spritePG  = new PixelGrabber(spriteIm, 0, 0, spriteW, spriteH, spriteArr, 0, spriteW);
                    basePG    = new PixelGrabber(baseIm, 0, 0, baseW,   baseH,   baseArr,   0, baseW  );

                    //For case if transparent color does not work for top:
                    topPG    = new PixelGrabber(topIm, 0, 0, topW,   topH,  topArr,   0, topW  );


                    /*
                    PixelGrabber(Image img,
                    int x,
                    int y,
                    int w,
                    int h,
                    int[] pix,
                    int off,
                    int scansize)
                    */

                    try {
                          spritePG.grabPixels();
                          basePG.grabPixels();
                          topPG.grabPixels();
                    } catch (InterruptedException e) {
                      GS.con("Faile grab pixels: " + e);
                      this.stop();
                    }
                    GS.con("Pixels grabbed successfully ... ");

                    this.stop();

                    calculateImage();
                    
                    //Debug:
                    //this.stop();
          }

       }
     } catch (InterruptedException e) {
             GS.con( "In method run" + e );
     }

   } //run()


   public void update( Graphics g ) {
     if( imageIsReady && resultIm != null ) {

       //Move result image to master image:
       g.drawImage( resultIm, 0,0, null );

       g.drawImage( spriteIm, resultX, 0, null );
       g.drawImage( baseWithSprite, resultX, baseH, null );

       int wi = frameNumber % animationFramesNumber;
       int animationI =  wi % columnNumber;
       int animationJ = (wi - animationI )/columnNumber;

       //Put animated image last:
       g.drawImage(resultArr[animationI][animationJ], 
                   resultX, 
                   baseH*2,
                   null );

       /* This works: (perhaps choppy for too many clips):
       g.drawImage( resultIm, //=Source 
                              //Target:
                    resultX, 
                    baseH*2,
                    resultX+baseW-1,
                    baseH*3      -1,
                              //Source:
                    animationI*baseW, 
                    animationJ*baseH, 
                    animationI*baseW+baseW-1,
                    animationJ*baseH+baseH-1,

                    null      //=ImageObserver
                  );

       */  

       //Debug: 
       //g.drawString( "Frame=" + frameNumber +
       //              "wi=" + wi + " i=" + animationI +
       //              " j=" + animationJ, 0, resultY/2 );
                     
       //Debug:
       //calculateThread.stop(); 

     } else {

       g.setColor( new Color(0) );
       g.drawString( "Image is not ready yet", 0,0 );

     }
     
     paint(g);
   }


   
   void calculateImage() {

        //Debug
        resultG.setColor(new Color( (255<<16) | (155<<8) | 255 ) );
        resultG.fillRect(0,0,resultX-1,resultY-1);
        resultG.setColor( new Color( 0, 0, 0 ) );
        resultG.drawString("Image Created", 3, 3 );

        for( int i=0; i<columnNumber; i++ ) {
             for( int j=0; j<rowNumber; j++ ) {
                  //Was bug?:
                  //resultG.drawImage( baseIm, i*baseW, j*baseH, null );

                  //if( 1 == i && 1 == j ) {
                     switch( speRadius ) {

                     case -1:
                      drawRotatedSprite( 
                           rotationCenterOnBaseX,
                           rotationCenterOnBaseY,
                           leftTopOfSpriteOnBaseX,
                           leftTopOfSpriteOnBaseY,
                           ICos.argMax4/3,
                           animationFramesNumber,
                           j*columnNumber + i
                         );
                         break;
                      default:
                         makeFullClipWithCircle( i, j );
                      }
                      
                      resultG.drawImage( baseWithSprite, i*baseW, j*baseH, null );
                      //topIm should have have transparent parts:
                      //resultG.drawImage( topIm, i*baseW, j*baseH, null );

                //}   
            }
        }

        //Debug for cos, sin: 
        //ICos.drawTest( resultG, resultY );

        //Add static images at the end:
        resultG.drawImage( noAnimationIm, 
                           (columnNumber-2)*baseW,
                           (rowNumber-1)*baseH, 
                            null );
       

        GS.con( "Mutiframe image created." );

        //Fill array to demonstrate animation in on screen:
        //Of course, one can assign baseWithSprite to 
        //array's element, but to verify resultIm, we prefer
        //to cut the piece from resultIm
        for( int i=0; i<columnNumber; i++ ) {
             for( int j=0; j<rowNumber; j++ ) {
                  //Loogs too clumsy. Is there any way to crop
                  //piece immediately from the picture-of-clips?: 
                  CropImageFilter cif = new CropImageFilter(
                        i*baseW, 
                        j*baseH, 
                        baseW,
                        baseH
                  );
                  FilteredImageSource fis = new FilteredImageSource(
                        resultIm.getSource(), cif );
                  resultArr[i][j] = createImage(fis);
             }
        }  
        GS.c("Array of result clips created.");                          

        imageIsReady = true;

   }

   public void drawRotatedSprite( int circleCenterX,
                                  int circleCenterY,
                                  int spritePosX,
                                  int spritePosY,
                                  int startAngle,
                                  int framesNumber,
                                  int currentFrame
                                ) {

               int argI = (ICos.argMax4*currentFrame)/framesNumber;
               if(argI>ICos.argMax4) argI = ICos.argMax4;
               int cosI = ICos.cosI[argI];
               int sinI = ICos.sinI[argI];
               int sinMax = ICos.sinMax;

               int spRelPosX =  spritePosX - circleCenterX;
               int spRelPosY = -(spritePosY- circleCenterY); //Normal, not screen coord.

               //GS.b("Copying to buffer..");
               for(int i=0; i<baseW; i++ ) {
                   //do1 use copyMem
                   for(int j=0; j<baseH; j++ ) {
                       int ix = j*baseW + i;
                       baseArrBuff[ix] = baseArr[ix];
                   }   
               }

                              
               //GS.m("rotating..");
               for(int i=0; i<spriteW; i++) {
                   int fullX = i + spRelPosX;
                   for(int j=0; j<spriteH; j++ ) {
                       int color = spriteArr[j*baseW + i];
                       if( (color & 0xFFFFFF ) != TRANSPARENT_COLOR ) {
                           int fullY = spRelPosY-j;
                           int xS = (fullX*cosI + fullY*sinI)/sinMax + circleCenterX;
                           int yS = circleCenterY -((-fullX*sinI) + fullY*cosI)/sinMax;
                           if( xS < baseW && xS > -1 && 
                               yS < baseH && yS > -1 ) {
                               int ix = yS*baseW + xS;
                               baseArrBuff[ix] = color;
                           }
                       }
                    }
               } 
               

               /*
               //Strangerly fails: 
               //GS.m("draw circle..");
               int speRadius = 19;
               for( int ang=0; ang<ICos.argMax4; ang+=ICos.argMax4/100 ) {
                    int color = 255 * ang / ICos.argMax4;
               if(argI>ICos.argMax4) argI = ICos.argMax4;
                    int ang2 = ( ang + argI ) % ICos.argMax4;
                    int xS =    speRadius * ICos.cosI[ang2] / ICos.sinMax   + circleCenterX;
                    int yS = - (speRadius * ICos.sinI[ang2] / ICos.sinMax) + circleCenterY;
                    if( xS < baseW && xS > -1 && yS < baseH && yS > -1 ) {
                        int ix = yS*baseW + xS;
                        baseArrBuff[ix] = color;
                    }
               } 
               */



               //GS.m("rotated.");
               GS.b( "topping.." );
                
               if( topIm != null && topArr != null ) {
                   GS.m("Put top clip with count of transparent color ..");
                   for(int j=0; j<topH; j++ ) {
                       int wj = j*topW;
                       for(int i=0; i<topW; i++) {
                           int wji = wj + i;
                           int color = topArr[wji];
                           if( (color & 0xFFFFFF ) != TRANSPARENT_COLOR ) {
                               baseArrBuff[wji] = color;
                           }
                        }
                   } 
               }
               GS.e("topped.");
           
               //this.stop();
               baseWithSprite= createImage( new MemoryImageSource( 
                                            baseW, baseH, baseArrBuff, 0, baseW));


               //GS.con("Created from arr to graph. ... ");

   } //drawRotatedSprite()
                                   


   void makeSpriteCircle() {
            spriteImNew = createImage( spriteW, spriteH );
            spriteGNew  = spriteImNew.getGraphics();
            drawSpriteCircle ( spriteGNew, 0, 0 );
   }

   void drawSpriteCircle( Graphics g, int i, int j ) {

        int currentFrame = i+j*columnNumber;

        int argI = (ICos.argMax4*currentFrame)/animationFramesNumber;
        if(argI>ICos.argMax4) argI = ICos.argMax4;


            g.setColor(new Color( 0 ) );
            g.fillRect(0,0,spriteW,spriteH);

            int angStep  = ICos.argMax4 / speDensity;
            if( angStep < 1 ) angStep = 1;

            int xSlast=0;
            int ySlast=0;
            for( int rWidth=0; rWidth<speRingWidth; rWidth++ ) {
               for( int ang=0; ang<ICos.argMax4; ang+=angStep ) {
                    int angEffective = ICos.sinI[( (ang+argI)*speFrequency ) % ICos.argMax4] + ICos.sinMax;
                    int cR = ( speAR * angEffective / ICos.sinMax2 + speColorMinR ) & 0xFF;
                    int cG = ( speAG * angEffective / ICos.sinMax2 + speColorMinG ) & 0xFF;
                    int cB = ( speAB * angEffective / ICos.sinMax2 + speColorMinB ) & 0xFF;
                    int color = ( cR<<16) | (cG<<8) | cB;
                    int r = speRadius - rWidth;
                    int xS =    r * ICos.cosI[ang] / ICos.sinMax   + rotationCenterOnBaseX;
                    int yS = - (r * ICos.sinI[ang] / ICos.sinMax)  + rotationCenterOnBaseY;
                    if( 0 == ang ) { xSlast = xS; ySlast = yS; }
                    if( xS < spriteW && xS > -1 && yS < spriteH && yS > -1 ) {
                        g.setColor( new Color(color) );
                        g.drawLine( xS,yS, xS,yS );
                    }
                    xSlast = xS;
                    ySlast = yS;
               } //for( int ang=0; 
            } // rWidth;
            spriteIm = spriteImNew;

   } //makeSpriteCircle()


   void makeFullClipWithCircle( int ii, int jj ) {

       //No need. Just put paint.
       //GS.b("Copying to buffer..");
       Image bf = createImage( baseW, baseH );
       Graphics g = bf.getGraphics();

       //do2 must move image memory, not grab it:
       for(int i=0; i<baseW; i++ ) {
           //do1 use copyMem
           for(int j=0; j<baseH; j++ ) {
               int ix = j*baseW + i;
               baseArrBuff[ix] = baseArr[ix];
           }   
       }
       Image bf2 = createImage( new MemoryImageSource( 
                                baseW, baseH, baseArrBuff, 0, baseW));
       g.drawImage( bf2, 0, 0, null );
       //Base is transferred.

       drawSpriteCircle(g, ii, jj);

       /* 
       int[] bfTopping = new int[baseW*baseH];
       GS.b( "topping.." );
       if( topIm != null && topArr != null ) {
           //GS.m("Put top clip with count of transparent color ..");
           for(int j=0; j<topH; j++ ) {
                       int wj = j*topW;
                       for(int i=0; i<topW; i++) {
                           int wji = wj + i;
                           int color = topArr[wji];
                           if( (color & 0xFFFFFF ) != TRANSPARENT_COLOR ) {
                               bfTopping[wji] = color;
                           } else {
                               //The rest is transparent:
                               bfTopping[wji] = color | 0x255<<24 ;
                           }
                       }
          } 
       } //if( topIm != null && 
       Image bf3 = createImage( new MemoryImageSource( 
                                baseW, baseH, bfTopping, 0, baseW));
       //Assert: transparent c. works:
       g.drawImage( bf3, 0, 0, null );
       */
       g.drawImage( topIm, 0, 0, null );

       GS.e("topped.");


   } //makeFullClipWithCircle()



} // class