/***********************************************************
 Copyright (C) 2004 VeriSign, Inc.

 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., 59 Temple Place, Suite 330, Boston, MA 02111-0107  USA

 http://www.verisign.com/nds/naming/namestore/techdocs.html
 ***********************************************************/
package com.verisign.epp.codec.change.util;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import com.verisign.epp.codec.change.EPPChangeCheckCmd;
import com.verisign.epp.codec.change.EPPChangeCheckResp;
import com.verisign.epp.codec.change.EPPChangeCreateCmd;
import com.verisign.epp.codec.change.EPPChangeDeleteCmd;
import com.verisign.epp.codec.change.EPPChangeInfoCmd;
import com.verisign.epp.codec.change.EPPChangeInfoResp;
import com.verisign.epp.codec.change.EPPChangeUpdateCmd;
import com.verisign.epp.codec.change.EPPChangeUpdateResp;
import com.verisign.epp.codec.change.type.EPPChangeAction;
import com.verisign.epp.codec.change.type.EPPChangeCheckData;
import com.verisign.epp.codec.change.type.EPPChangeUpdateAttributes;
import com.verisign.epp.codec.change.util.random.LetterDigit;
import com.verisign.epp.codec.change.util.random.LetterDigitHyphen;
import com.verisign.epp.codec.change.util.random.PrintableCharacters;
import com.verisign.epp.codec.change.util.random.PrintableCharactersWhitespace;
import com.verisign.epp.codec.change.util.random.RandomizableCharacter;
import com.verisign.epp.codec.gen.EPPResponse;
import com.verisign.epp.codec.gen.EPPTransId;

/**
 * This class knows how to construct a random command or response facilitating
 * the random unit test framework.
 * 
 * @author jcolosi
 */
public class RandomHelper {

  static private class RandomizationScheme {
    private int min;
    private int max;
    private RandomizableCharacter rand;

    public RandomizationScheme(int min, int max, RandomizableCharacter rand) {
      this.min = min;
      this.max = max;
      this.rand = rand;
    }

    public int getMax() {
      return max;
    }

    public int getMin() {
      return min;
    }

    public RandomizableCharacter getRand() {
      return rand;
    }

    public void setMax(int max) {
      this.max = max;
    }

    public void setMin(int min) {
      this.min = min;
    }

    public void setRand(RandomizableCharacter rand) {
      this.rand = rand;
    }

  }

  static private final boolean SIMPLE = false;

  static private final String DEFAULT_CLIENTTRANSID = "tk421-CLI";
  static private final String DEFAULT_SERVERTRANSID = "SRV-thx1138";

  static private Random random;
  static private RandomizableCharacter ldh;
  static private RandomizableCharacter ld;
  static private RandomizableCharacter pc;
  static private RandomizableCharacter pcw;

  static private final RandomizationScheme tridScheme;
  static private final RandomizationScheme priScheme;
  static private final RandomizationScheme defScheme;
  static private final RandomizationScheme descScheme;
  static private final RandomizationScheme statusScheme;
  static private final RandomizationScheme ilabelScheme;

  static {
    init();
    if (SIMPLE) {
      RandomizationScheme simpleScheme = new RandomizationScheme(3, 3, ld);
      tridScheme = simpleScheme;
      priScheme = simpleScheme;
      defScheme = simpleScheme;
      descScheme = simpleScheme;
      statusScheme = simpleScheme;
      ilabelScheme = simpleScheme;
    }
    else {
      tridScheme = new RandomizationScheme(3, 64, pcw);
      priScheme = new RandomizationScheme(4, 20, pcw);
      defScheme = new RandomizationScheme(1, 512, pcw);
      descScheme = new RandomizationScheme(1, 256, pcw);
      statusScheme = new RandomizationScheme(4, 32, pcw);
      ilabelScheme = new RandomizationScheme(0, 61, ldh);
    }
  }

  static public EPPChangeAction getChangeAction() {
    EPPChangeAction out = new EPPChangeAction();
    if (p(.9))
      out.setRequestId(getTransId());
    if (p(.9))
      out.setClientTransId(getTransId());
    out.setServerTransId(getTransId());
    if (p(.9))
      out.setDefinition(getDefinition());
    if (p(.9))
      out.setCreatedDate(getDate());
    if (p(.9))
      out.setUpdatedDate(getDate());
    return out;
  }

  static public EPPChangeCheckCmd getChangeCheckCmd() {
    EPPChangeCheckCmd cmd = null;
    if (p(.5)) {
      cmd = new EPPChangeCheckCmd(DEFAULT_CLIENTTRANSID);
      do
        cmd.addRequestId(getTransId());
      while (p(.5));
    }
    else {
      cmd = new EPPChangeCheckCmd(DEFAULT_CLIENTTRANSID, getTransId());
    }
    return cmd;
  }

  static public EPPChangeCheckData getChangeCheckData() {
    EPPChangeCheckData out = new EPPChangeCheckData();
    out.setRequestId(getTransId());
    out.setExists(p(.5));
    return out;
  }

  static public EPPChangeCheckResp getChangeCheckResp(String aClientTransId) {
    EPPTransId aTransId = new EPPTransId(aClientTransId, DEFAULT_SERVERTRANSID);
    EPPChangeCheckResp resp = null;
    if (p(.8)) {
      resp = new EPPChangeCheckResp(aTransId);
      do
        resp.addCheckData(getChangeCheckData());
      while (p(.5));
    }
    else {
      resp = new EPPChangeCheckResp(aTransId, getChangeCheckData());
    }

    return resp;
  }

  static public EPPChangeCreateCmd getChangeCreateCmd() {
    EPPChangeCreateCmd cmd = null;
    if (p(.8)) {
      cmd = new EPPChangeCreateCmd(DEFAULT_CLIENTTRANSID);
      cmd.setRequestId(getTransId());
      if (p(.5))
        cmd.setPriority(getPriority());
      cmd.setCategories(getCategories());
      cmd.setDescription(getWord(tridScheme));
    }
    else {
      cmd = new EPPChangeCreateCmd(DEFAULT_CLIENTTRANSID, getTransId(), getLabel(), getWord(tridScheme));
    }
    return cmd;
  }

  static public EPPChangeDeleteCmd getChangeDeleteCmd() {
    EPPChangeDeleteCmd cmd = null;
    if (p(.5)) {
      cmd = new EPPChangeDeleteCmd(DEFAULT_CLIENTTRANSID);
      cmd.setRequestId(getTransId());
    }
    else {
      cmd = new EPPChangeDeleteCmd(DEFAULT_CLIENTTRANSID, getTransId());
    }
    return cmd;
  }

  static public EPPChangeInfoCmd getChangeInfoCmd() {
    EPPChangeInfoCmd cmd = null;
    if (p(.5)) {
      cmd = new EPPChangeInfoCmd(DEFAULT_CLIENTTRANSID);
      cmd.setRequestId(getTransId());
    }
    else {
      cmd = new EPPChangeInfoCmd(DEFAULT_CLIENTTRANSID, getTransId());
    }
    return cmd;
  }

  static public EPPChangeInfoResp getChangeInfoResp(String aClientTransId) {
    EPPTransId aTransId = new EPPTransId(aClientTransId, DEFAULT_SERVERTRANSID);
    EPPChangeInfoResp resp = new EPPChangeInfoResp(aTransId);
    resp.setRequestId(getTransId());
    resp.setPriority(getPriority());
    resp.setCategories(getCategories());
    resp.setDescription(getWord(tridScheme));
    resp.setStatus(getStatus());
    resp.setCreatedDate(getDate());
    resp.setUpdatedDate(getDate());
    resp.setCreatedId(getTransId());
    resp.setUpdatedId(getTransId());
    if (p(.3))
      resp.addAction(getChangeAction());
    if (p(.3))
      resp.addAction(getChangeAction());
    if (p(.3))
      resp.addAction(getChangeAction());
    if (p(.3))
      resp.addAction(getChangeAction());
    return resp;
  }

  static public EPPChangeUpdateAttributes getChangeUpdateAttributes() {
    EPPChangeUpdateAttributes out = new EPPChangeUpdateAttributes();
    if (p(.333)) {
      out.setPriority(getWord(priScheme));
      if (p(.5))
        out.setCategories(getCategories());
      if (p(.5))
        out.setDescription(getWord(tridScheme));
    }
    else if (p(.5)) {
      out.setCategories(getCategories());
      if (p(.5))
        out.setDescription(getWord(tridScheme));
    }
    else
      out.setDescription(getWord(tridScheme));
    return out;
  }

  static public EPPChangeUpdateCmd getChangeUpdateCmd() {
    EPPChangeUpdateCmd cmd = new EPPChangeUpdateCmd(DEFAULT_CLIENTTRANSID);
    cmd.setRequestId(getTransId());
    if (p(.5))
      cmd.setAttributes(getChangeUpdateAttributes());
    else if (p(.5))
      cmd.setClear(true);
    else
      cmd.setSubmit(true);
    return cmd;
  }

  static public EPPChangeUpdateResp getChangeUpdateResp(String aClientTransId) {
    if (p(.5))
      return getChangeUpdateSubmitResp(aClientTransId);
    else
      return getChangeUpdateEmptyResp(aClientTransId);
  }

  static public EPPChangeUpdateResp getChangeUpdateSubmitResp(String aClientTransId) {
    EPPChangeUpdateResp resp = getChangeUpdateEmptyResp(aClientTransId);
    resp.setReceipt(getReceipt());
    return resp;
  }

  static public EPPChangeUpdateResp getChangeUpdateEmptyResp(String aClientTransId) {
    EPPTransId aTransId = new EPPTransId(aClientTransId, DEFAULT_SERVERTRANSID);
    EPPChangeUpdateResp resp = new EPPChangeUpdateResp(aTransId);
    return resp;
  }

  static public Date getDate() {
    return new Date(getLong(System.currentTimeMillis()));
  }

  static public String getDefinition() {
    return getWord(defScheme);
  }

  static public String getDescription() {
    return getWord(descScheme);
  }

  /**
   * Gets an {@link EPPResponse} with the supplied client transaction
   * identifier and a constant server transaction identifier.
   * 
   * @param aClientTransId
   *           Client transaction identifier if exists; {@code null} otherwise.
   * 
   * @return {@link EPPResponse} constructed from supplied client transaction
   *         identifier and a constant server transaction identifier.
   */
  static public EPPResponse getEppResponse(String aClientTransId) {
    EPPTransId aTransId = new EPPTransId(aClientTransId, DEFAULT_SERVERTRANSID);
    EPPResponse resp = new EPPResponse(aTransId);
    return resp;
  }

  static public int getInt() {
    return random.nextInt();
  }

  static public int getInt(int r) {
    return (int) (random.nextDouble() * r);
  }

  static public int getInt(int n, int r) {
    return (int) ((random.nextDouble() * (r - n)) + n);
  }

  static public String getLabel() {
    if (p(.75))
      return ld.get() + getWord(ilabelScheme) + ld.get();
    return ".";
  }

  static public long getLong() {
    return random.nextLong();
  }

  static public long getLong(long r) {
    return (long) (random.nextDouble() * r);
  }

  static public long getLong(long n, long r) {
    return (long) ((random.nextDouble() * (r - n)) + n);
  }

  static public List getCategories() {
    ArrayList out = new ArrayList();
    out.add(getLabel());
    while (p(.5))
      out.add(getLabel());
    return out;
  }

  static public String getReceipt() {
    return "\n<+> Begin Change Request Summary: DO NOT EDIT BELOW\n\n" + "Change Request Id:  tk421\n"
          + "Request Priority:  URGENT\n" + "Top-level domain:  .COM\n"
          + "Purpose/Description: Remove 3 of 4 name servers.  Add 3 name servers.  Change 1 IP address.\n"
          + "\n\nOperations:\n\n" + "    Domain Update\n" + "      Name:  .COM\n\n"
          + "      Current: A.GTLD-SERVERS.NET\n" + "      Current: B.GTLD-SERVERS.NET\n"
          + "      Current: C.GTLD-SERVERS.NET\n" + "      Current: D.GTLD-SERVERS.NET\n\n"
          + "      Remove:  A.GTLD-SERVERS.NET\n" + "      Remove:  B.GTLD-SERVERS.NET\n"
          + "      Remove:  C.GTLD-SERVERS.NET\n\n" + "      Add: X.GTLD-SERVERS.NET\n"
          + "      Add: Y.GTLD-SERVERS.NET\n" + "      Add: Z.GTLD-SERVERS.NET\n" + "\n" + "    Host Update\n"
          + "      Name:  D.GTLD-SERVERS.NET\n" + "\n" + "      New Name: Z.GTLD-SERVERS.NET\n" + "\n"
          + "      Current: 198.49.49.0\n" + "\n" + "      Remove:  198.49.49.0\n" + "\n" + "      Add: 127.0.0.1\n"
          + "\n" + "<-> End Change Request Summary: DO NOT EDIT ABOVE\n";

  }

  static public String getStatus() {
    return getWord(statusScheme);
  }

  static public String getPriority() {
    return getWord(priScheme);
  }

  static public String getTransId() {
    return getWord(tridScheme);
  }

  static public boolean p(double d) {
    return random.nextDouble() < d;
  }

  static public void reset() {
    random = new Random();
    random.nextDouble(); // LOGIC: It is necessary to prime the Random
    configRandomizableCharacters();
  }

  static public void reset(long seed) {
    random = new Random(seed);
    random.nextDouble(); // LOGIC: It is necessary to prime the Random
    configRandomizableCharacters();
  }

  static private void configRandomizableCharacters() {
    ldh.setRandom(random);
    ld.setRandom(random);
    pc.setRandom(random);
    pcw.setRandom(random);
  }

  static private String getWord(int len, RandomizableCharacter rc) {
    String out = TextUtil.normalize(new String(rc.get(len)));
    while (out.length() < len) {
      out += rc.get();
      out = TextUtil.normalize(out);
    }
    return out;
  }

  static private String getWord(RandomizationScheme scheme) {
    return getWord(getInt(scheme.getMin(), scheme.getMax()), scheme.getRand());
  }

  static private void init() {
    random = new Random();
    random.nextDouble(); // LOGIC: It is necessary to prime the Random
    ldh = new LetterDigitHyphen(random);
    ld = new LetterDigit(random);
    pc = new PrintableCharacters(random);
    pcw = new PrintableCharactersWhitespace(random);
  }

}
