<commit_before>package latis.time

import latis.dm.Real
import java.util.Date
import latis.metadata.Metadata
import latis.data.value.DoubleValue
import latis.data.EmptyData
import latis.data.Data
import latis.metadata.EmptyMetadata
import latis.util.RegEx

class Time extends Real {
  
  protected var timeScale: TimeScale = TimeScale.DEFAULT
  
  def getUnits() = timeScale
  
  def convert(scale: TimeScale): Time = {
    TimeConverter(this.timeScale, scale).convert(this)
  }
  
  def toIso: String = {
    val time = convert(TimeScale.JAVA).doubleValue.toLong //TODO: get long from Data
    val date = new java.util.Date(time)
    TimeFormat.DATETIME.format(date)
  }
  
  def format: String = {
    TimeFormat(metadata("format")).format(doubleValue)
  }
  
  //override to deal with formatted time strings  
  override def compare(that: String): Int = {
//  that match {
//    case RegEx.TIME(time) => { NOT matching, "findFirstIn" seems to work
//      //convert ISO to java time then to our time scale
//      //TODO: convert ISO string to java time, then convert to current time scale
//      val t = Time(javax.xml.bind.DatatypeConverter.parseDateTime(that).getTimeInMillis().toDouble)
//      //TODO: make sure parser does UTC
//      //TODO: reuse converter
//      val other = t.convert(timeScale).toDouble
//      compare(other)
//    }
    RegEx.TIME findFirstIn that match {
      case Some(s) => {
        val t = Time(javax.xml.bind.DatatypeConverter.parseDateTime(s).getTimeInMillis().toDouble)
        val other = t.convert(timeScale).doubleValue
        compare(other)
      }
      case _ => compare(that.toDouble) //TODO: potential parse error
    }
  }
}

object Time {
  import scala.collection.mutable.HashMap
  //TODO: DEFAULT vs JAVA time scale
  
  //val defaultMd = HashMap(("name", "time"), ("type", "Time"))
  //don't change name here
  
  def apply(md: Metadata): Time = {
    val scale = md.get("units") match {
      case Some(u) => TimeScale(u)
      case None => TimeScale.JAVA
    }
    val t = new Time
    t._data = EmptyData
    t._metadata = md
    t.timeScale = scale
    t
  }
    
  def apply(md: Metadata, values: Seq[Double]): Time = {
    val t = new Time
    t._metadata = md
    t._data = Data(values)
    t.timeScale = TimeScale.JAVA
    t
  }
    
  def apply(md: Metadata, value: Double): Time = {
    val scale = md.get("units") match {
      case Some(u) => TimeScale(u)
      case None => TimeScale.JAVA
    }
    val t = new Time
    t._data = DoubleValue(value)
    t._metadata = md
    t.timeScale = scale
    t
  }
  
  def apply(value: Double, units: String): Time = {
    val t = new Time
    t._data = DoubleValue(value)
    t._metadata = Metadata(Map(("units", units))) //Metadata(defaultMd + (("units", units)))
    t.timeScale = TimeScale(units)
    t
  }
  
  def apply(value: Double, scale: TimeScale): Time = {
    val t = new Time
    t._data = DoubleValue(value)
    t._metadata = Metadata(Map(("units", scale.toString)))
    t.timeScale = scale
    t
  }
  
  /**
   * Parse time given units as SimpleDateFormat using Java (default) time scale.
   */
  def apply(value: String, units: String): Time = {
    if (units.contains(" since ")) Time(value.toDouble, units)
    else {
      val format = new TimeFormat(units)
      Time(format.parse(value).getTime(), TimeScale.JAVA)
    }
  }
  
  def apply(md: Metadata, value: Double, scale: TimeScale): Time = {
    val t = new Time
    t._data = DoubleValue(value)
    t._metadata = md
    t.timeScale = scale
    t
  }
  
  /**
   * Parse time given units as SimpleDateFormat using Java (default) time scale.
   */
  def apply(md: Metadata, value: String): Time = {
    /*
     * TODO: units vs format
     * units is prime metadata (within "metadata" element)
     * format could just be an instruction to the adapter (xml attribute?)
     */
    md.get("units") match {
      case Some(u) => {
        if (u.contains(" since ")) Time(md, value.toDouble, TimeScale.JAVA)
        else {
          val format = new TimeFormat(u)
          Time(md, format.parse(value).getTime(), TimeScale.JAVA)
          //TODO: consider units and format attributes
        }
      }
      case None => Time(md, value.toDouble, TimeScale.JAVA) //assume default units
        //throw new RuntimeException("Time Metadata is missing units.")
    }
  }
  
  /*
   * TODO: use "format" if formatted, default to java units, override by also setting "units"?
   */
  
  /**
   * Milliseconds since 1970, native Java time.<commit_msg>notes on allowing Time to support real, integer, or Text<commit_after>package latis.time

import latis.dm.Real
import java.util.Date
import latis.metadata.Metadata
import latis.data.value.DoubleValue
import latis.data.EmptyData
import latis.data.Data
import latis.metadata.EmptyMetadata
import latis.util.RegEx

class Time extends Real {
  
  protected var timeScale: TimeScale = TimeScale.DEFAULT
  
  def getUnits() = timeScale
  
  def convert(scale: TimeScale): Time = {
    TimeConverter(this.timeScale, scale).convert(this)
  }
  
  def toIso: String = {
    val time = convert(TimeScale.JAVA).doubleValue.toLong //TODO: get long from Data
    val date = new java.util.Date(time)
    TimeFormat.DATETIME.format(date)
  }
  
  def format: String = {
    TimeFormat(metadata("format")).format(doubleValue)
  }
  
  //override to deal with formatted time strings  
  override def compare(that: String): Int = {
//  that match {
//    case RegEx.TIME(time) => { NOT matching, "findFirstIn" seems to work
//      //convert ISO to java time then to our time scale
//      //TODO: convert ISO string to java time, then convert to current time scale
//      val t = Time(javax.xml.bind.DatatypeConverter.parseDateTime(that).getTimeInMillis().toDouble)
//      //TODO: make sure parser does UTC
//      //TODO: reuse converter
//      val other = t.convert(timeScale).toDouble
//      compare(other)
//    }
    RegEx.TIME findFirstIn that match {
      case Some(s) => {
        val t = Time(javax.xml.bind.DatatypeConverter.parseDateTime(s).getTimeInMillis().toDouble)
        val other = t.convert(timeScale).doubleValue
        compare(other)
      }
      case _ => compare(that.toDouble) //TODO: potential parse error
    }
  }
}

object Time {
  import scala.collection.mutable.HashMap
  //TODO: DEFAULT vs JAVA time scale
  
  //val defaultMd = HashMap(("name", "time"), ("type", "Time"))
  //don't change name here
  
  def apply(md: Metadata): Time = {
    val scale = md.get("units") match {
      case Some(u) => TimeScale(u)
      case None => TimeScale.JAVA
    }
    val t = new Time
    t._data = EmptyData
    t._metadata = md
    t.timeScale = scale
    t
  }
    
  def apply(md: Metadata, values: Seq[Double]): Time = {
    val t = new Time
    t._metadata = md
    t._data = Data(values)
    t.timeScale = TimeScale.JAVA
    t
  }
    
  def apply(md: Metadata, value: Double): Time = {
    val scale = md.get("units") match {
      case Some(u) => TimeScale(u)
      case None => TimeScale.JAVA
    }
    val t = new Time
    t._data = DoubleValue(value)
    t._metadata = md
    t.timeScale = scale
    t
  }
  
  def apply(value: Double, units: String): Time = {
    val t = new Time
    t._data = DoubleValue(value)
    t._metadata = Metadata(Map(("units", units))) //Metadata(defaultMd + (("units", units)))
    t.timeScale = TimeScale(units)
    t
  }
  
  def apply(value: Double, scale: TimeScale): Time = {
    val t = new Time
    t._data = DoubleValue(value)
    t._metadata = Metadata(Map(("units", scale.toString)))
    t.timeScale = scale
    t
  }
  
  /**
   * Parse time given units as SimpleDateFormat using Java (default) time scale.
   */
  def apply(value: String, units: String): Time = {
    if (units.contains(" since ")) Time(value.toDouble, units)
    else {
      val format = new TimeFormat(units)
      Time(format.parse(value).getTime(), TimeScale.JAVA)
    }
  }
  
  def apply(md: Metadata, value: Double, scale: TimeScale): Time = {
    val t = new Time
    t._data = DoubleValue(value)
    t._metadata = md
    t.timeScale = scale
    t
  }
  
  /**
   * Parse time given units as SimpleDateFormat using Java (default) time scale.
   */
  def apply(md: Metadata, value: String): Time = {
    /*
     * TODO: units vs format
     * units is prime metadata (within "metadata" element)
     * format could just be an instruction to the adapter (xml attribute?)
     * Does it make sense to have "format" in the metadata?
     *   maybe for ascii output
     *   or is it just an instruction to the writer
     *   consider "Name (units)" header, format makes sense for units here
     *     special case: if hasFormat...
     *   mechanism for user request to inform format
     *     TSS1 problem, format filter sets units, writer doesn't know if filter set it or if to use writer default
     *   formatted time akin to diff grid project - refers to same thing, just diff representation?
     *     converting string to unix time like projecting onto standard grid
     *     might lose something in the conversion
     * So does format_time become a true filter like re-gridding?
     *   
     *     
     * should format in md suggest that binary have string representation?
     *   consider idl api where user wants yyyyDDD, otherwise need converter from unix time
     * do we store string in Time Data?
     *   Time extends Real
     *   but could always behave by supplying double
     *   
     * What about Integer time?
     *   avoid round off problems
     * Should Time extend Scalar and support double, long, or String?
     *   Real, Integer, Text as traits?
     *   
     */
    md.get("units") match {
      case Some(u) => {
        if (u.contains(" since ")) Time(md, value.toDouble, TimeScale.JAVA)
        else {
          val format = new TimeFormat(u)
          Time(md, format.parse(value).getTime(), TimeScale.JAVA)
          //TODO: consider units and format attributes
        }
      }
      case None => Time(md, value.toDouble, TimeScale.JAVA) //assume default units
        //throw new RuntimeException("Time Metadata is missing units.")
    }
  }
  
  /**
   * Milliseconds since 1970, native Java time.
{'fraction_non_alphanumeric': 0.11206812175785455, 'fraction_numerical': 0.0008808847998433983, 'mean_word_length': 2.5865215865215867, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '654459', 'n_tokens_mistral': 3178, 'n_tokens_neox': 3146, 'n_words': 979}