module Entry exposing (main)

import Browser
import Http

import Json.Decode as D
import Json.Decode.Pipeline as P

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)

import Calculator
import NumberInput
import Select

main = Browser.element
  { init = \globals ->
    ( Context globals <| ItemSearch { searchTerm = "", searchResults = [] }
    , Cmd.none
    )
  , subscriptions = \_ -> Sub.none
  , update = update
  , view = view
  }

-- Data types

type alias SearchResult =
  { barcode : String
  , name : String
  , netUnitPrice : Float
  , bought : String
  , salesUnits : Int
  , available : Bool
  , locationName : String
  , locationId : Int
  , groupName : String
  , groupId : Int
  , taxGroupId : Int
  }

searchResultDecoder =
  D.succeed SearchResult
    |> P.required "item_barcode" D.string
    |> P.required "name" D.string
    |> P.required "unit_price" D.float
    |> P.required "bought" D.string
    |> P.required "sales_units" D.int
    |> P.required "available" D.bool
    |> P.required "location_name" D.string
    |> P.required "location_id" D.int
    |> P.required "group_name" D.string
    |> P.required "group_id" D.int
    |> P.required "tax_group_id" D.int

type alias Location =
  { id : Int
  , name : String
  }

type alias Group =
  { id : Int
  , name : String
  }

type alias TaxGroup =
  { id : Int
  , description : String
  , percentage : Float
  }

type alias NewItem =
  { barcode : String
  , name : String
  , salesUnits : Int
  , group : Group
  , location : Location
  , netUnitPrice : Float
  , taxGroup : TaxGroup
  }

type alias Context =
  { globals : Globals
  , state : State
  }

type alias Globals =
  { locations : List Location
  , groups : List Group
  , taxGroups : List TaxGroup
  }

type State
  = ItemSearch
    { searchTerm : String
    , searchResults : List SearchResult
    }
  | ItemEditor
    { barcode : String
    , name : String
    , salesUnits : NumberInput.Model Int
    , calculator : Calculator.Model
    , netUnitPrice : NumberInput.Model Float
    , grossUnitPrice : NumberInput.Model Float
    , group : Select.Model Group
    , location : Select.Model Location
    , taxGroup : Select.Model TaxGroup
    }

type Msg
  = SetSearchTerm String
  | SubmitSearch
  | ReceiveSearchResults (Result Http.Error (List SearchResult))
  | GotoItemEditor SearchResult
  | SetBarcode String
  | SetName String
  | SetSalesUnits String
  | CalculatorMsg Calculator.Msg
  | SetNetUnitPrice String
  | SetGrossUnitPrice String
  | SetGroup String
  | SetLocation String
  | SetTaxGroup String

-- Update logic: State machine etc.

update msg { globals, state } =
  let
    (state_, cmd) = updateState msg globals state
  in
    ({ globals = globals, state = state_ }, cmd)

updateState msg globals state = case state of
  ItemSearch model -> case msg of
    SetSearchTerm searchTerm ->
      (ItemSearch { model  | searchTerm = searchTerm }, Cmd.none)
    SubmitSearch ->
      ( state
      , Http.get
        { url = "/entry/api/search-items?search-term=" ++ model.searchTerm
        , expect = Http.expectJson ReceiveSearchResults <| D.list searchResultDecoder
        }
      )
    ReceiveSearchResults (Ok searchResults) ->
      (ItemSearch { model | searchResults = searchResults }, Cmd.none)
    GotoItemEditor searchResult ->
      case find (\tg -> tg.id == searchResult.taxGroupId) globals.taxGroups of
        Nothing -> (state, Cmd.none)
        Just taxGroup ->
          ( ItemEditor
            { barcode = searchResult.barcode
            , name = searchResult.name
            , calculator = Calculator.init searchResult.netUnitPrice
            , netUnitPrice = NumberInput.fromFloat searchResult.netUnitPrice
            , grossUnitPrice = NumberInput.fromFloat
              (suggestedGrossPrice searchResult.netUnitPrice taxGroup.percentage)
            , salesUnits = NumberInput.fromInt searchResult.salesUnits
            , group = Select.init (.id >> String.fromInt) (.name) { id = searchResult.groupId, name = searchResult.groupName } globals.groups
            , location = Select.init (.id >> String.fromInt) (.name) { id = searchResult.locationId, name = searchResult.locationName } globals.locations
            , taxGroup = Select.init (.id >> String.fromInt) (.description) taxGroup globals.taxGroups
            }
          , Cmd.none
          )
    _ ->
      (state, Cmd.none)
  ItemEditor model -> case msg of
    SetBarcode barcode ->
      (ItemEditor { model | barcode = barcode }, Cmd.none)
    SetName name ->
      (ItemEditor { model | name = name }, Cmd.none)
    SetSalesUnits str ->
      (ItemEditor { model | salesUnits = NumberInput.update str model.salesUnits }, Cmd.none)
    CalculatorMsg msg_ ->
      (ItemEditor { model | calculator = Calculator.update msg_ model.calculator }, Cmd.none)
    SetNetUnitPrice str ->
      ( ItemEditor { model | netUnitPrice = NumberInput.update str model.netUnitPrice }
      , Cmd.none
      )
    SetGrossUnitPrice str ->
      ( ItemEditor { model | grossUnitPrice = NumberInput.update str model.grossUnitPrice }
      , Cmd.none
      )
    SetGroup key ->
      (ItemEditor { model | group = Select.update key model.group }, Cmd.none)
    SetLocation key ->
      (ItemEditor { model | location = Select.update key model.location }, Cmd.none)
    SetTaxGroup key ->
      ( ItemEditor { model | taxGroup = Select.update key model.taxGroup }
      , Cmd.none
      )
    _ ->
      (state, Cmd.none)

suggestedGrossPrice netPrice percentage =
  roundTo 2 <| netPrice * (1 + percentage) + 0.01

-- View stuff

view { globals, state } = case state of
  ItemSearch model ->
    fieldset []
      [ legend [] [ text "Vorlage für Auftrag wählen" ]
      , Html.form [ onSubmit SubmitSearch ]
        [ div [ class "form-input" ]
          [ label [ for "search-term", title "Barcode oder Name" ] [ text "Suchbegriff" ]
          , input [ onInput SetSearchTerm, value model.searchTerm, id "search-term" ] []
          ]
        , table [] <| searchResultHeaders :: List.map viewSearchResult model.searchResults
        ]
      ]
  ItemEditor model ->
    Html.form [ method "POST" ]
      [ fieldset []
        [ legend [] [ text "Neuer Inventareintrag" ]
        , div [ class "form-input" ]
          [ label [ for "barcode" ] [ text "Barcode" ]
          , input [ onInput SetBarcode, value model.barcode, name "barcode", id "barcode" ] []
          ]
        , div [ class "form-input" ]
          [ label [ for "name" ] [ text "Name" ]
          , input [ onInput SetName, value model.name, name "name", id "name" ] []
          ]
        , div [ class "form-input" ]
          [ label [ for "sales-units" ] [ text "Eingekauft" ]
          , input [ onInput SetSalesUnits, value <| NumberInput.show model.salesUnits, name "sales-units", id "sales-units", type_ "number" ] []
          ]
        , div [ class "form-input" ]
          [ label [ for "group" ] [ text "Gruppe" ]
          , Select.view [ name "group-id", id "group" ] SetGroup model.group
          , input
            [ type_ "hidden"
            , name "group-name"
            , value <| (Select.get model.group).name
            ]
            []
          ]
        , div [ class "form-input" ]
          [ label [ for "location" ] [ text "Raum" ]
          , Select.view [ name "location-id", id "location" ] SetLocation model.location
          , input
            [ type_ "hidden"
            , name "location-name"
            , value <| (Select.get model.location).name
            ]
            []
          ]
        , div [ class "form-input" ]
          [ label [ for "tax-group" ] [ text "Steuergruppe" ]
          , Select.view [ name "tax-group-id", id "tax-group" ] SetTaxGroup model.taxGroup
          , input
            [ type_ "hidden"
            , name "tax-group-description"
            , value <| (Select.get model.taxGroup).description
            ]
            []
          ]
        , Html.map CalculatorMsg <| Calculator.view model.calculator (Select.get model.taxGroup)
        , div [ class "form-input" ]
          [ label [ for "net-unit-price" ] [ text "Einkaufspreis (Netto)" ]
          , input
            [ value <| NumberInput.show model.netUnitPrice
            , onInput SetNetUnitPrice
            , type_ "number"
            , name "net-unit-price"
            , id "net-unit-price"
            , step "0.01"
            ]
            []
          , viewSetCalculatedPriceButton model
          ]
        , div [ class "form-input" ]
          [ label [ for "gross-unit-price" ] [ text "Verkaufspreis (Brutto)" ]
          , input
            [ value <| NumberInput.show model.grossUnitPrice
            , onInput SetGrossUnitPrice
            , type_ "number"
            , name "gross-unit-price"
            , id "gross-unit-price"
            , step "0.01"
            ]
            []
          , viewSetSuggestedPriceButton model
          ]
        , button [] [ text "Auftrag anlegen" ]
        ]
      ]

viewSetCalculatedPriceButton model =
  case Calculator.getResult model.calculator (Select.get model.taxGroup) of
    Nothing ->
      button [ disabled True ] [ text "Auf ? setzen" ]
    Just calculatedPrice ->
      button
        [ onClick <| SetNetUnitPrice <| String.fromFloat calculatedPrice
        -- Prevent submitting the form
        , type_ "button"
        ]
        [ text <| "Auf " ++ String.fromFloat calculatedPrice ++ " setzen"
        ]

viewSetSuggestedPriceButton model =
  case NumberInput.get model.netUnitPrice of
    Nothing ->
      button [ disabled True ] [ text "Auf ? setzen" ]
    Just netUnitPrice ->
      let
        grossUnitPrice = suggestedGrossPrice netUnitPrice (Select.get model.taxGroup).percentage
      in
        button
          [ onClick <| SetGrossUnitPrice <| String.fromFloat grossUnitPrice
          -- Prevent submitting the form
          , type_ "button"
          ]
          [ text <| "Auf " ++ String.fromFloat grossUnitPrice ++ " setzen"
          ]

searchResultHeaders =
  tr []
    [ th [] [ text "Barcode" ]
    , th [] [ text "Name" ]
    , th [] [ text "Gruppe" ]
    , th [] [ text "Stückpreis (Netto)" ]
    , th [] [ text "Eingekauft" ]
    , th [] [ text "Kaufdatum" ]
    , th [] [ text "Raum" ]
    , th [] [ text "Aktiv?" ]
    , th [] []
    ]

viewSearchResult model =
  tr []
    [ td [] [ code [] [ text model.barcode ] ]
    , td [] [ text model.name ]
    , td [] [ text model.groupName ]
    , td [] [ text <| String.fromFloat model.netUnitPrice ]
    , td [] [ text <| String.fromInt model.salesUnits ]
    , td [] [ text model.bought ]
    , td [] [ text model.locationName ]
    , td [] [ text <| showBool model.available ]
    , td []
      [ button [ onClick <| GotoItemEditor model ] [ text "Als Vorlage verwenden" ]
      ]
    ]

calculateGarfieldPrice model =
  NumberInput.get model.netUnitPrice |> Maybe.map (\netUnitPrice ->
    roundTo 2 <| netUnitPrice * (1 + (Select.get model.taxGroup).percentage) + 0.01
  )

roundTo places x = toFloat (round <| x * 10 ^ places) / 10 ^ places

showBool b = case b of
  True -> "✅"
  False -> "❌"

find pred xs = List.head <| List.filter pred xs