/*
 * LifetimeTest class.
 *
 * Copyright (C) 2007 SATOH Takayuki All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package ts.util.lifetime;

import ts.tester.function.coverage.FunctionTester;
import ts.tester.function.*;
import ts.tester.function.print.*;
import ts.util.*;
import java.util.*;
import java.lang.reflect.Method;

/**
 * {@link ts.util.lifetime.Lifetime Lifetime}クラスの機能試験クラス。
 *
 * @author  佐藤隆之.
 * @version $Revision: 1.2 $, $Date: 2011-06-06 14:19:08 $
 */
public class LifetimeTest extends FunctionTester
{
  public static void main(String[] args)
  {
    try {
      PrinterGroup group = new PrinterGroup();
      group.addPrinter(new ConsolePrinter());
      group.addPrinter(new HtmlPrinter("SATOH Takayuki"));
      setPrinter(group);

      run(LifetimeTest.class, (args.length == 0) ? null : args[0]);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  protected void preTesting()
  {
    MSG("Lifetime クラスの機能試験を実行する。");
  }

  /* -- inner classes -- */

  StringBuilder logbuf_ = null;

  void log(String msg) {
    logbuf_.append("\n" + msg);
    System.out.println(">> " + msg);
  }

  class LifeA implements Life {
    public void wasBorn() { log(getClass().getSimpleName() + " was born!"); }
    public void willDie() { log(getClass().getSimpleName() + " will die!"); }
  }
  class LifeB extends LifeA {}
  class LifeC extends LifeA {}

  class PEvLifeD implements PeriodicEventfulLife {
    long interval_ = 0L;
    public PEvLifeD(long interval) { interval_ = interval; }
    public void wasBorn() { log(getClass().getSimpleName() + " was born!"); }
    public void willDie() { log(getClass().getSimpleName() + " will die!"); }
    public void happens(){log(getClass().getSimpleName()+"'s event happens!");}
    public long getInterval() { return interval_; }
  }
  class PEvLifeE extends PEvLifeD { PEvLifeE(long v) { super(v); } }
  class PEvLifeF extends PEvLifeD { PEvLifeF(long v) { super(v); } }
  class PEvLifeG extends PEvLifeD { PEvLifeG(long v) { super(v); } }

  List<DateTime> dttmLst_ ;
  class SEvLifeH implements ScheduledEventfulLife {
    int sec_ ;
    public SEvLifeH(int sec) { sec_ = sec; }
    public void wasBorn() { 
      DateTime dttm = new DateTime();
      log(getClass().getSimpleName() + " was born! -- " + dttm);
      dttmLst_.add(dttm);
    }
    public void willDie() {
      DateTime dttm = new DateTime();
      log(getClass().getSimpleName() + " will die! --  " + dttm);
      dttmLst_.add(dttm);
    }
    public void happens() {
      DateTime dttm = new DateTime();
      log(getClass().getSimpleName() + "'s event happens! -- " + dttm);
      dttmLst_.add(dttm);
    }
    public Date getNextTime() {
      DateTime dttm = new DateTime();
      int y = dttm.getYear();
      int m = dttm.getMonth();
      int d = dttm.getDay();
      int hh = dttm.getHour();
      int mm = dttm.getMinute();
      int ss = dttm.getSecond();
      dttm.setDateTime(y, m, d, hh, mm, sec_);
      if (ss >= sec_) {
        dttm.addMinute(1);
      }
      return dttm.getDate();
    }
  }
  class SEvLifeI extends SEvLifeH { SEvLifeI(int v) { super(v); } }
  class SEvLifeJ extends SEvLifeH { SEvLifeJ(int v) { super(v); } }
  class SEvLifeK extends SEvLifeH { SEvLifeK(int v) { super(v); } }

  class OneTimeEventLife implements ScheduledEventfulLife {
    Date nexttime_ ;
    public OneTimeEventLife(int sec) {
      DateTime dttm = new DateTime();
      int y = dttm.getYear();
      int m = dttm.getMonth();
      int d = dttm.getDay();
      int hh = dttm.getHour();
      int mm = dttm.getMinute();
      int ss = dttm.getSecond();
      dttm.setDateTime(y, m, d, hh, mm, sec);
      if (ss >= sec) {
        dttm.addMinute(1);
      }
      nexttime_ = dttm.getDate();
    }
    public void wasBorn() { 
      DateTime dttm = new DateTime();
      log(getClass().getSimpleName() + " was born! -- " + dttm);
      dttmLst_.add(dttm);
    }
    public void willDie() {
      DateTime dttm = new DateTime();
      log(getClass().getSimpleName() + " will die! --  " + dttm);
      dttmLst_.add(dttm);
    }
    public void happens() {
      DateTime dttm = new DateTime();
      log(getClass().getSimpleName() + "'s event happens! -- " + dttm);
      dttmLst_.add(dttm);
    }
    public Date getNextTime() {
      return nexttime_;
    }
  }

  ObjectInspector oi_ = new ObjectInspector(this);

  protected void preInvocation(String method)
  {
    oi_.expect("lifeLst_", new ArrayList<Life>(0));
    oi_.ignore("lifeTimer_");

    logbuf_ = new StringBuilder();
    dttmLst_ = new LinkedList<DateTime>();
  }

  /* -- test case -- */

  public void constructor()
  {
    MSG("デフォルト・コンストラクタの確認。");

    oi_.inspect(new Lifetime());
  }

  public void add_and_kill()
  {
    MSG("Lifeの追加と破棄。");

    List<Life> lst = new ArrayList<Life>();

    Lifetime lifetime = new Lifetime();
    oi_.expect("lifeLst_", lst);
    oi_.inspect(lifetime);

    Life lifeA = new LifeA();
    lifetime.add(lifeA);
    lst.add(lifeA);
    oi_.inspect(lifetime);

    Life lifeB = new LifeB();
    lifetime.add(lifeB);
    lst.add(lifeB);
    oi_.inspect(lifetime);

    lifetime.kill();

    MATCH(logbuf_.toString(),
      "\nLifeA was born!" +
      "\nLifeB was born!" +
      "\nLifeA will die!" +
      "\nLifeB will die!" +
    "");
  }

  public void add_and_kill_1()
  {
    MSG("引数がヌルの場合。");
    Lifetime lifetime = new Lifetime();
    try {
      lifetime.add(null);
      NG();
    } catch (AssertionError e) {
      OK(e);
    } catch (Exception e) {
      NG(e);
    }
  }

  public void periodicEventfulLife()
  {
    MSG("周期的なイベントを持ったLife");

    PeriodicEventfulLife pelf = new PEvLifeD(5000L);
    Lifetime lifetime = new Lifetime();
    lifetime.add(pelf);

    try {
      Thread.sleep(5100L);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();

    MATCH(logbuf_.toString(),
      "\nPEvLifeD was born!" +
      "\nPEvLifeD's event happens!" +
      "\nPEvLifeD's event happens!" +
      "\nPEvLifeD will die!" +
    "");
  }

  public void periodicEventfulLife_1()
  {
    MSG("複数のPeriodicEventfulLifeを同時に実行。");

    Lifetime lifetime = new Lifetime();
    try {
      lifetime.add(new PEvLifeD(2000L));
      Thread.sleep(100L);
      lifetime.add(new PEvLifeE(10000L));
      Thread.sleep(100L);
      lifetime.add(new PEvLifeF(5000L));
      Thread.sleep(100L);
      lifetime.add(new PEvLifeG(3000L));
    } catch (Exception e) {
      NG(e);
    }

    try {
      Thread.sleep(20100L);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();

    MATCH(logbuf_.toString(),
      "\nPEvLifeD was born!" +
      "\nPEvLifeD's event happens!" +
      "\nPEvLifeE was born!" +
      "\nPEvLifeE's event happens!" +
      "\nPEvLifeF was born!" +
      "\nPEvLifeF's event happens!" +
      "\nPEvLifeG was born!" +
      "\nPEvLifeG's event happens!" +  // D.... E.... F.... G....
      "\nPEvLifeD's event happens!" +  //  2000
      "\nPEvLifeG's event happens!" +  //                    3300
      "\nPEvLifeD's event happens!" +  //  4000
      "\nPEvLifeF's event happens!" +  //              5200
      "\nPEvLifeD's event happens!" +  //  6000
      "\nPEvLifeG's event happens!" +  //                    6300
      "\nPEvLifeD's event happens!" +  //  8000
      "\nPEvLifeG's event happens!" +  //                    9300
      "\nPEvLifeD's event happens!" +  // 10000
      "\nPEvLifeE's event happens!" +  //       11000
      "\nPEvLifeF's event happens!" +  //             10200
      "\nPEvLifeD's event happens!" +  // 12000
      "\nPEvLifeG's event happens!" +  //                   12300
      "\nPEvLifeD's event happens!" +  // 14000
      "\nPEvLifeF's event happens!" +  //             15200
      "\nPEvLifeG's event happens!" +  //                   15300
      "\nPEvLifeD's event happens!" +  // 16000
      "\nPEvLifeD's event happens!" +  // 18000
      "\nPEvLifeG's event happens!" +  //                   18300
      "\nPEvLifeD's event happens!" +  // 20000
      "\nPEvLifeE's event happens!" +  //       21000
      "\nPEvLifeF's event happens!" +  //             20200
      "\nPEvLifeD will die!" +
      "\nPEvLifeE will die!" +
      "\nPEvLifeF will die!" +
      "\nPEvLifeG will die!" +
    "");
  }

  public void scheduledEventfulLife()
  {
    MSG("時刻指定のイベントを持ったLife（毎分20秒に起動）");

    ScheduledEventfulLife life = new SEvLifeH (20);
    Lifetime lifetime = new Lifetime();
    lifetime.add(life);

    try {
      Thread.sleep(60000 * 3);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();

    dttmLst_.remove(dttmLst_.size() - 1);
    dttmLst_.remove(0);
    for (DateTime dttm : dttmLst_) {
      EQUAL(dttm.getSecond(), 20);
    }
  }

  public void scheduledEventfulLife_1()
  {
    MSG("複数のScheduledEventfulLifeが同時に実行。");

    ScheduledEventfulLife life0 = new SEvLifeH (20);
    ScheduledEventfulLife life1 = new SEvLifeI (50);
    ScheduledEventfulLife life2 = new SEvLifeJ (15);
    ScheduledEventfulLife life3 = new SEvLifeK (35);
    Lifetime lifetime = new Lifetime();
    lifetime.add(life0);
    lifetime.add(life1);
    lifetime.add(life2);
    lifetime.add(life3);

    try {
      Thread.sleep(60000 * 3);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();
    OK();
  }

  public void oneTimeEvent()
  {
    MSG("一回だけのイベントを持ったLife (次の45秒に起動)");

    ScheduledEventfulLife life = new OneTimeEventLife(45);
    Lifetime lifetime = new Lifetime();
    lifetime.add(life);

    try {
      Thread.sleep(60000 * 3);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();

    dttmLst_.remove(dttmLst_.size() - 1);
    dttmLst_.remove(0);
    for (DateTime dttm : dttmLst_) {
      EQUAL(dttm.getSecond(), 45);
    }
  }

  public void causeErrorInLifetime()
  {
    MSG("Lifeオブジェクトが例外をスローする場合。");

    LifeA lifeA = new LifeA() {
      @Override public void willDie() {
        super.wasBorn();
        throw new RuntimeException("エラー");
      }
    };
    Lifetime lifetime = new Lifetime();

    lifetime.add(lifeA);

    try {
      Thread.sleep(1000);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();
    OK();
  }

  public void causeErrorInLifetime_wasBorn()
  {
    MSG("wasBornで例外が発生した場合。");

    final LifeA lifeA = new LifeA() {
      @Override public void wasBorn() {
        super.wasBorn();
        throw new RuntimeException("エラー");
      }
    };
    Lifetime lifetime = new Lifetime() {
      @Override protected void catchException(Exception e, Life lf, Method m) {
        EQUAL(e.getMessage(), "エラー");
        EQUAL(lf, lifeA);
        EQUAL(m.getName(), "wasBorn");
      }
    };

    lifetime.add(lifeA);

    try {
      Thread.sleep(1000);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();
  }

  public void causeErrorInLifetime_willDie()
  {
    MSG("willDieで例外が発生した場合。");

    final LifeA lifeA = new LifeA() {
      @Override public void willDie() {
        super.willDie();
        throw new RuntimeException("エラー");
      }
    };
    Lifetime lifetime = new Lifetime() {
      @Override protected void catchException(Exception e, Life lf, Method m) {
        EQUAL(e.getMessage(), "エラー");
        EQUAL(lf, lifeA);
        EQUAL(m.getName(), "willDie");
      }
    };

    lifetime.add(lifeA);

    try {
      Thread.sleep(1000);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();
  }

  public void causeErrorInPeriodicEventfulLife()
  {
    MSG("周期的に呼ばれるLife#happensでエラーがスローされた場合。");

    final PeriodicEventfulLife pelf = new PEvLifeD(1000L) {
      private int counter = 0;
      public void happens() {
        super.happens();
        counter ++;
        if (counter >= 5) throw new RuntimeException("エラー。");
      }
    };
    Lifetime lifetime = new Lifetime() {
      public void catchException(Exception e, Life lf, Method m) {
        EQUAL(e.getMessage(), "エラー。");
        EQUAL(lf, pelf);
        EQUAL(m.getName(), "happens");
      }
    };
    lifetime.add(pelf);

    try {
      Thread.sleep(10100L);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();
    OK();
  }

  public void causeErrorInScheduledEventfulLife()
  {
    MSG("時刻指定で呼ばれるLife#happensでエラーがスローされた場合。");

    final ScheduledEventfulLife life = new SEvLifeH (0) {
      private int counter = 0;
      public void happens() {
        super.happens();
        counter ++;
        if (counter >= 5) throw new RuntimeException("エラー。");
      }
      public Date getNextTime() {
        DateTime dttm = new DateTime();
        dttm.addSecond(1);
        return dttm.getDate();
      }
    };
    Lifetime lifetime = new Lifetime() {
      public void catchException(Exception e, Life lf, Method m) {
        EQUAL(e.getMessage(), "エラー。");
        EQUAL(lf, life);
        EQUAL(m.getName(), "happens");
      }
    };
    lifetime.add(life);

    try {
      Thread.sleep(10000);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();
    OK();
  }

  public void getLifeMethod()
  {
    MSG("Lifeオブジェクトのメソッドを取得");

    Lifetime lifetime = new Lifetime();
    Class[] types = { Life.class, String.class };
    Object[] values = new Object[2];

    values[0] = new LifeA();
    values[1] = "wasBorn";

    ObjectInspector oi = new ObjectInspector(this);
    try {
      Method m = (Method)
        oi.invokeMethod(lifetime, "getLifeMethod", types, values);
      EQUAL(m.getName(), "wasBorn");
      EQUAL(m.getDeclaringClass(), LifeA.class);
    } catch (Exception e) {
      NG(e);
    }
  }

  public void getLifeMethod_1()
  {
    MSG("引数がヌルの場合。");

    Lifetime lifetime = new Lifetime();
    Class[] types = { Life.class, String.class };
    Object[] values = new Object[2];

    values[0] = null;
    values[1] = "wasBorn";

    ObjectInspector oi = new ObjectInspector(this);
    try {
      NULL(oi.invokeMethod(lifetime, "getLifeMethod", types, values));
    } catch (Exception e) {
      NG(e);
    }

    values[0] = new LifeA();
    values[1] = null;

    try {
      NULL(oi.invokeMethod(lifetime, "getLifeMethod", types, values));
    } catch (Exception e) {
      NG(e);
    }
  }

  public void getLifeMethod_2()
  {
    MSG("存在しないメソッド名を指定した場合。");

    Lifetime lifetime = new Lifetime();
    Class[] types = { Life.class, String.class };
    Object[] values = new Object[2];

    values[0] = new LifeA();
    values[1] = "wwww";

    ObjectInspector oi = new ObjectInspector(this);
    try {
      NULL(oi.invokeMethod(lifetime, "getLifeMethod", types, values));
    } catch (Exception e) {
      NG(e);
    }
  }

  public void enumeration()
  {
    MSG("Lifeの列挙");

    Lifetime lifetime = new Lifetime();
    FALSE(lifetime.enumeration().hasMoreElements());

    Life la = new LifeA();
    lifetime.add(la);
    Enumeration<Life> e = lifetime.enumeration();
    TRUE(e.hasMoreElements());
    EQUAL(e.nextElement(), la); 
    FALSE(e.hasMoreElements());

    Life lb = new LifeB();
    lifetime.add(lb);
    e = lifetime.enumeration();
    TRUE(e.hasMoreElements());
    EQUAL(e.nextElement(), la); 
    TRUE(e.hasMoreElements());
    EQUAL(e.nextElement(), lb); 
    FALSE(e.hasMoreElements());

    lifetime.kill();

    e = lifetime.enumeration();
    FALSE(e.hasMoreElements());
  }

  public void enumeration_1()
  {
    MSG("Life.willDieメソッドでLifeを列挙");

    final Lifetime lifetime = new Lifetime();
    final LifeA lifeA = new LifeA();
    final LifeB lifeB = new LifeB();
    final LifeC lifeC = new LifeC() {
      public void willDie() {
        Enumeration e = lifetime.enumeration();
        TRUE(e.hasMoreElements());
        EQUAL(e.nextElement(), lifeA);
        TRUE(e.hasMoreElements());
        EQUAL(e.nextElement(), lifeB);
        TRUE(e.hasMoreElements());
        EQUAL(e.nextElement(), this);
        FALSE(e.hasMoreElements());
      }
    };

    lifetime.add(lifeA);
    lifetime.add(lifeB);
    lifetime.add(lifeC);

    try {
      Thread.sleep(1000);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();
  }


  public void enumeration_2()
  {
    MSG("Life.willDieメソッドで例外発生したときにLifeを列挙");

    final LifeA lifeA = new LifeA();
    final LifeB lifeB = new LifeB();
    final LifeC lifeC = new LifeC() {
      @Override public void willDie() {
        throw new RuntimeException("エラー");
      }
    };
    final Lifetime lifetime = new Lifetime() {
      protected void catchException(Exception exc, Life lf, Method m) {
        Enumeration e = enumeration();
        TRUE(e.hasMoreElements());
        EQUAL(e.nextElement(), lifeA);
        TRUE(e.hasMoreElements());
        EQUAL(e.nextElement(), lifeB);
        TRUE(e.hasMoreElements());
        EQUAL(e.nextElement(), lifeC);
        FALSE(e.hasMoreElements());
      }
    };

    lifetime.add(lifeA);
    lifetime.add(lifeB);
    lifetime.add(lifeC);

    try {
      Thread.sleep(1000);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();
  }

  public void whenGetIntervalOfLifeCauseErrorInSetupPeriodicEvent()
  {
    PEvLifeD life = new PEvLifeD(1000) {
      @Override public long getInterval() {
        throw new RuntimeException("エラー");
      }
    };
    Lifetime lifetime = new Lifetime();
    try {
      lifetime.add(life);
      NG();
    } catch (Exception e) {
      EQUAL(e.getMessage(), "エラー");
    }

    try {
      Thread.sleep(5000);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();
  }

  public void whenGetNextTimeOfLifeCauseErrorInSetupScheduledEvent()
  {
    SEvLifeH life = new SEvLifeH(20) {
      int counter = 0;
      @Override public Date getNextTime() {
        Date d = super.getNextTime();
        counter ++;
        if (counter >= 2 && counter <= 5) {
          throw new RuntimeException("エラー");
        }
        return d;
      }
    };
    Lifetime lifetime = new Lifetime() {
      protected void catchException(Exception exc, Life lf, Method m) {
        EQUAL(exc.getMessage(), "エラー");
      }
    };
    lifetime.add(life);

    try {
      Thread.sleep(3 * 60000);
    } catch (Exception e) {
      NG(e);
    }

    lifetime.kill();
    OK();
  }

  public void add_and_kill_2()
  {
    MSG("killした後に再度addした場合");

    Lifetime lifetime = new Lifetime();

    LifeA lifeA = new LifeA();
    PEvLifeD lifeD = new PEvLifeD(300L);

    lifetime.add(lifeA);
    lifetime.add(lifeD);

    try {
      Thread.sleep(1000L);
    } catch (Exception e) {}

    lifetime.kill();

    lifetime.add(lifeA);
    lifetime.add(lifeD);

    try {
      Thread.sleep(1000L);
    } catch (Exception e) {}

    lifetime.kill();

    OK();
  }
}

