/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt'
 * which is part of this source code package
 */

package com.scytl.slogger.event;

import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static java.util.Collections.unmodifiableMap;

import com.scytl.slogger.util.SecureStringUtils;

import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Secure message consists of the two parts of the message being logger: a simple string text and
 * properties.
 *
 * <p>Instances of this class are immutable.
 *
 * @author aakimov
 */
public final class SecureMessage {
  private static final SecureMessage CHECKPOINT_INSTANCE = new SecureMessage("Checkpoint Entry",
      unmodifiableMap(singletonMap(SecureMessageProperties.CHECKPOINT, null)));
  private static final SecureMessage HMAC_INSTANCE =
      new SecureMessage("New Secret Key generated.", emptyMap());
  private static final String BEGIN = " {*";
  private static final String SEPARATOR = ",";
  private static final String ASSIGNMENT = "::";
  private static final String END = "*}";
  private final String text;
  private final Map<String, String> properties;

  private SecureMessage(String text, Map<String, String> properties) {
    this.text = text;
    this.properties = properties;
  }

  /**
   * Returns the checkpoint instance.
   *
   * @return the checkpoint instance.
   */
  public static SecureMessage getCheckpointInstance() {
    return CHECKPOINT_INSTANCE;
  }

  /**
   * Returns the HMac instance.
   *
   * @return the HMac instance.
   */
  public static SecureMessage getHMacInstance() {
    return HMAC_INSTANCE;
  }

  /**
   * Creates a new message from a client's one.
   *
   * @param message the client's message
   * @return a new instance.
   */
  public static SecureMessage newClientInstance(String message) {
    return newRenderedInstance(SecureStringUtils.escape(message));
  }

  /**
   * Creates a new instance for a given log file.
   *
   * @param file the log file
   * @return a new instance.
   */
  public static SecureMessage newFileInstance(File file) {
    return new SecureMessage("Log File Name: " + file.getAbsolutePath(), emptyMap());
  }

  /**
   * Creates an instance from a rendered message.
   *
   * @param rendered the rendered message
   * @return an instance.
   */
  public static SecureMessage newRenderedInstance(String rendered) {
    int textTo = rendered.lastIndexOf(BEGIN);
    if (textTo < 0) {
      return new SecureMessage(rendered, emptyMap());
    }
    if (!rendered.endsWith(END)) {
      throw new IllegalArgumentException("Text found after event properties");
    }
    int propertiesFrom = textTo + BEGIN.length();
    int propertiesTo = rendered.length() - END.length();
    String text = rendered.substring(0, textTo);
    Map<String, String> properties = new LinkedHashMap<>();
    for (String entry : rendered.substring(propertiesFrom, propertiesTo).split(SEPARATOR)) {
      String[] keyAndValue = entry.split(ASSIGNMENT);
      String key = keyAndValue[0];
      String value = keyAndValue.length == 2 ? keyAndValue[1] : null;
      properties.put(key, value);
    }
    return new SecureMessage(text, unmodifiableMap(properties));
  }

  /**
   * Adds given properties.
   *
   * @param properties the properties
   * @return a new instance.
   */
  public SecureMessage addProperties(Map<String, String> properties) {
    Map<String, String> newProperties = new LinkedHashMap<>(this.properties);
    newProperties.putAll(properties);
    return new SecureMessage(text, unmodifiableMap(newProperties));
  }

  /**
   * Returns whether the event is a checkpoint.
   *
   * @return the event is a checkpoint.
   */
  public boolean isCheckpoint() {
    return properties.containsKey(SecureMessageProperties.CHECKPOINT);
  }

  /**
   * Returns the properties.
   *
   * @return the properties.
   */
  public Map<String, String> properties() {
    return properties;
  }

  /**
   * Clears the properties.
   *
   * @return a new instance.
   */
  public SecureMessage removeProperties() {
    return new SecureMessage(text, emptyMap());
  }

  /**
   * Removes a given property.
   *
   * @param key the key
   * @return an instance.
   */
  public SecureMessage removeProperty(String key) {
    if (!properties.containsKey(key)) {
      return this;
    }
    Map<String, String> newProperties = new LinkedHashMap<>(this.properties);
    newProperties.remove(key);
    return new SecureMessage(text, unmodifiableMap(newProperties));
  }
  
  /**
   * Renders the message joining the text and properties into a single string. The returned value
   * can be passed to {@link #newRenderedInstance(String)} to restore the original instance.
   * 
   * @return the rendered message.
   */
  public String render() {
    StringBuilder builder = new StringBuilder();
    builder.append(text);
    if (!properties.isEmpty()) {
      builder.append(BEGIN);
      boolean first = true;
      for (Map.Entry<String, String> entry : properties.entrySet()) {
        if (first) {
          first = false;
        } else {
          builder.append(SEPARATOR);
        }
        builder.append(entry.getKey()).append(ASSIGNMENT);
        if (entry.getValue() != null) {
          builder.append(entry.getValue());
        }
      }
      builder.append(END);
    }
    return builder.toString();
  }

  /**
   * Returns the text.
   *
   * @return the text.
   */
  public String text() {
    return text;
  }
}
