/*                                                                           *\
**        _____ ____  _____   _____    __                                    **
**       / ___// __ \/  _/ | / /   |  / /   HDL Core                         **
**       \__ \/ /_/ // //  |/ / /| | / /    (c) Dolu, All rights reserved    **
**      ___/ / ____// // /|  / ___ |/ /___                                   **
**     /____/_/   /___/_/ |_/_/  |_/_____/                                   **
**                                                                           **
**      This library is free software; you can redistribute it and/or        **
**    modify it under the terms of the GNU Lesser General Public             **
**    License as published by the Free Software Foundation; either           **
**    version 3.0 of the License, or (at your option) any later version.     **
**                                                                           **
**      This library is distributed in the hope that it will be useful,      **
**    but WITHOUT ANY WARRANTY; without even the implied warranty of         **
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      **
**    Lesser General Public License for more details.                        **
**                                                                           **
**      You should have received a copy of the GNU Lesser General Public     **
**    License along with this library.                                       **
\*                                                                           */
package spinal.core

import spinal.core.Nameable.DATAMODEL_WEAK
import spinal.core.internals.Misc
import spinal.idslplugin.PostInitCallback


class Composite[T <: Nameable](val self : T, postfix : String = null, weak : Boolean = true) extends Area{
  override def childNamePriority = Nameable.USER_WEAK
  if(postfix == null) {
    setCompositeName(self, weak)
  } else {
    setCompositeName(self, postfix, weak)
  }
}


/** Sometime, creating a `Component` to define some logic is overkill.
  * For this kind of cases you can use `Area` to define a group of signals/logic.
  *
  * @example {{{
  *     val tickCounter = new Area {
  *       val tick = Reg(UInt(8 bits) init(0)
  *       tick := tick + 1
  *     }
  * }}}
  *  @see  [[https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Structuring/area.html `Area` Documentation]]
  */
trait Area extends NameableByComponent with ContextUser with OwnableRef with ScalaLocated with ValCallbackRec with OverridedEqualsHashCode  {
  if(OnCreateStack.nonEmpty){
    OnCreateStack.get match {
      case null =>
      case f => f(this)
    }
    OnCreateStack.clear()
  }
  def childNamePriority = DATAMODEL_WEAK
  val _context = ScopeProperty.capture() //TODO not as heavy
  def rework[T](body : => T): T = {
    val oldContext = ScopeProperty.captureNoClone()
    _context.restoreCloned()
    val b = body
    oldContext.restore()
    b
  }
  override private[core] def getComponent() = component

  override def valCallbackRec(obj: Any, name: String): Unit = {
    obj match {
      case component: Component =>
        if (component.parent == this.component) {
          component.setPartialName(name, childNamePriority, this)
        }
      case namable: Nameable =>
        if (!namable.isInstanceOf[ContextUser]) {
          namable.setPartialName(name, childNamePriority, this)
        } else if (namable.asInstanceOf[ContextUser].component == component){
          namable.setPartialName(name, childNamePriority, this)
        } else {
          if(component != null) for (kind <- component.children) {
            //Allow to name a component by his io reference into the parent component
            if (kind.reflectIo == namable) {
              kind.setPartialName(name, childNamePriority, this)
            }
          }
        }
      case _ =>
    }
  }

  override def toString: String = (if(component != null)component.getPath() + "/"  else "") + super.toString()
}




/**
  * Create an Area which can be assign to a data
  *
  * @example {{{
  *     class Counter extends ImplicitArea[UInt]{
  *        val cnt = Reg(UInt(8 bits)
  *        ...
  *        override def implicitValue: UInt = cnt
  *     }
  *     val myCounter = Counter()
  *     io.myUInt = myCounter
  * }}}
  */
abstract class ImplicitArea[T] extends Area {
  def implicitValue: T
}

object ImplicitArea{
  implicit def toImplicit[T](area: ImplicitArea[T]): T = area.implicitValue
}


/** Clock domains could be applied to some area of the design and then all synchronous elements instantiated into
  * this area will then implicitly use this clock domain.
  *
  *  @see [[https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Structuring/clock_domain.html Clock domains documentation]]
  */
class ClockingArea(val clockDomain: ClockDomain) extends Area with PostInitCallback {
  val ctx = ClockDomainStack.set(clockDomain)

  override def postInitCallback(): this.type = {
    ctx.restore()
    this
  }
}


/** Clock Area with a special clock enable.
  * @see [[https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Structuring/clock_domain.html#clockenablearea `ClockEnableArea` documentation]]
  */
class ClockEnableArea(clockEnable: Bool) extends Area with PostInitCallback {

  val newClockEnable : Bool = if (ClockDomain.current.config.clockEnableActiveLevel == HIGH)
    ClockDomain.current.readClockEnableWire & clockEnable
  else
    ClockDomain.current.readClockEnableWire | !clockEnable

  val clockDomain = ClockDomain.current.copy(clockEnable = newClockEnable)

  val ctx = ClockDomainStack.set(clockDomain)


  override def postInitCallback(): this.type = {
    ctx.restore()
    this
  }
}


/** Define a clock domain which is x time slower than the current clock.
  * @see [[https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Structuring/clock_domain.html#slow-area `SlowArea` documentation]]
  */
class SlowArea(val factor: BigInt, allowRounding : Boolean) extends ClockingArea(ClockDomain.current.newClockDomainSlowedBy(factor)){
  def this(factor: BigInt) = {
    this(factor, allowRounding = false)
  }

  def this(frequency: HertzNumber, allowRounding : Boolean) = {
    this((ClockDomain.current.frequency.getValue / frequency).toBigInt, allowRounding)

    val factor = ClockDomain.current.frequency.getValue / frequency
    require(allowRounding || factor.toBigInt == factor)
  }

  def this(frequency: HertzNumber) = {
    this(frequency, allowRounding = false)
  }

  def getFrequency() = (ClockDomain.current.frequency.getValue.toBigDecimal / BigDecimal(factor)) Hz
}


/** `ResetArea` allow to reset an area with a special reset combining with the current reset (cumulative).
  *  @see [[https://spinalhdl.github.io/SpinalDoc-RTD/master/SpinalHDL/Structuring/clock_domain.html#resetarea `ResetArea` documentation]]
  */
class ResetArea(reset: Bool, cumulative: Boolean) extends Area with PostInitCallback {

  val newReset: Bool = if (ClockDomain.current.config.resetActiveLevel == LOW) {
    if(cumulative) (ClockDomain.current.readResetWire & !reset) else !reset
  }else {
    if(cumulative) (ClockDomain.current.readResetWire | reset) else reset
  }

  val clockDomain = ClockDomain.current.copy(reset = newReset)
  val ctx = ClockDomainStack.set(clockDomain)

  override def postInitCallback(): this.type = {
    ctx.restore()
    this
  }
}


trait AreaObject extends Area {
  setName(this.getClass.getSimpleName.replace("$",""))
}

trait AreaRoot extends Area {
  setName("")
}