/**
 * Judge Pattern Application
 *
 * @package    Portal
 * @category   Application
 * @author     Everett Myers <Everett@AllSportsEquine.com>
 * @copyright  2013-2021 All Sports Equine, All Rights Reserved
 * @link       https://www.AllSportsEquine.com/portal/AReact/Application.html
 * @version    1.0.0 2021-03-04
 */

// -------------------------------------------------------------------------------------------------

/**
 * Judge Pattern Application
 *
 * This is the base or root application for the portal.
 *
 */
class JudgePatternApp extends App {

  /**
   * Constructor
   *
   * The constructor for a React component is called before it is mounted. When
   * implementing the constructor for a React.Component subclass, you should
   * call super(props) before any other statement. Otherwise, this.props will be
   * undefined in the constructor, which can lead to bugs. The initialized state
   * will be set to FALSE with initializing being added to waiting state to
   * ensure initialize() will be called after componentDidMount().
   *
   * @param object props
   *
   * @return void
   */
  constructor(props) {

    // =========================================================================
    // PARENT
    // =========================================================================
    super(props)

    // =========================================================================
    // STATE TEMPLATES
    // =========================================================================
    this.state_templates = {
      form: {
        modified: false
      , data: {
          comments: ''
        }
      , error: null
      }
    }

    // =========================================================================
    // PARENT APP LINKS (note DO NOT link states here)
    // =========================================================================
    this.MainApp      = props.parentApp.MainApp
    this.JudgeApp     = props.parentApp.JudgeApp
    this.classObject  = props.parentApp.state.classObject

    // =========================================================================
    // SET THIS STATE
    // =========================================================================
    Object.assign(this.state, {
      form:                 cloneObj(this.state_templates.form)
    , pattern_id:           0
    , patternObject:        null
    , not_found:            false
    , pattern:              null
    , mansave_inprocess:    false
    , autosave_registered:  false
    })

    // =========================================================================
    // URL PATHS (things that DO NOT CHANGE this instance)
    // =========================================================================
    this.url_paths = Object.assign({
      route_base:         props.parentApp.url_paths.route_base+'/'+CONFIG.classes_uri
    , class_route_base:   props.parentApp.url_paths.route_base
    , pattern:            props.parentApp.url_paths.class+'/patterns/'+parseInt(props.parentMatch.params.pattern_id)
    }, props.parentApp.url_paths)

    // =========================================================================
    // CLASS ID WAS PASSED
    // =========================================================================
    if (typeof props.parentMatch.params.pattern_id !== 'undefined') {
      this.state.pattern_id = parseInt(props.parentMatch.params.pattern_id)
    }

  }

  /**
   * Initialize
   *
   * initialize() is invoked following a standard componentDidMount() call. It
   * may also be invoked following componentDidUpdate() if the current
   * initialized state is set to FALSE.
   *
   * This is a good place to load data from the API and check for changed props
   * from the parent. Until this method has completed a waiting for initializing
   * will be in place. This is normally extended in each child.
   *
   * @param callback callback optional
   *
   * @return Promise
   */
  initialize(callback) {

    // =========================================================================
    // SET OPTIONAL ARGUMENTS (if not passed)
    // =========================================================================
    if(typeof(callback) !== "function") var callback = () => {}

    // =========================================================================
    // GET PARENT STATES ITEMS
    // =========================================================================
    const classObject = this.classObject

    // =========================================================================
    // FIND ENTRY
    // =========================================================================
    var found   = false
    let pattern = null
    const form  = cloneObj(this.state_templates.form)
    form.data = {}
    
    // Pattern Data (Pre Fill)
    CONFIG.pattern_data.forEach( item => {
      form.data[`pattern_data_${item.name}`] = ''
    })
    
    classObject.patterns.forEach( patternObject => {
      if (patternObject.pattern_id == this.state.pattern_id) {
        found = true
        pattern = patternObject

        // Pattern Data (Pre Fill)
        CONFIG.pattern_data.forEach( item => {
          if (patternObject.getData(item.name) !== null) {
            form.data[`pattern_data_${item.name}`] = patternObject.getData(item.name)
          }
        })

        // -------------------------------------------------------------------
        // Load Marks from API
        // TODO: look into removing this, data is preloaded else where but comments?
        // -------------------------------------------------------------------
        patternObject.getChildren({
          marks:    'MarkModel'
        , comments: 'CommentModel'
        , elements: 'ElementModel'
        }, {
          filters: {
            elements_include_children: 'marks'
          }
        }).then(
          children => {

            // Place Holders
            classObject.entries.forEach( entryObject => {
              form.data[`comment_${entryObject.entry_id}`] = ''
              patternObject.elements.forEach( elementObject => {
                form.data[`comment_${entryObject.entry_id}_${elementObject.element_id}`] = ''
                if (elementObject.marks !== undefined) {
                  elementObject.marks.forEach( markObject => {
                    switch (markObject.method) {
                      case 'controlled':
                        return
                      break
                      case 'manual':
                        form.data[`mark_${entryObject.entry_id}_${elementObject.element_id}_${markObject.mark_id}`] = ''
                      break
                      case 'calculation':
                        form.data[`calc_${entryObject.entry_id}_${markObject.mark_id}`] = 0
                      break
                    }
                  })
                }
              })
            })

            let calc_entry_ids = [] // used to pre calculate
            children.marks.forEach( markObject => {
              if (markObject.pattern_id != this.state.pattern_id) return
              if ( ! calc_entry_ids.includes(markObject.entry_id)) {
                calc_entry_ids.push(markObject.entry_id)
              }
              switch (markObject.method) {
                case 'controlled':
                  return
                break
                case 'manual':
                  form.data[`mark_${markObject.entry_id}_${markObject.element_id}_${markObject.mark_id}`] = markObject[`value_${markObject.type}`]
                  if (markObject.multiples_of !== undefined) {
                    let formattedValue = form.data[`mark_${markObject.entry_id}_${markObject.element_id}_${markObject.mark_id}`]
                    formattedValue = parseFloat(formattedValue).toFixed(markObject.multiples_of.countDecimals())
                    form.data[`mark_${markObject.entry_id}_${markObject.element_id}_${markObject.mark_id}`] = formattedValue
                  }
                break
              }
            })

            children.comments.forEach( comment => {
              if (comment.element_id === undefined) {
                form.data[`comment_${comment.entry_id}`] = comment.text
              } else {
                form.data[`comment_${comment.entry_id}_${comment.element_id}`] = comment.text
              }
            })

            // Perform calculations
            calc_entry_ids.forEach( entry_id => {
              form.data = this.calculateMark(entry_id, patternObject, form.data)
            })

            this.waitingEnd('initializing', {
              patternObject:  patternObject
            , form:           form
            , pattern:        pattern
            , not_found:      false
            , initialized:    true
            })
          }
        )
      }
    })

    // =========================================================================
    // Not Found
    // =========================================================================
    if (found == false) {
      this.waitingEnd('initializing', {
        not_found:   true
      , initialized: true
      , pattern:     pattern
      })
    }

    // =========================================================================
    // Auto Save
    // =========================================================================
    const autoSaveTime = 300000 // msec (i.e. 300,000 = 5 min)
    const autoSave = () => {
      if ( ! document.getElementById('auto-save-marker-judges')) return // marker not found so stop
      if (this.state.mansave_inprocess === false && this.state.form.modified) {
        this.saveScores({
          // Placebo event
          preventDefault: () => {}
        , target: document.getElementById('score-card')
        })
      }
      setTimeout(autoSave, autoSaveTime)
    }
    if (this.state.autosave_registered === false) {
      this.setState({autosave_registered:true}, () => {
        setTimeout(autoSave, autoSaveTime)
      })
    }

  }

  /**
   * Calculate Mark
   */
  calculateMark(entry_id, patternObject, data) {
    patternObject.elements.forEach( element => {
      element.marks.forEach( mark => {
        if (mark.method != 'calculation') return
        const inst = mark.controlling_instructions
        switch(inst.action) {
          case 'sum':
            const this_mark_name = `calc_${entry_id}_${mark.mark_id}`
            data[this_mark_name] = 0
            patternObject.elements.forEach( element2 => {
              element.marks.forEach( mark2 => {
                if (inst.marks.includes(mark2.mark_id)) {
                  let val = data[`mark_${entry_id}_${element2.element_id}_${mark2.mark_id}`]
                  if (val == '') return
                  switch (mark2.type) {
                    case 'float':
                      data[this_mark_name] += parseFloat(val)
                    break
                    case 'integer':
                      data[this_mark_name] += parseInt(val)
                    break
                  }
                }
              })
            })
          break
          default:
            console.error('Unknown mark calculation '+inst.action)
          break
        }
      })
    })
    return data
  }

  /**
   * Save Scores
   */
  saveScores(e) {
    this.formHandlerSubmit(e, 
      (patternObj, response) => {
        this.state.patternObject.score_status = patternObj.score_status
        this.state.patternObject.data = patternObj.data
        if(this.state.mansave_inprocess === true) {
          window.history.back()
        } else {
          this.MainApp.setState({tranToHaltMessage:null})
          const form = this.state.form
          form.modified = false
          this.setState({form:form,mansave_inprocess:false})
        }
      },
      // failure
      response => {
        this.MainApp.setState({tranToHaltMessage:null})
        const form = this.state.form
        form.modified = false
        this.setState({form:form,mansave_inprocess:false})
        alert(response.getError())
      }
    )
  }

  // ---------------------------------------------------------------------------------------------

  /**
   * Render
   *
   * The render() method is used to create new elements for the DOM. This is
   * normally where the main <Switch> and <Route> elements are placed for this
   * application.
   *
   * @return <element>
   */
  render() {

    // =========================================================================
    // DEBUG
    // =========================================================================
    if (appDebug.includes('lifecycle')) {
      console.log('JudgePatternApp.render()')
    }

    // =========================================================================
    // RENDER REDIRECT/LOADING
    // =========================================================================
    const parentRender = super.render()
    if (parentRender != null) {
      return parentRender
    }

    // =========================================================================
    // GET PARENT STATES ITEMS
    // =========================================================================
    const classObject = this.classObject

    // =========================================================================
    // ACCOUNT NOT LOADED OR NOT INITIALIZED
    // =========================================================================
    if (classObject == null) {
      return this.renderLoading(null, 'JudgePatternApp.classObject')
    }

    // =========================================================================
    // RENDER
    // =========================================================================
    return (
      <Switch>
        <Route path={this.url_paths.pattern+'.html'} render={ p => this.renderScorecard(p)} />
        <Route path="*" render={ p => this.renderNotFound(p)} />
      </Switch>
    )

  }

  /**
   * Render Scorecard
   *
   * @return <element>
   */
  renderScorecard(props) {

    // =========================================================================
    // DEBUG
    // =========================================================================
    if (appDebug.includes('lifecycle')) {
      console.log('JudgePatternApp.renderScorecard()')
    }

    // =========================================================================
    // GET PARENT STATES ITEMS
    // =========================================================================
    const showObject    = this.JudgeApp.state.showObject
    const classObject   = this.classObject
    const patternObject = this.state.patternObject

    // =========================================================================
    // NOT LOADED YET
    // =========================================================================
    if (showObject.classes.length == 0 || this.state.pattern == null) {
      return this.renderLoading(null, 'JudgePatternApp.showObject')
    }

    // =========================================================================
    // NOT FOUND
    // =========================================================================
    if (this.state.not_found) {
      return this.renderNotFound(props)
    }

    // =========================================================================
    // DATA/VARIABLES
    // =========================================================================
    const data    = this.state.form.data
    const waiting = this.waiting('score-card-form-submit')

    // =========================================================================
    // Calculation Marks
    // =========================================================================
    const calculations = (e, mark, entry_id) => {
      
      patternObject.elements.forEach( element => {
        element.marks.forEach( mark => {
          if (mark.method != 'calculation') return
          const inst = mark.controlling_instructions
          switch(inst.action) {
            case 'sum':
              const new_form = this.state.form
              const this_mark_name = `calc_${entry_id}_${mark.mark_id}`
              new_form.data[this_mark_name] = 0
              patternObject.elements.forEach( element2 => {
                element.marks.forEach( mark2 => {
                  if (inst.marks.includes(mark2.mark_id)) {
                    let val = new_form.data[`mark_${entry_id}_${element2.element_id}_${mark2.mark_id}`]
                    if (val == '') return
                    switch (mark2.type) {
                      case 'float':
                        new_form.data[this_mark_name] += parseFloat(val)
                      break
                      case 'integer':
                        new_form.data[this_mark_name] += parseInt(val)
                      break
                    }
                  }
                })
              })
              this.setState({form:new_form})
            break
            default:
              console.error('Unknown mark calculation '+inst.action)
            break
          }
        })
      })
    }

    // =========================================================================
    // RENDER
    // =========================================================================
    return (
      <div className="judges-area">
        <div className="hidden" id="auto-save-marker-judges"></div>
        { waiting == true &&
          <div className="saving-overlay"><i className="fad fa-save"></i> <span>Saving...</span></div>
        }
        <div className="page-header">
          <h1>
            {showObject.name}<br />
            <span>{classObject.class_variety.name}</span>
          </h1>
        </div>
        <div className="workspace-container no_video">
          <form
            className="score-card standard"
            id="score-card"
            method="put"
            action={patternObject._true_self+'/marks'}
            as_model="pattern"
            error_as="alert"
            onSubmit={ e => {
              e.persist()
              this.setState({mansave_inprocess:true}, () => this.saveScores(e))
            }}
          >
            <h2>
              <i
                className="fad fa-arrow-alt-square-left fa-button-action fa-2x"
                onClick={ e => window.history.back() }
              ></i>
            </h2>
            {(CONFIG.pattern_data.length > 0) &&
              <fieldset className="info">
                <legend>Pattern</legend>
                <h2>{patternObject.name}</h2>
                {CONFIG.pattern_data.map( (item, key) => {
                  return (
                    <p key={key}>
                      <span>{item.name}: </span>
                      <input
                        key={key}
                        type={item.type}
                        name={`pattern_data_${item.name}`}
                        value={data[`pattern_data_${item.name}`]}
                        onChange={ e => this.formHandlerChange(e) }
                        onKeyDown={ e => {
                          if (e.keyCode == 13) {
                            e.preventDefault()
                            return false
                          }
                        }}
                      />
                    </p>
                  )
                })}
              </fieldset>
            }
            <table className="default full-width">
              <thead>
                <tr>
                  {this.state.userObject.hasRole('SUPER_ADMIN') == true &&
                    <th className="super_admin" title="Super Admin Data">ID</th>
                  }
                  <th>Go Order</th>
                  <th>Name</th>
                  <th>{CONFIG.nomenclatures.equine}</th>
                  {patternObject.elements.map( (element, key) => {
                    return element.marks.map( (mark, key2) => {
                      if (mark.method == 'controlled') return
                      return (
                        <th key={`${key}-${key2}`}>{mark.name}</th>
                      )
                    })
                  })}
                </tr>
              </thead>
              <tbody>
                {classObject.entries.map( (entryObj, key) => {
                  if (entryObj.entry_type_id != patternObject.entry_type_id) return
                  return (
                    <tr key={key} className={entryObj.qualified==true?'':'not-qualified'}>
                      {this.state.userObject.hasRole('SUPER_ADMIN') == true &&
                        <td className="align-center super_admin super_admin_action" title="Super Admin: Click to copy" onClick={ () => {
                          navigator.clipboard.writeText(entryObj.entry_id)
                        }}>{entryObj.entry_id}</td>
                      }
                      <td className="align-center">
                        {entryObj.qualified == true &&
                          <span>{entryObj.go_order}</span>
                        }
                        {entryObj.qualified == false &&
                          <span>Not Qualified</span>
                        }
                      </td>
                      <td>{entryObj.participant.toString()}</td>
                      <td>{entryObj.equine.barn_name}</td>
                      {patternObject.elements.map( (element, key2) => {
                        return element.marks.map( (mark, key3) => {
                          if (mark.method == 'controlled') return
                          const field_name = `mark_${entryObj.entry_id}_${element.element_id}_${mark.mark_id}`
                          if (entryObj.qualified == false) {
                            return (
                              <td key={`${key}-${key2}-${key3}`}></td>
                            )
                          }
                          if (mark.method == 'calculation') {
                            let formattedValue = data[`calc_${entryObj.entry_id}_${mark.mark_id}`]
                            if (mark.multiples_of !== undefined) {
                              formattedValue =  parseFloat(formattedValue).toFixed(mark.multiples_of.countDecimals())
                            }
                            return (
                              <td key={`${key}-${key2}-${key3}`} className="align-center">
                                <span>{formattedValue}</span>
                              </td>
                            )
                          }
                          return (
                            <td key={`${key}-${key2}-${key3}`}>
                              <input
                                type="number"
                                min={mark.min}
                                max={mark.max}
                                step={mark.multiples_of}
                                name={field_name}
                                className={mark.name}
                                value={data[field_name]}
                                onBlur={ e => {
                                  if (mark.multiples_of !== undefined && e.target.value !== '') {
                                    let formattedValue =  parseFloat(e.target.value).toFixed(mark.multiples_of.countDecimals())
                                    if (data[field_name] == formattedValue) {
                                      return
                                    }
                                    const updatedForm = this.state.form
                                    updatedForm.data[field_name] = formattedValue
                                    this.setState({form:updatedForm})
                                  }
                                }}
                                onChange={ e => {
                                  this.formHandlerChange(e, () => {
                                    const new_form = this.state.form
                                    new_form.data = this.calculateMark(entryObj.entry_id, patternObject, new_form.data)
                                    this.setState({form:new_form})
                                  })
                                }}
                                onKeyDown={ e => {
                                  if (e.keyCode == 13) {
                                    e.preventDefault()
                                    var nextRow = e.target.closest('tr').nextElementSibling
                                    if (nextRow && nextRow.querySelector(`input.${mark.name}`)) {
                                      nextRow.querySelector(`input.${mark.name}`).focus()
                                    }
                                    return false
                                  }
                                }}
                              />
                            </td>
                          )
                        })
                      })}
                    </tr>
                  )
                })}
              </tbody>
              {(classObject.entries == 0) && (
                <tfoot>
                  <tr className="no-results">
                    <td className="align-center" colSpan="999">No Entries Found</td>
                  </tr>
                </tfoot>
              )}
            </table>
            <div className="buttons">
              <button className="action" id="save-button" type="submit" form="score-card" disabled={waiting}>
                {waiting == true  && <i className="fas fa-spinner fa-spin"></i>}
                {waiting == false && <i className="fas fa-save"></i>}
                Save
              </button>
            </div>
          </form>
        </div>
      </div>
    )
  
  }

}

// EOF /////////////////////////////////////////////////////////////////////////////////////////////
