import {HTMLBox, HTMLBoxView} from "./layout"
import {build_view} from "@bokehjs/core/build_views"
import {Plot} from "@bokehjs/models/plots"
import {Line, Step, VArea, VBar} from "@bokehjs/models/glyphs"
import * as p from "@bokehjs/core/properties"
import {div} from "@bokehjs/core/dom"
import {ColumnDataSource} from "@bokehjs/models/sources/column_data_source"
import {BasicTickFormatter, NumeralTickFormatter, TickFormatter} from "@bokehjs/models/formatters"

const red: string="#d9534f";
const green: string="#5cb85c";
const blue: string="#428bca";

export class TrendIndicatorView extends HTMLBoxView {
  model: TrendIndicator
  containerDiv: HTMLDivElement
  textDiv: HTMLDivElement
  titleDiv: HTMLDivElement
  valueDiv: HTMLDivElement
  value2Div: HTMLDivElement
  changeDiv: HTMLElement
  plotDiv: HTMLDivElement
  plot: Plot
  _value_format: string
  _value_change_format: string

  initialize(): void {
    super.initialize()
    this.containerDiv = div({style: "height:100%; width:100%;"})
    this.titleDiv = div({style: "font-size: 1em; word-wrap: break-word;"})
    this.valueDiv = div({style: "font-size: 2em"})
    this.value2Div = div({style: "font-size: 1em; opacity: 0.5; display: inline"})
    this.changeDiv = div({style: "font-size: 1em; opacity: 0.5; display: inline"})
    this.textDiv = div({}, this.titleDiv, this.valueDiv, div({}, this.changeDiv, this.value2Div))

    this.updateTitle()
    this.updateValue()
    this.updateValue2()
    this.updateValueChange()
    this.updateTextFontSize()

    this.plotDiv = div({})
    this.containerDiv = div({style: "height:100%; width:100%"}, this.textDiv, this.plotDiv)
    this.updateLayout()
  }

  connect_signals(): void {
    super.connect_signals()

    const {pos_color, neg_color} = this.model.properties
    this.on_change([pos_color, neg_color], () => this.updateValueChange())
    const {plot_color, plot_type, width, height, sizing_mode} = this.model.properties
    this.on_change([plot_color, plot_type, width, height, sizing_mode], () => this.render())

    this.connect(this.model.properties.title.change, () => this.updateTitle(true))
    this.connect(this.model.properties.value.change, () => this.updateValue(true))
    this.connect(this.model.properties.value_change.change, () => this.updateValue2(true))
    this.connect(this.model.properties.layout.change, () => this.updateLayout())
  }

  async render(): Promise<void> {
    super.render()
    this.shadow_el.appendChild(this.containerDiv)
    await this.setPlot()
  }

  private async setPlot() {
    this.plot = new Plot({
      background_fill_color: null,
      border_fill_color: null,
      outline_line_color: null,
      min_border: 0,
      sizing_mode: "stretch_both",
      toolbar_location: null,
    });

    var source = this.model.source
    if (this.model.plot_type === "line"){
      var line = new Line({
        x: { field: this.model.plot_x },
        y: { field: this.model.plot_y },
        line_width: 4,
        line_color: this.model.plot_color,
      })
      this.plot.add_glyph(line, source)
    } else if (this.model.plot_type === "step"){
      var step = new Step({
        x: { field: this.model.plot_x },
        y: { field: this.model.plot_y },
        line_width: 3,
        line_color: this.model.plot_color,
      })
      this.plot.add_glyph(step, source)
    } else if (this.model.plot_type === "area") {
      var varea = new VArea({
        x: { field: this.model.plot_x },
        y1: { field: this.model.plot_y },
        y2: 0,
        fill_color: this.model.plot_color,
        fill_alpha: 0.5,
      })
      this.plot.add_glyph(varea, source)
      var line = new Line({
        x: { field: this.model.plot_x },
        y: { field: this.model.plot_y },
        line_width: 3,
        line_color: this.model.plot_color,
      })
      this.plot.add_glyph(line, source)
    } else {
      var vbar = new VBar({
        x: { field: this.model.plot_x },
        top: { field: this.model.plot_y },
        width: 0.9,
        line_color: null,
        fill_color: this.model.plot_color
      })
      this.plot.add_glyph(vbar, source)
    }

    const view = await build_view(this.plot)
    this.plotDiv.innerHTML = ""
    view.render_to(this.plotDiv)
  }

  after_layout(): void {
    super.after_layout()
    this.updateTextFontSize()
  }

  updateTextFontSize(): void {
    this.updateTextFontSizeColumn();
  }

  updateTextFontSizeColumn(): void {
    let elWidth = this.containerDiv.clientWidth;
    let elHeight = this.containerDiv.clientHeight;
    if (this.model.layout === "column")
      elHeight = Math.round(elHeight/2)
    else
      elWidth = Math.round(elWidth/2)

    const widthTitle = this.model.title.length
    const widthValue = 2*this._value_format.length
    const widthValue2 = this._value_change_format.length+1

    const widthConstraint1 = elWidth/widthTitle*2.0
    const widthConstraint2 = elWidth/widthValue*1.8
    const widthConstraint3 = elWidth/widthValue2*2.0
    const heightConstraint = elHeight/6

    const fontSize = Math.min(widthConstraint1, widthConstraint2, widthConstraint3, heightConstraint)
    this.textDiv.style.fontSize = Math.trunc(fontSize) + "px";
    this.textDiv.style.lineHeight = "1.3";
  }

  updateTitle(update_fontsize: boolean = false): void {
    this.titleDiv.innerText = this.model.title
    if (update_fontsize)
      this.updateTextFontSize()
  }

  updateValue(update_fontsize: boolean = false): void {
    this._value_format = this.model.formatter.doFormat([this.model.value], {loc: 0})[0]
    this.valueDiv.innerText = this._value_format
    if (update_fontsize)
      this.updateTextFontSize()
  }

  updateValue2(update_fontsize: boolean = false): void {
    this._value_change_format = this.model.change_formatter.doFormat([this.model.value_change], {loc: 0})[0]
    this.value2Div.innerText = this._value_change_format
    this.updateValueChange()
    if (update_fontsize)
      this.updateTextFontSize()
  }

  updateValueChange(): void {
    if (this.model.value_change > 0) {
      this.changeDiv.innerHTML = "&#9650;"
      this.changeDiv.style.color = this.model.pos_color
    }
    else if (this.model.value_change < 0) {
      this.changeDiv.innerHTML = "&#9660;"
      this.changeDiv.style.color = this.model.neg_color
    }
    else {
      this.changeDiv.innerHTML = "&nbsp;"
      this.changeDiv.style.color = "inherit"
    }
  }

  updateLayout(): void {
    if (this.model.layout === "column"){
      this.containerDiv.style.display = "block"
      this.textDiv.style.height = "50%";
      this.textDiv.style.width = "100%";
      this.plotDiv.style.height = "50%";
      this.plotDiv.style.width = "100%";
    } else {
      this.containerDiv.style.display = "flex"
      this.textDiv.style.height = "100%";
      this.textDiv.style.width = "";
      this.plotDiv.style.height = "100%";
      this.plotDiv.style.width = "";
      this.textDiv.style.flex = "1"
      this.plotDiv.style.flex = "1"
    }
    if (this._has_finished)
      this.invalidate_layout()
  }
}

export namespace TrendIndicator {
  export type Attrs = p.AttrsOf<Props>

  export type Props = HTMLBox.Props & {
    change_formatter: p.Property<TickFormatter>
    description: p.Property<string>
    formatter: p.Property<TickFormatter>
    layout: p.Property<string>
    source: p.Property<any>
    plot_x: p.Property<string>
    plot_y: p.Property<string>
    plot_color: p.Property<string>
    plot_type: p.Property<string>
    pos_color: p.Property<string>
    neg_color: p.Property<string>
    title: p.Property<string>
    value: p.Property<number>
    value_change: p.Property<number>
  }
}

export interface TrendIndicator extends TrendIndicator.Attrs { }

export class TrendIndicator extends HTMLBox {
  properties: TrendIndicator.Props

  constructor(attrs?: Partial<TrendIndicator.Attrs>) {
    super(attrs)
  }

  static __module__ = "panel.models.trend"

  static {
    this.prototype.default_view = TrendIndicatorView;

    this.define<TrendIndicator.Props>(({Number, String, Ref}) => ({
      description:  [ String,              "" ],
      formatter:        [ Ref(TickFormatter), () => new BasicTickFormatter() ],
      change_formatter: [ Ref(TickFormatter), () => new NumeralTickFormatter() ],
      layout:       [ String,        "column" ],
      source:       [ Ref(ColumnDataSource)   ],
      plot_x:       [ String,             "x" ],
      plot_y:       [ String,             "y" ],
      plot_color:   [ String,            blue ],
      plot_type:    [ String,           "bar" ],
      pos_color:    [ String,           green ],
      neg_color:    [ String,             red ],
      title:        [ String,              "" ],
      value:        [ Number,               0 ],
      value_change: [ Number,               0 ],
    }))
  }
}
