package org.testng.remote.strprotocol;


import org.testng.ITestResult;
import org.testng.collections.Lists;
import org.testng.collections.Maps;

import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;


/**
 * Marshal/unmarshal tool for <code>IStringMessage</code>s.
 * 
 * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
 */
public class MessageHelper {
  public static final char DELIMITER = '\u0001';
  public static final char PARAM_DELIMITER = '\u0004';
  private static final char LINE_SEP_DELIMITER_1 = '\u0002';
  private static final char LINE_SEP_DELIMITER_2 = '\u0003';
  
  public static final int GENERIC_SUITE_COUNT = 1;
  
  public static final int SUITE = 10;
  public static final int SUITE_START = 11;
  public static final int SUITE_FINISH = 12;
  
  public static final int TEST = 100;
  public static final int TEST_START = 101;
  public static final int TEST_FINISH = 102;
  
  public static final int TEST_RESULT = 1000;
  public static final int PASSED_TEST = TEST_RESULT + ITestResult.SUCCESS;
  public static final int FAILED_TEST = TEST_RESULT + ITestResult.FAILURE;
  public static final int SKIPPED_TEST = TEST_RESULT + ITestResult.SKIP;
  public static final int FAILED_ON_PERCENTAGE_TEST = TEST_RESULT + ITestResult.SUCCESS_PERCENTAGE_FAILURE;
  public static final int TEST_STARTED = TEST_RESULT + ITestResult.STARTED;
  
  public static final String STOP_MSG = ">STOP";
  public static final String ACK_MSG = ">ACK";
  
  public static int getMessageType(final String message) {
    int idx = message.indexOf(DELIMITER);
    
    return idx == -1 ? Integer.parseInt(message) : Integer.parseInt(message.substring(0, idx));
  }
  
  public static GenericMessage unmarshallGenericMessage(final String message) {
    String[] messageParts = parseMessage(message);
    if(messageParts.length == 1) {
      return new GenericMessage(Integer.parseInt(messageParts[0]));
    }
    else {
      Map props = Maps.newHashMap();
      
      for(int i = 1; i < messageParts.length; i+=2) {
        props.put(messageParts[i], messageParts[i + 1]);
      }
      
      return new GenericMessage(Integer.parseInt(messageParts[0]), props);
    }
  }
  
  public static SuiteMessage createSuiteMessage(final String message) {
    int type = getMessageType(message);
    String[] messageParts = parseMessage(message);
    
    return new SuiteMessage(messageParts[1],
                            MessageHelper.SUITE_START == type,
                            Integer.parseInt(messageParts[2]));
  }
  
  public static TestMessage createTestMessage(final String message) {
    int type = getMessageType(message);
    String[] messageParts = parseMessage(message);
    
    return new TestMessage(MessageHelper.TEST_START == type,
                           messageParts[1],
                           messageParts[2],
                           Integer.parseInt(messageParts[3]),
                           Integer.parseInt(messageParts[4]),
                           Integer.parseInt(messageParts[5]),
                           Integer.parseInt(messageParts[6]),
                           Integer.parseInt(messageParts[7]));
  }
  
  public static TestResultMessage unmarshallTestResultMessage(final String message) {
    String[] messageParts = parseMessage(message);
    
    String parametersFragment= null;
    String startTimestampFragment= null;
    String stopTimestampFragment= null;
    String stackTraceFragment= null;
    String testDescriptor= null;
    switch(messageParts.length) {
      case 10:
      {
        parametersFragment= messageParts[5];
        startTimestampFragment= messageParts[6];
        stopTimestampFragment= messageParts[7];
        stackTraceFragment= messageParts[8];
        testDescriptor= messageParts[9];
      }
      break;
      case 9:
      {
        parametersFragment= messageParts[5];
        startTimestampFragment= messageParts[6];
        stopTimestampFragment= messageParts[7];
        stackTraceFragment= messageParts[8];        
      }
      break;
      default:
      {
        // HINT: old protocol without parameters
        parametersFragment= null;
        startTimestampFragment= messageParts[5];
        stopTimestampFragment= messageParts[6];
        stackTraceFragment= messageParts[7];        
      }
    }
    return new TestResultMessage(Integer.parseInt(messageParts[0]),
                                 messageParts[1],
                                 messageParts[2],
                                 messageParts[3],
                                 messageParts[4],
                                 replaceAsciiCharactersWithUnicode(replaceNewLineReplacer(testDescriptor)),
                                 parseParameters(parametersFragment),
                                 Long.parseLong(startTimestampFragment),
                                 Long.parseLong(stopTimestampFragment),
                                 replaceAsciiCharactersWithUnicode(replaceNewLineReplacer(stackTraceFragment))
            );
  }
  
  public static String replaceNewLine(String message) {
    if(null == message) {
      return message;
    }
    
    return message.replace('\n', LINE_SEP_DELIMITER_1).replace('\r', LINE_SEP_DELIMITER_2);
  }

  public static String replaceUnicodeCharactersWithAscii(String message) {
    if(null == message) {
      return message;
    }

    return replace(
              replace(
                  replace(
                    replace(message, "\u0004", "\\u0004"), 
                  "\u0003", "\\u0003"), 
              "\u0002", "\\u0002"), 
           "\u0001", "\\u0001");
  }

  public static String replaceAsciiCharactersWithUnicode(String message) {
    if(null == message) {
      return message;
    }

    return replace(
            replace(
                replace(
                    replace(message, "\\u0004", "\u0004"), 
                    "\\u0003", "\u0003"), 
                "\\u0002", "\u0002"), 
            "\\u0001", "\u0001");
  }
  
  public static String replaceNewLineReplacer(String message) {
    if(null == message) {
      return message;
    }
    
    return message.replace(LINE_SEP_DELIMITER_1, '\n').replace(LINE_SEP_DELIMITER_2, '\r');
  }
  
  private static String[] parseParameters(final String messagePart) {
    return tokenize(messagePart, PARAM_DELIMITER);
  }
  
  private static String[] parseMessage(final String message) {
    return tokenize(message, DELIMITER);
  }
  
  private static String[] tokenize(final String message, final char separator) {
    if(null == message) {
      return new String[0];
    }
    
    List<String> tokens = Lists.newArrayList();
    int start = 0;
    for(int i = 0; i < message.length(); i++) {
      if(separator == message.charAt(i)) {
        tokens.add(message.substring(start, i));
        start = i + 1;
      }
    }
    if(start < message.length()) {
      tokens.add(message.substring(start, message.length()));
    }
    
    return tokens.toArray(new String[tokens.size()]);
  }
  
  /**
   * Implementation according to JDK5 String.replace(CharSequence,CharSequence)
   */
  private static final String replace(String original, CharSequence target, CharSequence replacement) {
      return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(original)
          .replaceAll(quoteReplacement(replacement.toString()));
  }
  
  /**
   * Implementation according to JDK5 String.replace(CharSequence,CharSequence)
   */
  private static String quoteReplacement(String s) {
      if ((s.indexOf('\\') == -1) && (s.indexOf('$') == -1))
          return s;
      StringBuffer sb = new StringBuffer();
      for (int i=0; i<s.length(); i++) {
          char c = s.charAt(i);
          if (c == '\\') {
              sb.append('\\'); sb.append('\\');
          } else if (c == '$') {
              sb.append('\\'); sb.append('$');
          } else {
              sb.append(c);
          }
      }
      return sb.toString();
  }  
}
