/*--
  This file is a part of ZetaGrid, a simple and secure Grid Computing
  kernel.

  Copyright (c) 2001-2004 Sebastian Wedeniwski.  All rights reserved.

  Use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:

  1. The source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.

  2. The origin of this software must not be misrepresented; you must 
     not claim that you wrote the original software.  If you plan to
     use this software in a product, please contact the author.

  3. Altered source versions must be plainly marked as such, and must
     not be misrepresented as being the original software. The author
     must be informed about these changes.

  4. The name of the author may not be used to endorse or promote 
     products derived from this software without specific prior written 
     permission.

  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

  Version 1.9.0, February 8, 2004

  This program is based on the work of:
     S. Wedeniwski
--*/

package zeta.tool;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.ServletException;

import zeta.ZetaServlet;
import zeta.handler.statistic.AbstractHandler;
import zeta.util.DatabaseUtils;
import zeta.util.Properties;
import zeta.util.StreamUtils;
import zeta.util.ThrowableHandler;

public class CreateStatistics extends Thread {
  public static void main(String[] args) {
    if (args.length == 1) {
      if (args[0].equals("?")) {
        System.out.println("USAGE: [<classname> [<imagename>]]");
        return;
      }
      CreateStatistics csi = new CreateStatistics();
      csi.getPage(args[0]);
      return;
    } else if (args.length == 2) {
      CreateStatistics csi = new CreateStatistics();
      csi.getImage(args[0], args[1]);
      return;
    } else {
      CreateStatistics csi = new CreateStatistics();
      csi.start();
    }
  }

  public void run() {
    int error = 0;
    int wait = 0;
    do {
      error = 0;
      try {
        wait = generatePagesImages();
      } catch (SQLException se) {
        ++error;
        try {
          sleep(30000);
        } catch (InterruptedException ie) {
        }
      } catch (ServletException se) {
        ++error;
        try {
          sleep(30000);
        } catch (InterruptedException ie) {
        }
      }
    } while (error > 0 && error < 10);
    while (true) {
      try {
        sleep(wait);
      } catch (InterruptedException ie) {
      }
      error = 0;
      do {
        try {
          updatePages();
        } catch (SQLException se) {
          ++error;
          try {
            sleep(30000);
          } catch (InterruptedException ie) {
          }
        } catch (ServletException se) {
          ++error;
          try {
            sleep(30000);
          } catch (InterruptedException ie) {
          }
        }
      } while (error == 1);
      error = 0;
      do {
        try {
          updateImages();
        } catch (SQLException se) {
          ++error;
          try {
            sleep(30000);
          } catch (InterruptedException ie) {
          }
        } catch (ServletException se) {
          ++error;
          try {
            sleep(30000);
          } catch (InterruptedException ie) {
          }
        }
      } while (error == 1);
    }
  }

  public int generatePagesImages() throws SQLException, ServletException {
    Statement stmt = null;
    PreparedStatement pStmt = null;
    int wait = 0;
    try {
      Properties properties = new Properties();
      ZetaServlet servlet = new ZetaServlet();
      servlet.getInitParameter("server_id");    // init the server configuration ("hasSeparateFiles")
      servlet.addStatisticHandler("handler.get.producers 0 Top producers", "zeta.handler.statistic.TopProducersHandler", null);
      servlet.addStatisticHandler("handler.get.teammembers 1 Team members", "zeta.handler.statistic.TeamMembersHandler", null);
      servlet.setServerId(properties.get("server_id", 1));
      connection = GetData.getConnection();
      stmt = connection.createStatement();
      AbstractHandler previousStatistic = null;
      // Generate images
      ResultSet rs = stmt.executeQuery("SELECT classname,imagename FROM zeta.image");
      while (rs.next()) {
        String classname = rs.getString(1);
        String imagename = rs.getString(2);
        Class statisticClass = Class.forName(classname);
        Constructor[] statisticConstructor = statisticClass.getConstructors();
        AbstractHandler statistic = servlet.getStatisticHandler(statisticClass);
        if (statistic == null) {
          statistic = (AbstractHandler)statisticConstructor[0].newInstance(new Object[] { servlet });
        }
        if (wait == 0) {
          wait = statistic.getInnerPageExpiredDiffTime();
        } else {
          wait = gcd(wait, statistic.getInnerPageExpiredDiffTime());
        }
        byte[] buffer = null;
        long startTime = System.currentTimeMillis();
        try {
          buffer = generateImage(statistic, imagename);
        } catch (Exception e) {
          ThrowableHandler.handle(e);
        }
        long stopTime = System.currentTimeMillis();
        Timestamp lastUpdate = new Timestamp(startTime);
        if (buffer != null) {
          pStmt = connection.prepareStatement("UPDATE zeta.image SET (last_update,image)=('" + lastUpdate.toString()
                                              + "',?) WHERE classname='" + classname + "' AND imagename='" + imagename + '\'');
          pStmt.setBytes(1, buffer);
          pStmt.execute();
          DatabaseUtils.close(pStmt);
          pStmt = null;
          images.add(classname);
          images.add(imagename);
          images.add(statistic);
          String path = properties.get("ftp.statistic.path");
          if (path != null && path.length() > 0) {
            InputStream in = null;
            FTP ftp = null;
            try {
              ftp = new FTP();
              ftp.connect(properties.get("ftp.host", ""));
              ftp.login(properties.get("ftp.user", ""), properties.get("ftp.password", ""));
              ftp.cd(path);
              in = new ByteArrayInputStream(buffer);
              ftp.put(in, classname + '_' + imagename + ".png", FTP.MODE_BINARY);
            } catch (Exception e) {
              ThrowableHandler.handle(e);
            } finally {
              StreamUtils.close(in);
              in = null;
              if (ftp != null) {
                ftp.disconnect();
              }
            }
          }
          System.out.println(lastUpdate.toString() + ':' + classname + ' ' + imagename + " (" + ((stopTime-startTime)/1000) + "s)");
        } else {
          System.out.println(lastUpdate.toString() + ':' + classname + ' ' + imagename + " is EMPTY! (" + ((stopTime-startTime)/1000) + "s)");
        }
        statistic.imageUpdateCompleted(imagename);
      }
      // Generate pages
      rs = stmt.executeQuery("SELECT classname FROM zeta.page ORDER BY classname DESC");
      while (rs.next()) {
        String classname = rs.getString(1);
        Class statisticClass = Class.forName(classname);
        Constructor[] statisticConstructor = statisticClass.getConstructors();
        int idx = indexOf(statisticClass);
        AbstractHandler statistic = (idx >= 0)? (AbstractHandler)images.get(idx) : servlet.getStatisticHandler(statisticClass);
        if (statistic == null) {
          statistic = (AbstractHandler)statisticConstructor[0].newInstance(new Object[] { servlet });
        }
        long startTime = System.currentTimeMillis();
        String page = statistic.createPage(connection);
        long stopTime = System.currentTimeMillis();
        Timestamp lastUpdate = new Timestamp(startTime);
        ZipOutputStream zip = null;
        ByteArrayOutputStream out = null;
        InputStream in = null;
        try {
          in = new ByteArrayInputStream(page.getBytes());
          out = new ByteArrayOutputStream();
          zip = new ZipOutputStream(out);
          zip.setLevel(Deflater.BEST_COMPRESSION);
          zip.putNextEntry(new ZipEntry(classname + ".html"));
          StreamUtils.writeData(in, zip, true, true);
          byte[] buffer = out.toByteArray();
          in = null;
          zip = null;
          pStmt = connection.prepareStatement("UPDATE zeta.page SET (last_update,page)=('" + lastUpdate.toString()
                                              + "',?) WHERE classname='" + classname + '\'');
          pStmt.setBytes(1, buffer);
          pStmt.execute();
          DatabaseUtils.close(pStmt);
          pStmt = null;
          if (idx == -1) {
            images.add(classname);
            images.add(null);
            images.add(statistic);
          }
          String path = properties.get("ftp.statistic.path", "");
          if (path != null && path.length() > 0) {
            FTP ftp = null;
            try {
              ftp = new FTP();
              ftp.connect(properties.get("ftp.host", ""));
              ftp.login(properties.get("ftp.user", ""), properties.get("ftp.password", ""));
              ftp.cd(path);
              in = new ByteArrayInputStream(buffer);
              ftp.put(in, classname + ".zip", FTP.MODE_BINARY);
            } catch (Exception e) {
              ThrowableHandler.handle(e);
            } finally {
              StreamUtils.close(in);
              in = null;
              if (ftp != null) {
                ftp.disconnect();
              }
            }
          }
          System.out.println(lastUpdate.toString() + ':' + classname + " (" + ((stopTime-startTime)/1000) + "s)");
          statistic.pageUpdateCompleted();
        } catch (IOException ioe) {
          ThrowableHandler.handle(ioe);
        } finally {
          StreamUtils.close(in);
          StreamUtils.close(zip);
        }
      }
    } catch (SQLException se) {
      System.out.println(se.getMessage());
      throw se;
    } catch (ServletException se) {
      System.out.println(se.getMessage());
      throw se;
    } catch (Exception e) {
      ThrowableHandler.handle(e);
    } finally {
      DatabaseUtils.close(stmt);
      DatabaseUtils.close(pStmt);
      DatabaseUtils.close(connection);
      connection = null;
    }
    return (wait < 60000)? 60000 : wait;
  }

  public int indexOf(Class cl) {
    final int l = images.size();
    for (int i = 2; i < l; i += 3) {
      if (cl == images.get(i).getClass()) {
        return i;
      }
    }
    return -1;
  }

  public void updatePages() throws SQLException, ServletException {
    PreparedStatement pStmt = null;
    try {
      Properties properties = new Properties();
      connection = GetData.getConnection();
      final int l = images.size();
      Set statisticUpdated = new HashSet(l);
      for (int i = 2; i < l; i += 3) {
        AbstractHandler statistic = (AbstractHandler)images.get(i);
        if (!statisticUpdated.contains(statistic.getClass()) && statistic.isInnerPageExpired()) {
          statisticUpdated.add(statistic.getClass());
          ZipOutputStream zip = null;
          ByteArrayOutputStream out = null;
          InputStream in = null;
          try {
            String page = null;
            long startTime = System.currentTimeMillis();
            try {
              page = statistic.createPage(connection);
            } catch (NullPointerException npe) {
              throw new SQLException("Reset connection");
            }
            long stopTime = System.currentTimeMillis();
            Timestamp lastUpdate = new Timestamp(startTime);
            in = new ByteArrayInputStream(page.getBytes());
            out = new ByteArrayOutputStream();
            zip = new ZipOutputStream(out);
            zip.setLevel(Deflater.BEST_COMPRESSION);
            String classname = (String)images.get(i-2);
            zip.putNextEntry(new ZipEntry(classname + ".html"));
            StreamUtils.writeData(in, zip, true, true);
            byte[] buffer = out.toByteArray();
            in = null;
            zip = null;
            pStmt = connection.prepareStatement("UPDATE zeta.page SET (last_update,page)=('" + lastUpdate.toString() + "',?) WHERE classname='" + classname + '\'');
            pStmt.setBytes(1, buffer);
            pStmt.execute();
            DatabaseUtils.close(pStmt);
            pStmt = null;
            String path = properties.get("ftp.statistic.path");
            if (path != null && path.length() > 0) {
              FTP ftp = null;
              try {
                ftp = new FTP();
                ftp.connect(properties.get("ftp.host", ""));
                ftp.login(properties.get("ftp.user", ""), properties.get("ftp.password", ""));
                ftp.cd(path);
                in = new ByteArrayInputStream(buffer);
                ftp.put(in, classname + ".zip", FTP.MODE_BINARY);
              } catch (Exception e) {
                ThrowableHandler.handle(e);
                continue;
              } finally {
                StreamUtils.close(in);
                in = null;
                if (ftp != null) {
                  ftp.disconnect();
                }
              }
            }
            System.out.println(lastUpdate.toString() + ':' + classname + " (" + ((stopTime-startTime)/1000) + "s)");
            statistic.pageUpdateCompleted();
          } catch (IOException ioe) {
            ThrowableHandler.handle(ioe);
          } finally {
            StreamUtils.close(in);
            StreamUtils.close(zip);
          }
        }
      }
    } catch (SQLException se) {
      System.out.println(se.getMessage());
      throw se;
    } catch (ServletException se) {
      System.out.println(se.getMessage());
      throw se;
    } catch (Exception e) {
      ThrowableHandler.handle(e);
    } finally {
      DatabaseUtils.close(pStmt);
      DatabaseUtils.close(connection);
      connection = null;
    }
  }

  public void updateImages() throws SQLException, ServletException {
    PreparedStatement pStmt = null;
    try {
      Properties properties = new Properties();
      connection = GetData.getConnection();
      final int l = images.size();
      for (int i = 2; i < l; i += 3) {
        AbstractHandler statistic = (AbstractHandler)images.get(i);
        String imagename = (String)images.get(i-1);
        if (imagename != null && statistic.isImageExpired(imagename)) {
          String classname = (String)images.get(i-2);
          long startTime = System.currentTimeMillis();
          byte[] buffer = generateImage(statistic, imagename);
          long stopTime = System.currentTimeMillis();
          Timestamp lastUpdate = new Timestamp(startTime);
          pStmt = connection.prepareStatement("UPDATE zeta.image SET (last_update,image)=('" + lastUpdate.toString()
                                              + "',?) WHERE classname='" + classname + "' AND imagename='" + imagename + '\'');
          pStmt.setBytes(1, buffer);
          pStmt.execute();
          DatabaseUtils.close(pStmt);
          pStmt = null;
          String path = properties.get("ftp.statistic.path", "");
          if (path != null && path.length() > 0) {
            InputStream in = null;
            FTP ftp = null;
            try {
              ftp = new FTP();
              ftp.connect(properties.get("ftp.host", ""));
              ftp.login(properties.get("ftp.user", ""), properties.get("ftp.password", ""));
              ftp.cd(path);
              in = new ByteArrayInputStream(buffer);
              ftp.put(in, classname + '_' + imagename + ".png", FTP.MODE_BINARY);
            } catch (Exception e) {
              ThrowableHandler.handle(e);
              continue;
            } finally {
              StreamUtils.close(in);
              in = null;
              if (ftp != null) {
                ftp.disconnect();
              }
            }
          }
          System.out.println(lastUpdate.toString() + ':' + classname + ' ' + imagename + " (" + ((stopTime-startTime)/1000) + "s)");
          statistic.imageUpdateCompleted(imagename);
        }
      }
    } catch (SQLException se) {
      System.out.println(se.getMessage());
      throw se;
    } catch (ServletException se) {
      System.out.println(se.getMessage());
      throw se;
    } catch (Exception e) {
      ThrowableHandler.handle(e);
    } finally {
      DatabaseUtils.close(pStmt);
      DatabaseUtils.close(connection);
      connection = null;
    }
  }

  public byte[] generateImage(AbstractHandler statistic, String imageName) throws IOException, SQLException, ServletException, com.sun.jimi.core.JimiException {
    BufferedImage image = null;
    try {
      image = statistic.createImage(connection, imageName);
    } catch (NullPointerException npe) {
ThrowableHandler.handle(npe);
      throw new SQLException("Reset connection");
    }
    if (image == null) {
      return null;
    }
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    com.sun.jimi.core.encoder.png.PNGEncoder encoder = new com.sun.jimi.core.encoder.png.PNGEncoder();
    encoder.encodeImage(com.sun.jimi.core.Jimi.createRasterImage(image.getSource()), stream);
    stream.close();
    return stream.toByteArray();
  }

  public void getImage(String classname, String imageName) {
    Statement stmt = null;
    try {
      connection = GetData.getConnection();
      stmt = connection.createStatement();
      ResultSet rs = stmt.executeQuery("SELECT image FROM zeta.image WHERE classname='" + classname + "' AND imagename='" + imageName + '\'');
      if (rs.next()) {
        StreamUtils.writeData(rs.getBinaryStream(1), new FileOutputStream(imageName + ".png"), false, true);
      } else {
        System.err.println("classname='" + classname + "' AND imagename='" + imageName + "' not found!");
      }
      rs.close();
    } catch (Exception e) {
      ThrowableHandler.handle(e);
    } finally {
      DatabaseUtils.close(connection);
      connection = null;
    }
  }

  public void getPage(String classname) {
    Statement stmt = null;
    try {
      connection = GetData.getConnection();
      stmt = connection.createStatement();
      ResultSet rs = stmt.executeQuery("SELECT page FROM zeta.page WHERE classname='" + classname + '\'');
      if (rs.next()) {
        StreamUtils.writeData(rs.getBinaryStream(1), new FileOutputStream(classname + ".zip"), false, true);
      } else {
        System.err.println("classname='" + classname + "' not found!");
      }
      rs.close();
    } catch (Exception e) {
      ThrowableHandler.handle(e);
    } finally {
      DatabaseUtils.close(connection);
      connection = null;
    }
  }

  public static int gcd(int a, int b) {
    if (a == 0) return b;
    if (a < 0) a = -a;
    if (b == 0) return a;
    if (b < 0) b = -b;
  
    int i,j;
    for (i = 0; (a&1) == 0; ++i) a >>= 1;
    for (j = 0; (b&1) == 0; ++j) b >>= 1;
    while (a != b)
      if (a > b) {
        a -= b;
        do a >>= 1; while ((a&1) == 0);
      } else {
        b -= a;
        do b >>= 1; while ((b&1) == 0);
      }
    return a << ((i > j)? j : i);
  }

  private Connection connection = null;
  private List images = new ArrayList(300);
}