// Copyright 2015 The Weave Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "examples/daemon/common/daemon.h"

#include <weave/device.h>

#include <base/bind.h>
#include <base/memory/weak_ptr.h>

namespace {

const char kTraits[] = R"({
  "onOff": {
    "commands": {
      "setConfig": {
        "minimalRole": "user",
        "parameters": {
          "state": {
            "type": "string",
            "enum": [ "on", "standby" ]
          }
        }
      }
    },
    "state": {
      "state": {
        "type": "string",
        "enum": [ "on", "standby" ],
        "isRequired": true
      }
    }
  },
  "brightness": {
    "commands": {
      "setConfig": {
        "minimalRole": "user",
        "parameters": {
          "brightness": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          }
        }
      }
    },
    "state": {
      "brightness": {
        "type": "integer",
        "isRequired": true,
        "minimum": 0,
        "maximum": 100
      }
    }
  },
  "colorXY": {
    "commands": {
      "setConfig": {
        "minimalRole": "user",
        "parameters": {
          "colorSetting": {
            "type": "object",
            "required": [
              "colorX",
              "colorY"
            ],
            "properties": {
              "colorX": {
                "type": "number",
                "minimum": 0.0,
                "maximum": 1.0
              },
              "colorY": {
                "type": "number",
                "minimum": 0.0,
                "maximum": 1.0
              }
            },
            "additionalProperties": false
          }
        },
        "errors": ["colorOutOfRange"]
      }
    },
    "state": {
      "colorSetting": {
        "type": "object",
        "isRequired": true,
        "required": [ "colorX", "colorY" ],
        "properties": {
          "colorX": {
            "type": "number",
            "minimum": 0.0,
            "maximum": 1.0
          },
          "colorY": {
            "type": "number",
            "minimum": 0.0,
            "maximum": 1.0
          }
        }
      },
      "colorCapRed": {
        "type": "object",
        "isRequired": true,
        "required": [ "colorX", "colorY" ],
        "properties": {
          "colorX": {
            "type": "number",
            "minimum": 0.0,
            "maximum": 1.0
          },
          "colorY": {
            "type": "number",
            "minimum": 0.0,
            "maximum": 1.0
          }
        }
      },
      "colorCapGreen": {
        "type": "object",
        "isRequired": true,
        "required": [ "colorX", "colorY" ],
        "properties": {
          "colorX": {
            "type": "number",
            "minimum": 0.0,
            "maximum": 1.0
          },
          "colorY": {
            "type": "number",
            "minimum": 0.0,
            "maximum": 1.0
          }
        }
      },
      "colorCapBlue": {
        "type": "object",
        "isRequired": true,
        "required": [ "colorX", "colorY" ],
        "properties": {
          "colorX": {
            "type": "number",
            "minimum": 0.0,
            "maximum": 1.0
          },
          "colorY": {
            "type": "number",
            "minimum": 0.0,
            "maximum": 1.0
          }
        }
      }
    }
  }
})";

const char kDefaultState[] = R"({
  "colorXY": {
    "colorSetting": {"colorX": 0, "colorY": 0},
    "colorCapRed":  {"colorX": 0.674, "colorY": 0.322},
    "colorCapGreen":{"colorX": 0.408, "colorY": 0.517},
    "colorCapBlue": {"colorX": 0.168, "colorY": 0.041}
  }
})";

const char kComponent[] = "light";

}  // anonymous namespace

// LightHandler is a command handler example that shows
// how to handle commands for a Weave light.
class LightHandler {
 public:
  LightHandler() = default;
  void Register(weave::Device* device) {
    device_ = device;

    device->AddTraitDefinitionsFromJson(kTraits);
    CHECK(device->AddComponent(kComponent, {"onOff", "brightness", "colorXY"},
                               nullptr));
    CHECK(
        device->SetStatePropertiesFromJson(kComponent, kDefaultState, nullptr));
    UpdateLightState();

    device->AddCommandHandler(kComponent, "onOff.setConfig",
                              base::Bind(&LightHandler::OnOnOffSetConfig,
                                         weak_ptr_factory_.GetWeakPtr()));
    device->AddCommandHandler(kComponent, "brightness.setConfig",
                              base::Bind(&LightHandler::OnBrightnessSetConfig,
                                         weak_ptr_factory_.GetWeakPtr()));
    device->AddCommandHandler(kComponent, "colorXY.setConfig",
                              base::Bind(&LightHandler::OnColorXYSetConfig,
                                         weak_ptr_factory_.GetWeakPtr()));
  }

 private:
  void OnBrightnessSetConfig(const std::weak_ptr<weave::Command>& command) {
    auto cmd = command.lock();
    if (!cmd)
      return;
    LOG(INFO) << "received command: " << cmd->GetName();
    const auto& params = cmd->GetParameters();
    int32_t brightness_value = 0;
    if (params.GetInteger("brightness", &brightness_value)) {
      // Display this command in terminal.
      LOG(INFO) << cmd->GetName() << " brightness: " << brightness_value;

      if (brightness_state_ != brightness_value) {
        brightness_state_ = brightness_value;
        UpdateLightState();
      }
      cmd->Complete({}, nullptr);
      return;
    }
    weave::ErrorPtr error;
    weave::Error::AddTo(&error, FROM_HERE, "invalid_parameter_value",
                        "Invalid parameters");
    cmd->Abort(error.get(), nullptr);
  }

  void OnOnOffSetConfig(const std::weak_ptr<weave::Command>& command) {
    auto cmd = command.lock();
    if (!cmd)
      return;
    LOG(INFO) << "received command: " << cmd->GetName();
    const auto& params = cmd->GetParameters();
    std::string requested_state;
    if (params.GetString("state", &requested_state)) {
      LOG(INFO) << cmd->GetName() << " state: " << requested_state;

      bool new_light_status = requested_state == "on";
      if (new_light_status != light_status_) {
        light_status_ = new_light_status;

        LOG(INFO) << "Light is now: " << (light_status_ ? "ON" : "OFF");
        UpdateLightState();
      }
      cmd->Complete({}, nullptr);
      return;
    }
    weave::ErrorPtr error;
    weave::Error::AddTo(&error, FROM_HERE, "invalid_parameter_value",
                        "Invalid parameters");
    cmd->Abort(error.get(), nullptr);
  }

  void OnColorXYSetConfig(const std::weak_ptr<weave::Command>& command) {
    auto cmd = command.lock();
    if (!cmd)
      return;
    LOG(INFO) << "received command: " << cmd->GetName();
    const auto& params = cmd->GetParameters();
    const base::DictionaryValue* colorXY = nullptr;
    if (params.GetDictionary("colorSetting", &colorXY)) {
      bool updateState = false;
      double X = 0.0;
      double Y = 0.0;
      if (colorXY->GetDouble("colorX", &X)) {
        color_X_ = X;
        updateState = true;
      }

      if (colorXY->GetDouble("colorY", &Y)) {
        color_Y_ = Y;
        updateState = true;
      }

      if (updateState)
        UpdateLightState();

      cmd->Complete({}, nullptr);
      return;
    }

    weave::ErrorPtr error;
    weave::Error::AddTo(&error, FROM_HERE, "invalid_parameter_value",
                        "Invalid parameters");
    cmd->Abort(error.get(), nullptr);
  }

  void UpdateLightState() {
    base::DictionaryValue state;
    state.SetString("onOff.state", light_status_ ? "on" : "standby");
    state.SetInteger("brightness.brightness", brightness_state_);

    std::unique_ptr<base::DictionaryValue> colorXY(new base::DictionaryValue());
    colorXY->SetDouble("colorX", color_X_);
    colorXY->SetDouble("colorY", color_Y_);
    state.Set("colorXY.colorSetting", colorXY.release());
    device_->SetStateProperties(kComponent, state, nullptr);
  }

  weave::Device* device_{nullptr};

  // Simulate the state of the light.
  bool light_status_{false};
  int32_t brightness_state_{0};
  double color_X_{0.0};
  double color_Y_{0.0};
  base::WeakPtrFactory<LightHandler> weak_ptr_factory_{this};
};

int main(int argc, char** argv) {
  Daemon::Options opts;
  opts.model_id_ = "AIAAA";
  if (!opts.Parse(argc, argv)) {
    Daemon::Options::ShowUsage(argv[0]);
    return 1;
  }
  Daemon daemon{opts};
  LightHandler handler;
  handler.Register(daemon.GetDevice());
  daemon.Run();
  return 0;
}