import io.circe.parser.decode
import io.circe.syntax.*
import io.gitlab.myrolabs.family.data.InitialSeeding
import io.gitlab.myrolabs.family.model.*

import cats.syntax.functor.*
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.syntax.*
import io.circe.{Decoder, Encoder}
import cats.syntax.functor.*
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.syntax.*
import io.circe.{Decoder, Encoder}

sealed trait StorageItem {
  def uid: String
  def document: Doc
  def comment: String
  def duplicates: Seq[String]
}
object StorageItem {
  implicit val encoder: Encoder[StorageItem] = Encoder.instance {
    case n@BirthCertificate(_, _, _, _, _) => n.asJson
    case n@BaptismRecord(_, _, _, _, _, _, _) => n.asJson
    case n@WeddingRecord(_, _, _, _, _, _, _, _, _) => n.asJson
    case n@DeathRecord(_, _, _, _, _, _, _) => n.asJson
    case n@Tomb(_, _, _, _, _) => n.asJson
    case n@ResidenceRecord(_, _, _, _, _) => n.asJson
    case n@Letter(_, _, _, _, _, _) => n.asJson
    case n@PersonalDocument(_, _, _, _, _, _) => n.asJson
  }
  implicit val decoder: Decoder[StorageItem] = List[Decoder[StorageItem]](
    Decoder[BirthCertificate].widen,
    Decoder[BaptismRecord].widen,
    Decoder[WeddingRecord].widen,
    Decoder[DeathRecord].widen,
    Decoder[Tomb].widen,
    Decoder[ResidenceRecord].widen,
    Decoder[Letter].widen,
    Decoder[PersonalDocument].widen,
  ).reduceLeft(_ or _)
}

case class BirthCertificate(uid: String, birth: Birth, document: Doc, comment: String, duplicates: Seq[String]) extends StorageItem
object BirthCertificate {
  implicit val decoder: Decoder[BirthCertificate] = deriveDecoder[BirthCertificate]
  implicit val encoder: Encoder[BirthCertificate] = deriveEncoder[BirthCertificate]
}

case class BaptismRecord(uid: String, birth: Birth, baptismDate: Option[Date], godparents: Seq[Name], document: Doc,
                         comment: String, duplicates: Seq[String]) extends StorageItem
object BaptismRecord {
  implicit val decoder: Decoder[BaptismRecord] = deriveDecoder[BaptismRecord]
  implicit val encoder: Encoder[BaptismRecord] = deriveEncoder[BaptismRecord]
}

case class WeddingRecord(uid: String, groom: Person, bride: Person, weddingPlace: Option[Place], weddingDate: Option[Date],
                         godparents: Seq[Name], document: Doc, comment: String, duplicates: Seq[String]) extends StorageItem
object WeddingRecord {
  implicit val decoder: Decoder[WeddingRecord] = deriveDecoder[WeddingRecord]
  implicit val encoder: Encoder[WeddingRecord] = deriveEncoder[WeddingRecord]
}

case class DeathRecord(uid: String, death: Death, related: Option[Name], reason: String, document: Doc, comment: String,
                       duplicates: Seq[String]) extends StorageItem
object DeathRecord {
  implicit val decoder: Decoder[DeathRecord] = deriveDecoder[DeathRecord]
  implicit val encoder: Encoder[DeathRecord] = deriveEncoder[DeathRecord]
}

case class Tomb(uid: String, death: Death, document: Doc, comment: String, duplicates: Seq[String]) extends StorageItem
object Tomb {
  implicit val decoder: Decoder[Tomb] = deriveDecoder[Tomb]
  implicit val encoder: Encoder[Tomb] = deriveEncoder[Tomb]
}

case class ResidenceRecord(uid: String, residents: Seq[Individual], document: Doc, comment: String,
                           duplicates: Seq[String]) extends StorageItem
object ResidenceRecord {
  implicit val decoder: Decoder[ResidenceRecord] = deriveDecoder[ResidenceRecord]
  implicit val encoder: Encoder[ResidenceRecord] = deriveEncoder[ResidenceRecord]
}

case class Letter(uid: String, title: String, correspondents: Seq[LegalEntity], document: Doc, comment: String,
                  duplicates: Seq[String]) extends StorageItem
object Letter {
  implicit val decoder: Decoder[Letter] = deriveDecoder[Letter]
  implicit val encoder: Encoder[Letter] = deriveEncoder[Letter]
}

/** Fallback for other types of documents */
case class PersonalDocument(uid: String, title: String, person: Name, document: Doc, comment: String,
                            duplicates: Seq[String]) extends StorageItem
object PersonalDocument {
  implicit val decoder: Decoder[PersonalDocument] = deriveDecoder[PersonalDocument]
  implicit val encoder: Encoder[PersonalDocument] = deriveEncoder[PersonalDocument]
}



trait Person {
  def name: Name
}

object Person {
  def livingIn(name: String, residence: Place, birthDate: Date, comment: String): Person =
    Resident(Name(name), residence, birthDate, comment)

  def bornAt(name: String, birthPlace: Place, birthDate: Date, comment: String): Person =
    Individual(Name(name), Some(birthPlace), birthDate, comment)

  def phoneBookList(residents: Seq[(String, Date, String)]): Seq[Individual] =
    residents.map(t => Individual(Name(t._1), None, t._2, t._3))

  def confessionList(documentYear: Int, residents: Seq[(String, Int, String)]): Seq[Individual] =
    residents.map(t => Individual(Name(t._1), None, Date.circa(String.valueOf(documentYear - t._2)), t._3))

  def revisionList(documentYear: Int, residents: Seq[(String, Place, Int, String)]): Seq[Individual] =
    residents.map(t => Individual(Name(t._1), Some(t._2), Date.circa(String.valueOf(documentYear - t._3)), t._4))

  implicit val encoder: Encoder[Person] = Encoder.instance {
    case r@Resident(_, _, _, _) => r.asJson
    case s@Individual(_, _, _, _) => s.asJson
  }

  implicit val decoder: Decoder[Person] = List[Decoder[Person]](Decoder[Resident].widen, Decoder[Individual].widen).reduceLeft(_ or _)
}

/** Used for church wedding records */
case class Resident(name: Name, residence: Place, birthDate: Date, comment: String) extends Person

object Resident {
  implicit val decoder: Decoder[Resident] = deriveDecoder[Resident]
  implicit val encoder: Encoder[Resident] = deriveEncoder[Resident]
}

/** Used in residence records and in marriage certificate */
case class Individual(name: Name, birthPlace: Option[Place], birthDate: Date, comment: String) extends Person

object Individual {
  implicit val decoder: Decoder[Individual] = deriveDecoder[Individual]
  implicit val encoder: Encoder[Individual] = deriveEncoder[Individual]
}

import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}

// TODO: attachments, transcript?
case class Doc(tags: Seq[String], repository: Option[Repo], place: Option[Place], date: Option[Date])

object Doc {
  def birth(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("birth", "свідоцтво про народження"), repository, place, date)
  def baptism(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("birth", "baptism", "метричний запис"), repository, place, date)
  def mrgCert(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("wedding", "свідоцтво про шлюб"), repository, place, date)
  def mrgRec(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("wedding", "метричний запис"), repository, place, date)
  def mrgPreRec(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("wedding", "шлюбний обшук"), repository, place, date)
  def death(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("death", "метричний запис"), repository, place, date)
  def tomb: Doc = Doc(Seq("death", "tomb"), None, None, None)
  def rsdConfession(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("residence", "метричний запис"), repository, place, date)
  def rsdCensus(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("residence", "перепис"), repository, place, date)
  def rsdRevision(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("residence", "ревізія"), repository, place, date)
  def rsdPhoneBook(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("residence", "телефонний довідник"), repository, place, date)
  def letter(repository: Option[Repo], place: Option[Place], date: Option[Date]): Doc = Doc(Seq("letter"), repository, place, date)

  implicit val decoder: Decoder[Doc] = deriveDecoder[Doc]
  implicit val encoder: Encoder[Doc] = deriveEncoder[Doc]
}

import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}

case class Birth(child: Name, father: Option[Name], mother: Option[Name], place: Option[Place], date: Option[Date])

object Birth {
  def apply(child: String, father: Option[String], mother: Option[String], place: Option[Place], date: Option[Date]): Birth =
    Birth(Name(child), father.map(n => Name(n)), mother.map(n => Name(n)), place, date)

  implicit val decoder: Decoder[Birth] = deriveDecoder[Birth]
  implicit val encoder: Encoder[Birth] = deriveEncoder[Birth]
}

import cats.syntax.functor.*
import io.circe.generic.auto.*
import io.circe.syntax.*
import io.circe.{Decoder, Encoder}

import java.time.{LocalDate, Year, YearMonth}
import scala.util.Try

sealed trait Date
case class DateRange(from: InternalDate, to: InternalDate)       extends Date
case class SingleDate(value: InternalDate, approximate: Boolean) extends Date

sealed trait InternalDate
case class YearOnly(year: Year)              extends InternalDate
case class YearAndMoth(yearMonth: YearMonth) extends InternalDate
case class FullDate(fullDate: LocalDate)     extends InternalDate

object Date {
  def range(from: String, to: String): DateRange = DateRange(parse(from), parse(to))

  def circa(d: String): SingleDate = SingleDate(parse(d), approximate = true)

  def on(d: String): SingleDate = SingleDate(parse(d), approximate = false)

  private def parse(str: String): InternalDate =
    Seq(
      Try(LocalDate.parse(str)).map(FullDate),
      Try(YearMonth.parse(str)).map(YearAndMoth),
      Try(Year.parse(str)).map(YearOnly)
    ).find(_.isSuccess)
      .getOrElse(throw new RuntimeException(s"Can't parse $str to date!"))
      .get // indicate parsing error, but not data absence

  implicit val encoder: Encoder[Date] = Encoder.instance {
    case r @ DateRange(_, _)  => r.asJson
    case s @ SingleDate(_, _) => s.asJson
  }

  implicit val decoder: Decoder[Date] =
    List[Decoder[Date]](Decoder[DateRange].widen, Decoder[SingleDate].widen).reduceLeft(_ or _)

  implicit val encode: Encoder[InternalDate] = Encoder.instance {
    case r @ YearOnly(_)    => r.asJson
    case s @ YearAndMoth(_) => s.asJson
    case s @ FullDate(_)    => s.asJson
  }

  implicit val decode: Decoder[InternalDate] = List[Decoder[InternalDate]](
    Decoder[YearOnly].widen,
    Decoder[YearAndMoth].widen,
    Decoder[FullDate].widen
  ).reduceLeft(_ or _)
}

import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}

case class Death(person: Name, place: Option[Place], birthDate: Option[Date], deathDate: Option[Date])

object Death {
  def apply(person: String, place: Place, birthDate: Date, deathDate: Date): Death =
    Death(Name(person), Some(place), Some(birthDate), Some(deathDate))

  implicit val decoder: Decoder[Death] = deriveDecoder[Death]
  implicit val encoder: Encoder[Death] = deriveEncoder[Death]
}


import cats.syntax.functor.*
import io.circe.generic.auto.*
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.syntax.*
import io.circe.{Decoder, Encoder}

trait LegalEntity

object LegalEntity {
  implicit val encoder: Encoder[LegalEntity] = Encoder.instance {
    case r @ Org(_)        => r.asJson
    case s @ Name(_, _, _) => s.asJson
  }

  implicit val decoder: Decoder[LegalEntity] =
    List[Decoder[LegalEntity]](Decoder[Org].widen, Decoder[Name].widen).reduceLeft(_ or _)
}

case class Org(name: String) extends LegalEntity

case class Name(surname: String, name: String, paternal: Option[String]) extends LegalEntity

object Name {
  def apply(fullName: String): Name = {
    val parsed = fullName.split(" ")
    parsed.size match {
      case 2 => Name(parsed.head, parsed.last, None)
      case 3 => Name(parsed.head, parsed(1), parsed.lastOption)
      case _ =>
        throw new RuntimeException(s"Can't parse name $fullName to surname, name and paternal") // indicate parsing error, but not data absence
    }
  }

  implicit val decoder: Decoder[Name] = deriveDecoder[Name]
  implicit val encoder: Encoder[Name] = deriveEncoder[Name]
}


import cats.syntax.functor.*
import io.circe.generic.auto.*
import io.circe.syntax.*
import io.circe.{Decoder, Encoder}

sealed trait Place

case class NamedPlace(name: String) extends Place

case class GeoPlace(lat: Double, lon: Double, name: String) extends Place

object Place {
  def geo(latLon: String, name: String): GeoPlace = {
    val parsed = latLon.split(",")
    parsed.size match {
      case 2 => GeoPlace(parsed.head.trim.toDouble, parsed.last.trim.toDouble, name)
      case _ => throw new RuntimeException(s"Can not parse $latLon to geo coordinates!") // indicate parsing error, but not data absence
    }
  }

  def named(name: String): NamedPlace = name.isEmpty match {
    case false => NamedPlace(name)
    case true => throw new RuntimeException("Empty place name! Use None if place is not specified") // indicate parsing error, but not data absence
  }

  def zh: Place = Place.named("м. Житомир")
  def shchyh: Place = Place.named("с. Щигліївка")
  def lutsk: Place = Place.named("м. Луцьк")
  def krop: Place = Place.named("с. Кропивня")
  def vilnaChurch: Place = Place.named("Церква св. Михаїла с. Вільня")
  def minijky: Place = Place.named("с. Минійки")
  def starosilChurch: Place = Place.named("Церква Різдва Богородиці с. Старосільці")
  def may: Place = Place.named("с. Перше Травня")

  implicit val encode: Encoder[Place] = Encoder.instance {
    case n@NamedPlace(_) => n.asJson
    case g@GeoPlace(_, _, _) => g.asJson
  }

  implicit val decode: Decoder[Place] = List[Decoder[Place]](Decoder[NamedPlace].widen, Decoder[GeoPlace].widen).reduceLeft(_ or _)
}

import cats.syntax.functor.*
import io.circe.generic.auto.*
import io.circe.syntax.*
import io.circe.{Decoder, Encoder}

sealed trait Repo

case class Archive(name: String, callNo: String, pages: Option[String]) extends Repo

case class Repository(location: String) extends Repo

object Repo {
  def cdiak(callNo: String, pages: Option[String] = None): Archive = Archive("ЦДІАК", callNo, pages)

  def dazho(callNo: String, pages: Option[String] = None): Archive = Archive("ДАЖО", callNo, pages)

  def dako(callNo: String, pages: Option[String] = None): Archive = Archive("ДАКО", callNo, pages)

  def camo(callNo: String, pages: Option[String] = None): Archive = Archive("ЦАМО РФ", callNo, pages)

  def home: Repository = Repository("Домашній архів")

  def apply(location: String): Repository = Repository(location)

  implicit val encodeEvent: Encoder[Repo] = Encoder.instance {
    case n@Archive(_, _, _) => n.asJson
    case g@Repository(_) => g.asJson
  }

  implicit val decodeEvent: Decoder[Repo] = List[Decoder[Repo]](Decoder[Archive].widen, Decoder[Repository].widen).reduceLeft(_ or _)
}

val allData: Seq[StorageItem] = Seq(
    BirthCertificate(
      "A001",
      Birth("FOO BAR BAZ",
            "FOO BAR BAZ",
            "FOO BAR BAZ",
            Place.zh,
            Date.on("2019-05-31")),
      Doc.birth(Repo.home, Place.zh, Date.on("2019-11-18")),
      "повторно",
      Seq()
    ),
    PersonalDocument("A002",
                     "Свідоцтво про зміну імені",
                     Name("FOO BAR BAZ"),
                     Doc(Seq("birth"), Repo.home, Place.zh, Date.on("2019-12-18")),
                     "",
                     Seq()),
    BirthCertificate(
      "A003",
      Birth("FOO BAR BAZ",
            "FOO BAR BAZ",
            "РFOO BAR BAZ",
            Place.zh,
            Date.on("1984-04-27")),
      Doc.birth(Repo.home, Place.zh, Date.on("1984-05-12")),
      "недійсне",
      Seq()
    ),
    PersonalDocument("A004",
                     "Вітання з новонародженим",
                     Name("FOO BAR BAZ"),
                     Doc(Seq("birth"), Repo.home, Place.zh, Date.circa("1984")),
                     "",
                     Seq()),
    BaptismRecord(
      "A005",
      Birth("FOO BAR BAZ", None, None, None, None),
      Date.on("1984-04-17"),
      Seq(),
      Doc(Seq("baptism"),
          Repo.home,
          Place.geo("50.23622104582598, 28.70584145545469", "Церква Св. Миколи"),
          Date.circa("1984")),
      "",
      Seq()
    ),
    BirthCertificate(
      "A006",
      Birth("FOO BAR BAZ",
            "FOO BAR BAZ",
            "FOO BAR BAZ",
            Place.shchyh,
            Date.on("1960-10-31")),
      Doc.birth(Repo.home, Place.shchyh, Date.on("1961-01-02")),
      "",
      Seq()
    ),
    BirthCertificate(
      "A007",
      Birth("FOO BAR BAZ",
            "FOO BAR BAZ",
            "FOO BAR BAZ",
            Place.lutsk,
            Date.on("1995-07-21")),
      Doc.birth(Repo.home, Place.lutsk, Date.on("1995-07-28")),
      "",
      Seq()
    ),
    BaptismRecord(
      "A008",
      Birth("FOO BAR BAZ",
            "FOO BAR BAZ",
            "РFOO BAR BAZ",
            Place.krop,
            Date.on("1914-02-21")),
      Date.on("1914-03-02"),
      Seq(),
      Doc.baptism(Repo.dazho("1-77-1715"), Place.vilnaChurch, None),
      "Хрещені записані нерозбірливо",
      Seq()
    ),
    BaptismRecord(
      "A009",
      Birth("FOO BAR BAZ",
            "FOO BAR BAZ",
            "FOO BAR BAZ",
            Place.shchyh,
            Date.on("1897-04-21")),
      Date.on("1897-04-19"),
      Seq(Name("FOO BAR BAZ"), Name("FOO BAR BAZ")),
      Doc.baptism(Repo.dazho("1-77-1575"), Place.vilnaChurch, None),
      "",
      Seq()
    ),
    BaptismRecord(
      "A010",
      Birth("FOO BAR BAZ",
            "FOO BAR BAZ",
            "FOO BAR BAZ",
            Place.minijky,
            Date.on("1856-08-21")),
      Date.on("1856-08-24"),
      Seq(Name("FOO BAR BAZ"), Name("FOO BAR BAZ")),
      Doc.baptism(Repo.cdiak("127-1012-3054"), Place.starosilChurch, None),
      "",
      Seq()
    ),
    BirthCertificate(
      "A011",
      Birth("FOO BAR BAZ",
            "FOO BAR BAZ",
            "FOO BAR BAZ",
            Place.shchyh,
            Date.on("1924-05-17")),
      Doc.birth(Repo.home, Place.shchyh, None),
      "",
      Seq()
    ),
    BirthCertificate(
      "A012",
      Birth("FOO BAR BAZ", "FOO BAR BAZ", "FOO BAR BAZ", Place.may, Date.on("1931-11-29")),
      Doc.birth(Repo.home, Place.may, None),
      "",
      Seq()
    ),
    PersonalDocument("A013",
                     "Колекція документів",
                     Name("FOO"),
                     Doc(Seq("birth", "wedding", "letter"), Repo.home, None, Date.range("1959", "2007")),
                     "todo",
                     Seq()),
  )

  allData.map(i => (i.uid, i.asJson.spaces4)).foreach { t =>
    println(t)
  } 

Scala Online Compiler

Write, Run & Share Scala code online using OneCompiler's Scala online compiler for free. It's one of the robust, feature-rich online compilers for Scala language, running on the latest version 2.13.8. Getting started with the OneCompiler's Scala compiler is simple and pretty fast. The editor shows sample boilerplate code when you choose language as Scala and start coding.

Read input from STDIN in Scala

OneCompiler's Scala online editor supports stdin and users can give inputs to programs using the STDIN textbox under the I/O tab. Following is a sample Scala program which takes name as input and prints hello message with your name.

object Hello {
	def main(args: Array[String]): Unit = {
	  val name = scala.io.StdIn.readLine()        // Read input from STDIN
    println("Hello " + name ) 
	}
}

About Scala

Scala is both object-oriented and functional programming language by Martin Odersky in the year 2003.

Syntax help

Variables

Variable is a name given to the storage area in order to identify them in our programs.

var or val Variable-name [: Data-Type] = [Initial Value];

Loops and conditional statements

1. If family:

If, If-else, Nested-Ifs are used when you want to perform a certain set of operations based on conditional expressions.

If

if(conditional-expression){    
//code    
} 

If-else

if(conditional-expression) {  
//code if condition is true  
} else {  
//code if condition is false  
} 

Nested-If-else

if(condition-expression1) {  
//code if above condition is true  
} else if (condition-expression2) {  
//code if above condition is true  
}  
else if(condition-expression3) {  
//code if above condition is true  
}  
...  
else {  
//code if all the above conditions are false  
}  

2. For:

For loop is used to iterate a set of statements based on a criteria.

for(index <- range){  
  // code  
} 

3. While:

While is also used to iterate a set of statements based on a condition. Usually while is preferred when number of iterations are not known in advance.

while(condition) {  
 // code 
}  

4. Do-While:

Do-while is also used to iterate a set of statements based on a condition. It is mostly used when you need to execute the statements atleast once.

do {
  // code 
} while (condition) 

Functions

Function is a sub-routine which contains set of statements. Usually functions are written when multiple calls are required to same set of statements which increases re-usuability and modularity.

def functionname(parameters : parameters-type) : returntype = {   //code
}

Note:

You can either use = or not in the function definition. If = is not present, function will not return any value.