package li.cil.oc.util

import java.util.Calendar
import java.util.GregorianCalendar
import java.util.TimeZone

import scala.collection.mutable

object GameTimeFormatter {
  // Locale? What locale? Seriously though, since this would depend on the
  // server's locale I think it makes more sense to keep it English always.
  private val weekDays = Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
  private val shortWeekDays = Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")
  private val months = Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December")
  private val shortMonths = Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
  private val amPm = Array("AM", "PM")

  class DateTime(val year: Int, val month: Int, val day: Int,
                 val weekDay: Int, val yearDay: Int,
                 val hour: Int, val minute: Int, val second: Int)

  // See http://www.cplusplus.com/reference/ctime/strftime/
  private val specifiers: Map[Char, (DateTime) => String] = Map(
    'a' -> (t => shortWeekDays(t.weekDay - 1)),
    'A' -> (t => weekDays(t.weekDay - 1)),
    'b' -> (t => shortMonths(t.month - 1)),
    'B' -> (t => months(t.month - 1)),
    'c' -> (t => format("%a %b %e %H:%M:%S %Y", t)),
    'C' -> (t => f"${t.year / 100}%02d"),
    'd' -> (t => f"${t.day}%02d"),
    'D' -> (t => format("%m/%d/%y", t)),
    'e' -> (t => f"${t.day}%2d"),
    'F' -> (t => format("%Y-%m-%d", t)),
    //'g' -> (t => ""),
    //'G' -> (t => ""),
    'h' -> (t => format("%b", t)),
    'H' -> (t => f"${t.hour}%02d"),
    'I' -> (t => f"${(t.hour + 11) % 12 + 1}%02d"),
    'j' -> (t => f"${t.yearDay}%03d"),
    'm' -> (t => f"${t.month}%02d"),
    'M' -> (t => f"${t.minute}%02d"),
    'n' -> (t => "\n"),
    'p' -> (t => amPm(if (t.hour < 12) 0 else 1)),
    'r' -> (t => format("%I:%M:%S %p", t)),
    'R' -> (t => format("%H:%M", t)),
    'S' -> (t => f"${t.second}%02d"),
    't' -> (t => "\t"),
    'T' -> (t => format("%H:%M:%S", t)),
    //'u' -> (t => ""),
    //'U' -> (t => ""),
    //'V' -> (t => ""),
    'w' -> (t => s"${t.weekDay - 1}"),
    //'W' -> (t => ""),
    'x' -> (t => format("%D", t)),
    'X' -> (t => format("%T", t)),
    'y' -> (t => f"${t.year % 100}%02d"),
    'Y' -> (t => f"${t.year}%04d"),
    //'z' -> (t => ""),
    //'Z' -> (t => ""),
    '%' -> (t => "%")
  )

  def parse(time: Double) = {
    val calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"))
    calendar.setTimeInMillis((time * 1000).toLong)

    new DateTime(
      calendar.get(Calendar.YEAR),
      calendar.get(Calendar.MONTH) + 1,
      calendar.get(Calendar.DAY_OF_MONTH),
      calendar.get(Calendar.DAY_OF_WEEK),
      calendar.get(Calendar.DAY_OF_YEAR),
      calendar.get(Calendar.HOUR_OF_DAY),
      calendar.get(Calendar.MINUTE),
      calendar.get(Calendar.SECOND))
  }

  def format(format: String, time: DateTime) = {
    val result = new mutable.StringBuilder()
    val iterator = format.iterator
    while (iterator.hasNext) {
      iterator.next() match {
        case '%' if iterator.hasNext =>
          specifiers.get(iterator.next()) match {
            case Some(specifier) => result.append(specifier(time))
            case _ =>
          }
        case c => result.append(c)
      }
    }
    result.toString()
  }

  def mktime(year: Int, mon: Int, mday: Int, hour: Int, min: Int, sec: Int): Option[Int] = {
    val calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"))
    calendar.set(Calendar.YEAR, year)
    calendar.set(Calendar.MONTH, mon - 1)
    calendar.set(Calendar.DAY_OF_MONTH, mday)
    calendar.set(Calendar.HOUR_OF_DAY, hour)
    calendar.set(Calendar.MINUTE, min)
    calendar.set(Calendar.SECOND, sec)

    Option((calendar.getTimeInMillis / 1000).toInt)
  }
}
