/*
 * Copyright 2016 The Winston Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.s13g.winston.master.channel.instance;

import com.google.common.collect.ImmutableList;
import com.s13g.winston.lib.nest.Thermostat;
import com.s13g.winston.lib.nest.data.HvacState;
import com.s13g.winston.lib.temperature.Temperature;
import com.s13g.winston.master.channel.Channel;
import com.s13g.winston.master.channel.ChannelException;
import com.s13g.winston.master.channel.ChannelValue;
import com.s13g.winston.master.channel.ReadOnlyChannelValue;

import java.util.List;
import java.util.Optional;

/**
 * A channel for a Nest thermostat.
 */
public class NestThermostatChannel implements Channel {
  /**
   * After this time, the data is considered old and needs to be refreshed if accessed again. This
   * avoid multiple requests to the multiple values here to send out a request each, even though
   * we get the data for all channels with one request to the Nest API.
   */
  private static final long MAX_DATA_AGE_MILLIS = 10 * 1000;
  private final String mChannelId;
  private final Thermostat mThermostat;

  public NestThermostatChannel(String channelId, Thermostat thermostat) {
    mChannelId = channelId;
    mThermostat = thermostat;
  }

  @Override
  public String getChannelId() {
    return mChannelId;
  }

  @Override
  public List<ChannelValue> getValues() {
    ChannelValue<String> deviceName = new ReadOnlyChannelValue<>(
        "deviceName", this::readName, "Cannot set name");
    ChannelValue<Float> ambientTempCelcius = new ReadOnlyChannelValue<>(
        "ambientTempCelsius", this::readTemperature,
        "Cannot set ambientTemp. Set targetTemp instead.");
    ChannelValue<Float> humidity = new ReadOnlyChannelValue<>(
        "humidity", this::readHumidity, "Cannot set humidity.");
    ChannelValue<String> hvacStatus = new ReadOnlyChannelValue<>(
        "hvacState", this::readHvacState, "Cannot set Hvac state.");

    return ImmutableList.of(
        deviceName,
        ambientTempCelcius,
        humidity,
        hvacStatus,
        new NestTargetTemperatureCelsiusChannel());
  }

  private String readName() throws ChannelException {
    Optional<String> deviceName = mThermostat.refresh(MAX_DATA_AGE_MILLIS).getName();
    if (!deviceName.isPresent()) {
      throw new ChannelException("Cannot read thermostat name for '" + mChannelId + "'.");
    }
    return deviceName.get();
  }

  /** Reads the temperature from the thermostat. */
  private float readTemperature() throws ChannelException {
    Optional<Temperature> ambientTemperature =
        mThermostat.refresh(MAX_DATA_AGE_MILLIS).getAmbientTemperature();
    if (!ambientTemperature.isPresent()) {
      throw new ChannelException("Cannot read ambient temperature for '" + mChannelId + "'.");
    }
    return ambientTemperature.get().get(Temperature.Unit.CELSIUS);
  }

  /** Read the humidity from the thermostat. */
  private float readHumidity() throws ChannelException {
    Optional<Float> humidity = mThermostat.refresh(MAX_DATA_AGE_MILLIS).getHumidity();
    if (!humidity.isPresent()) {
      throw new ChannelException("Cannot read humidity for '" + mChannelId + "'.");
    }
    return humidity.get();
  }

  /** Read the HVAC state from the thermostat. */
  private String readHvacState() throws ChannelException {
    Optional<HvacState> hvacState = mThermostat.refresh(MAX_DATA_AGE_MILLIS).getHvacState();
    if (!hvacState.isPresent()) {
      throw new ChannelException("Cannot read HVAC state for '" + mChannelId + "'.");
    }
    return hvacState.get().toString();

  }

  /** Read/Write channel for the thermostat's target temperature. */
  private class NestTargetTemperatureCelsiusChannel implements ChannelValue<Float> {
    @Override
    public Mode getType() {
      return Mode.READ_WRITE;
    }

    @Override
    public String getName() {
      return "targetTempCelsius";
    }

    @Override
    public void writeRaw(String valueRaw) throws ChannelException {
      try {
        write(Float.parseFloat(valueRaw));
      } catch (NumberFormatException ex) {
        throw new ChannelException("Cannot parse temperature", ex);
      }
    }

    @Override
    public void write(Float value) throws ChannelException {
      mThermostat.setTargetTemperature(new Temperature(value, Temperature.Unit.CELSIUS));
    }

    @Override
    public Float read() throws ChannelException {
      Optional<Temperature> ambientTemperature =
          mThermostat.refresh(MAX_DATA_AGE_MILLIS).getTargetTemperature();
      if (!ambientTemperature.isPresent()) {
        throw new ChannelException("Cannot read target temperature for '" + mChannelId + "'.");
      }
      return ambientTemperature.get().get(Temperature.Unit.CELSIUS);
    }
  }
}

@@ -1911,41 +1911,40 @@
     
-ChannelValue%3CString%3E deviceName =
+return ImmutableList.of(%0A       
  new
@@ -1967,25 +1967,16 @@
 Value%3C%3E(
-%0A        
 %22deviceN
@@ -2019,54 +2019,17 @@
 me%22)
-;
+,
 %0A    
-ChannelValue%3CFloat%3E ambientTempCelcius =
+   
  new
@@ -2052,25 +2052,16 @@
 Value%3C%3E(
-%0A        
 %22ambient
@@ -2100,29 +2100,37 @@
 ure,
-%0A       
  %22Can
-no
+'
 t set 
+%22 +%0A            %22
 ambi
@@ -2167,44 +2167,17 @@
 d.%22)
-;
+,
 %0A    
-ChannelValue%3CFloat%3E humidity =
+   
  new
@@ -2200,25 +2200,16 @@
 Value%3C%3E(
-%0A        
 %22humidit
@@ -2259,47 +2259,17 @@
 y.%22)
-;
+,
 %0A    
-ChannelValue%3CString%3E hvacStatus =
+   
  new
@@ -2292,25 +2292,16 @@
 Value%3C%3E(
-%0A        
 %22hvacSta
@@ -2355,124 +2355,104 @@
 e.%22)
-;%0A
+,
 %0A    
-return ImmutableList.of(%0A        deviceName,%0A        ambientTempCelcius,%0A        humidity,%0A        hvacStatus
+    new ReadOnlyChannelValue%3C%3E(%22isOnline%22, this::readIsOnline, %22Cannot set online state.%22)
 ,%0A  
@@ -3975,16 +3975,322 @@
 ring();%0A
+  %7D%0A%0A  private boolean readIsOnline() throws ChannelException %7B%0A    Optional%3CBoolean%3E isOnline = mThermostat.refresh(MAX_DATA_AGE_MILLIS).getIsOnline();%0A    if (!isOnline.isPresent()) %7B%0A      throw new ChannelException(%22Cannot read online state for '%22 + mChannelId + %22'.%22);%0A    %7D%0A    return isOnline.get();
 %0A  %7D%0A%0A  

Add isOnline state to Rest API.