package nxm.sys.libg;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.File;
import java.net.InetAddress;
import java.net.URI;

import javax.imageio.ImageIO;

import nxm.sys.inc.Constants;
import nxm.sys.inc.PlotFile;
import nxm.sys.lib.Args;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Shell;
import nxm.sys.lib.Table;

/**
  A class to hold and plot Google Map images.

  @author Jeff Schoen
*/
public class LayerGMap extends Layer implements ImageObserver {

  private BufferedImage[] mbis,tbis,ibis;
  private String[] murls,turls,iurls;

  private String imgServer, mapServer, localServer;
  private int mapAvailable, imgAvailable, localAvailable;
  private boolean mapUse, imgUse, tapUse, localUse;
  private int maxLocalLevel;

  /** list of image options/properties */
  public static final String optionsList = "Imagery,Charts,Overlay,Contrast";
  protected static final int O_IMAGERY=0x1, O_CHARTS=0x2, O_OVERLAY=0x4, O_CONTRAST=0x8;
  protected static final int O_DEFAULT = O_IMAGERY|O_OVERLAY;

  protected int options=O_DEFAULT, opaqueness=75, contrast=50;
  protected double maxComp=1.5, minComp=0.001;
  protected static int maxLevel=18;
  protected int jper,busy;

  /* Constructors */
  /** LayerGMap Default Constructor */
  public LayerGMap () {
    super();
    name = "GMAP";
    typeClass = 7;
    frame = 0;
    size = 0;
    imgServer = "kh1.google.com";
    mapServer = "mt1.google.com";
    localServer = "/midas/mapdata/gmap";
    localAvailable = checkServer(localServer);
  }

  /** setup the file input - if necessary */
  public boolean setFile (PlotFile pf, Table tab, Args MA) {
    process();
    return true;
  }

  /** process the plot data for this frame
      calls {@link #process(double, double, double, double)}
   */
  public void process() {
    // find viewable region intersected with gridable region
    double[] box = MP.getXYGrid( -180.0, +180.0, -90.0, +90.0);
    double gx1=box[0], gx2=box[1], gy1=box[2], gy2=box[3];
    process (gx1,gx2,gy1,gy2);
  }

  /** process the plot data for a given frame
      @param gx1 first longitude value
      @param gx2 second longitude value
      @param gy1 first latitude value
      @param gy2 second latitude value
   */
  public void process (double gx1, double gx2, double gy1, double gy2) {
    int level,div;
    double d, res = 1/MP.mxx; // degrees per pixel
    if (MP.view==MPlot.V_GEODETIC) res *= Constants.RAD2DEG;

    for (level=1,div=1; level<maxLevel; level++,div*=2) {
      double dpc = (360.0/div) / 256; // degrees per cell
      double ppc = dpc / res; // pixels per cell
      if (ppc < maxComp) break;
      if (imgAvailable<0 && level>=maxLocalLevel) break;
    }

    xstart = -180;
    xdelta = 360.0/div;
    d = Math.max(-180,gx1);
    int sx = Math.max(0,(int)((d-xstart)/xdelta+.01));
    xstart += xdelta*sx;
    d = Math.min(+180,gx2);
    frame  = (int)((d-xstart)/xdelta+.99);
    frame = Math.min(frame,div-sx);

    ystart = 180;
    ydelta = -360.0/div;
    d = Math.max(-180,ViewMercader.lat2mer(gy2));
    int sy = Math.max(0,(int)((d-ystart)/ydelta+.01));
    ystart += ydelta*sy;
    d = Math.min(+180,ViewMercader.lat2mer(gy1));
    size   = (int)((d-ystart)/ydelta+.99);
    size = Math.min(size,div-sy);

    jper = 256;
    if (MP.view != MPlot.V_MERCATOR && level<4) jper=32; // stretch in segments

//System.out.println("OK level="+level+" div="+div+" frame="+frame+" size="+size+" sx="+sx);
    imgUse = (options&O_IMAGERY)!=0;
    mapUse = (options&O_CHARTS )!=0 && (options&O_IMAGERY)==0;
    tapUse = (options&O_OVERLAY)!=0 && (options&O_CHARTS)==0;
    if (imgUse) loadTiles (O_IMAGERY,level,sx,sy,frame,size);
    if (mapUse) loadTiles (O_CHARTS ,level,sx,sy,frame,size);
    if (tapUse) loadTiles (O_OVERLAY,level,sx,sy,frame,size);
  }

  private int checkServer (String server) {
    int defstat = -1;
    if (server.equals(localServer)) {
      File f = new File(server);
      return f.exists()? 1 : -1;
    }
    try {
      Shell.info("Resolving "+server);
      InetAddress ia = InetAddress.getByName(server);
      Shell.info("Connecting to "+ia);
//JDK1.5 only         if (ia.isReachable(2000)) return 1;
      return 1;
    }
    catch (Exception e) {
      warning(e.getMessage()); // stack trace not relevant, usually connection error
    }
    warning("Problem connecting to "+server);
    return defstat;
  }

  private void loadTiles (int type, int level, int sx, int sy, int nx, int ny) {
    int x,y,z=0;
    String url=null,lurl=null;
    BufferedImage bi=null;
    String[] urls = null, u = new String[nx*ny];
    BufferedImage[] bis = null, b = new BufferedImage[nx*ny];
    if (type==O_IMAGERY) { urls=iurls; bis=ibis; }
    if (type==O_CHARTS ) { urls=murls; bis=mbis; }
    if (type==O_OVERLAY) { urls=turls; bis=tbis; }
    StringBuffer sb = new StringBuffer(32);
//System.out.println("GMap Loading type="+type+" tiles="+(nx*ny));
    for (y=0; y<ny; y++) {
      for (x=0; x<nx; u[z]=url,b[z]=bi,x++,z++) {
        busy = (z+1)*100/(nx*ny+1);
        bi = null;
        int ix = sx+x;
        int iy = sy+y;
        if (type==O_IMAGERY) {
          sb.setLength(0); sb.append('t');
          for (int i=2; i<=level; i++) {
            int mask = 0x1 << (level-i);
            boolean bx = (ix & mask) != 0;
            boolean by = (iy & mask) != 0;
            if (by) sb.append( (bx)? 's' : 't');
            else    sb.append( (bx)? 'r' : 'q');
          }
          url = "/kh?v=3&n=404&t="+sb;
          lurl = "/kh_"+sb+".jpg";
        }
        else if (type==O_CHARTS) {
          int iz = 18-level;
          url = "/mt?v=t2.2&n=404&x="+ix+"&y="+iy+"&zoom="+iz;
          lurl = "/mt_t2_x"+ix+"y"+iy+"zoom"+iz+".jpg";
        }
        else if (type==O_OVERLAY) {
          int iz = 18-level;
          url = "/mt?v=t2t.2&n=404&x="+ix+"&y="+iy+"&zoom="+iz;
          lurl = "/mt_t2t_x"+ix+"y"+iy+"zoom"+iz+".jpg";
        }
        if (urls!=null)			// check in cache
        for (int c=0; c<urls.length; c++) {
          if (url.equals(urls[c])) { bi = bis[c]; break; }
        }
        if (bi!=null) continue;		// found in cache
        String surl = null;
        if (localAvailable>0) { 	// check the local server
          surl = localServer+lurl;
	  File f = new File(surl);
          surl = f.exists()? "file:"+localServer+lurl : null;
        }
        if (surl!=null) {		// found on local server
          maxLocalLevel = Math.max(maxLocalLevel,level);
        }
        else if (type==O_IMAGERY) {
          if (imgAvailable==0) imgAvailable = checkServer(imgServer);
          if (imgAvailable<0) continue;
          surl = "http://"+imgServer+url;
        }
        else {
          if (mapAvailable==0) mapAvailable = checkServer(mapServer);
          if (mapAvailable<0) continue;
          surl = "http://"+mapServer+url;
        }
        Shell.info("Loading "+surl);
        try { bi = ImageIO.read((new URI(surl)).toURL()); }
        catch (Exception e) {
          warning("GMapErr: No data from Google: "+e); // stack trace not relevant
        }
      }
    }
    if (type==O_IMAGERY) { iurls=u; ibis=b; }
    if (type==O_CHARTS ) { murls=u; mbis=b; }
    if (type==O_OVERLAY) { turls=u; tbis=b; }
  }

  /* find layer bounds */
  @Override
  public void findRange() {
    x1 = -180;
    x2 = 180;
    y1 = -90;
    y2 = 90;
    z1 = 0;
    z2 = 1;
    // Set Minimum and Maximum actual auto-scale value (since 3.7.0 for NXM-2984)
    a1 = y1;
    a2 = y2;
  }

  public void findFullRange() { findFullRange(pf); }

  /** draw the layer on the specified plot */
  public void draw (int flag) {
    super.draw(flag);
    if (MP.xvy || MP.gc==null) return;
    // find viewable region intersected with gridable region
    double[] box = MP.getXYGrid( -180.0, +180.0, -90.0, +90.0);
    double gx1=box[0], gx2=box[1], gy1=box[2], gy2=box[3];
    process(gx1,gx2,gy1,gy2);
    for (int y=0; y<size; y++) {
     for (int x=0; x<frame; x++) {
      if (MP.cancel) break;
      int z = x+y*frame;
      int jx1=0, jx2=256, jy1=0, jy2=256;
      int ix1=MP.ix1, ix2=MP.ix2, iy1=MP.iy1, iy2=MP.iy2;
      double xs = xstart + x*xdelta;
      double xe = xstart + (x+1)*xdelta;
      double xd = (xe-xs)/jx2;
      double ys = ystart + y*ydelta;
      double ye = ystart + (y+1)*ydelta;
      double yd = (ye-ys)/jy2;
      if (MP.is2D) {
        if (xs>MP.rx2 || xs+jx2*xd<MP.rx1) continue;
        jx1 = Math.max(jx1,(int)((MP.rx1-xs)/xd+.49));
        jx2 = Math.min(jx2,(int)((MP.rx2-xs)/xd+1.51));
      } else {
//System.out.println("Drawing "+MP.view+" "+imgUse+" "+ibis[z]+" "+xs+" "+ys);
        if (imgUse && ibis[z]!=null) MP.drawImage (ibis[z],null,true, xs,xd,jx2,ys,yd,jy2);
        continue;
      }
      ys = ViewMercader.mer2lat(ys);
      ye = ViewMercader.mer2lat(ye);
      yd = (ye-ys)/jy2;
      ix1 = (int)(MP.mxx*(xs+jx1*xd)+MP.mxb);
      ix2 = (int)(MP.mxx*(xs+jx2*xd)+MP.mxb);
      for (int jy2x;jy1<jy2;jy1=jy2x) {
        jy2x = Math.min(jy1+jper,jy2);
        double ym1 = ViewMercader.mer2lat(ystart + y*ydelta + jy1*ydelta/256);
        double ym2 = ViewMercader.mer2lat(ystart + y*ydelta + jy2x*ydelta/256);
        if (MP.view == MPlot.V_MERCATOR) {
          iy1 = MP.viewMerc.ry2iy(ym1);
          iy2 = MP.viewMerc.ry2iy(ym2);
        } else {
          iy1 = (int)(MP.myy*(ym1)+MP.myb);
          iy2 = (int)(MP.myy*(ym2)+MP.myb);
        }
//System.out.println("Draw i="+ix1+" "+iy1+" "+ix2+" "+iy2+" j="+jx1+" "+jy1+" "+jx2+" "+jy2);
        if (imgUse && ibis[z]!=null) MP.gc.drawImage(ibis[z], ix1,iy1,ix2,iy2, jx1,jy1,jx2,jy2x, this);
        if (mapUse && mbis[z]!=null) MP.gc.drawImage(mbis[z], ix1,iy1,ix2,iy2, jx1,jy1,jx2,jy2x, this);
        if (tapUse && tbis[z]!=null) MP.gc.drawImage(tbis[z], ix1,iy1,ix2,iy2, jx1,jy1,jx2,jy2x, this);
      }
     }
    }
    busy = 0;
  }

  public boolean imageUpdate (Image img, int infoflags,
                        int x, int y, int width, int height) {
    return true;
  }

  /** set image options to be used, refresh layer if options change
      @param list list of image options to be used
   */
  public void setOptions (String list) {
    int oldOptions = options;
    options = Parser.mask(optionsList,list,options);
    if (options!=oldOptions) refresh();
  }
  /** get image options
      @return image options being used
   */
  public String getOptions() {
    return Parser.mask2s(optionsList,options);
  }

  /** set image contrast
      @param percent image contrast (in percentage form)
   */
  public void setContrast (int percent) {
    int oldContrast = contrast;
    contrast = Math.max(0,Math.min(100,percent));
    if (contrast!=oldContrast && (options&O_CONTRAST)!=0) refresh();
  }
  /** get image contrast
      @return image contrast (in percentage form)
   */
  public int getContrast() { return contrast; }

  /** Gets the value of the "busyness" (progress) indicator
      @return "busyness" indicator (0 to 100)
   */
  public int getBusy() { return busy; }

}
