/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.activemq.usage;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadPoolExecutor;

import org.apache.activemq.Service;
import org.apache.activemq.broker.scheduler.JobSchedulerStore;
import org.apache.activemq.store.PListStore;
import org.apache.activemq.store.PersistenceAdapter;

//fake
public class SystemUsage implements Service {

   private SystemUsage parent;
   private String name;
   private MemoryUsage memoryUsage;
   private StoreUsage storeUsage;
   private TempUsage tempUsage;
   private ThreadPoolExecutor executor;
   private JobSchedulerUsage jobSchedulerUsage;
   private String checkLimitsLogLevel = "warn";

   /**
    * True if someone called setSendFailIfNoSpace() on this particular usage manager
    */
   private boolean sendFailIfNoSpaceExplicitySet;
   private boolean sendFailIfNoSpace;
   private boolean sendFailIfNoSpaceAfterTimeoutExplicitySet;
   private long sendFailIfNoSpaceAfterTimeout = 0;

   private final List<SystemUsage> children = new CopyOnWriteArrayList<>();

   public SystemUsage() {
      this("default", null, null, null);
   }

   public SystemUsage(String name,
                      PersistenceAdapter adapter,
                      PListStore tempStore,
                      JobSchedulerStore jobSchedulerStore) {
      this.parent = null;
      this.name = name;
      this.memoryUsage = new MemoryUsage(name + ":memory");
      this.storeUsage = new StoreUsage(name + ":store", adapter);
      this.tempUsage = new TempUsage(name + ":temp", tempStore);
      this.jobSchedulerUsage = new JobSchedulerUsage(name + ":jobScheduler", jobSchedulerStore);
      this.memoryUsage.setExecutor(getExecutor());
      this.storeUsage.setExecutor(getExecutor());
      this.tempUsage.setExecutor(getExecutor());
   }

   public SystemUsage(SystemUsage parent, String name) {
      this.parent = parent;
      this.executor = parent.getExecutor();
      this.name = name;
      this.memoryUsage = new MemoryUsage(parent.memoryUsage, name + ":memory");
      this.storeUsage = new StoreUsage(parent.storeUsage, name + ":store");
      this.tempUsage = new TempUsage(parent.tempUsage, name + ":temp");
      this.jobSchedulerUsage = new JobSchedulerUsage(parent.jobSchedulerUsage, name + ":jobScheduler");
      this.memoryUsage.setExecutor(getExecutor());
      this.storeUsage.setExecutor(getExecutor());
      this.tempUsage.setExecutor(getExecutor());
   }

   public String getName() {
      return name;
   }

   public MemoryUsage getMemoryUsage() {
      return this.memoryUsage;
   }

   public StoreUsage getStoreUsage() {
      return this.storeUsage;
   }

   public TempUsage getTempUsage() {
      return this.tempUsage;
   }

   public JobSchedulerUsage getJobSchedulerUsage() {
      return this.jobSchedulerUsage;
   }

   @Override
   public String toString() {
      return "UsageManager(" + getName() + ")";
   }

   @Override
   public void start() {
      if (parent != null) {
         parent.addChild(this);
      }
      this.memoryUsage.start();
      this.storeUsage.start();
      this.tempUsage.start();
      this.jobSchedulerUsage.start();
   }

   @Override
   public void stop() {
      if (parent != null) {
         parent.removeChild(this);
      }
      this.memoryUsage.stop();
      this.storeUsage.stop();
      this.tempUsage.stop();
      this.jobSchedulerUsage.stop();
   }

   public void setSendFailIfNoSpace(boolean failProducerIfNoSpace) {
      sendFailIfNoSpaceExplicitySet = true;
      this.sendFailIfNoSpace = failProducerIfNoSpace;
   }

   public boolean isSendFailIfNoSpace() {
      if (sendFailIfNoSpaceExplicitySet || parent == null) {
         return sendFailIfNoSpace;
      } else {
         return parent.isSendFailIfNoSpace();
      }
   }

   private void addChild(SystemUsage child) {
      children.add(child);
   }

   private void removeChild(SystemUsage child) {
      children.remove(child);
   }

   public SystemUsage getParent() {
      return parent;
   }

   public void setParent(SystemUsage parent) {
      this.parent = parent;
   }

   public boolean isSendFailIfNoSpaceExplicitySet() {
      return sendFailIfNoSpaceExplicitySet;
   }

   public void setSendFailIfNoSpaceExplicitySet(boolean sendFailIfNoSpaceExplicitySet) {
      this.sendFailIfNoSpaceExplicitySet = sendFailIfNoSpaceExplicitySet;
   }

   public long getSendFailIfNoSpaceAfterTimeout() {
      if (sendFailIfNoSpaceAfterTimeoutExplicitySet || parent == null) {
         return sendFailIfNoSpaceAfterTimeout;
      } else {
         return parent.getSendFailIfNoSpaceAfterTimeout();
      }
   }

   public void setSendFailIfNoSpaceAfterTimeout(long sendFailIfNoSpaceAfterTimeout) {
      this.sendFailIfNoSpaceAfterTimeoutExplicitySet = true;
      this.sendFailIfNoSpaceAfterTimeout = sendFailIfNoSpaceAfterTimeout;
   }

   public void setName(String name) {
      this.name = name;
      this.memoryUsage.setName(name + ":memory");
      this.storeUsage.setName(name + ":store");
      this.tempUsage.setName(name + ":temp");
      this.jobSchedulerUsage.setName(name + ":jobScheduler");
   }

   public void setMemoryUsage(MemoryUsage memoryUsage) {
      if (memoryUsage.getName() == null) {
         memoryUsage.setName(this.memoryUsage.getName());
      }
      if (parent != null) {
         memoryUsage.setParent(parent.memoryUsage);
      }
      this.memoryUsage = memoryUsage;
      this.memoryUsage.setExecutor(getExecutor());
   }

   public void setStoreUsage(StoreUsage storeUsage) {
      if (storeUsage.getStore() == null) {
         storeUsage.setStore(this.storeUsage.getStore());
      }
      if (storeUsage.getName() == null) {
         storeUsage.setName(this.storeUsage.getName());
      }
      if (parent != null) {
         storeUsage.setParent(parent.storeUsage);
      }
      this.storeUsage = storeUsage;
      this.storeUsage.setExecutor(executor);
   }

   public void setTempUsage(TempUsage tempDiskUsage) {
      if (tempDiskUsage.getStore() == null) {
         tempDiskUsage.setStore(this.tempUsage.getStore());
      }
      if (tempDiskUsage.getName() == null) {
         tempDiskUsage.setName(this.tempUsage.getName());
      }
      if (parent != null) {
         tempDiskUsage.setParent(parent.tempUsage);
      }
      this.tempUsage = tempDiskUsage;
      this.tempUsage.setExecutor(getExecutor());
   }

   public void setJobSchedulerUsage(JobSchedulerUsage jobSchedulerUsage) {
      if (jobSchedulerUsage.getStore() == null) {
         jobSchedulerUsage.setStore(this.jobSchedulerUsage.getStore());
      }
      if (jobSchedulerUsage.getName() == null) {
         jobSchedulerUsage.setName(this.jobSchedulerUsage.getName());
      }
      if (parent != null) {
         jobSchedulerUsage.setParent(parent.jobSchedulerUsage);
      }
      this.jobSchedulerUsage = jobSchedulerUsage;
      this.jobSchedulerUsage.setExecutor(getExecutor());
   }

   public ThreadPoolExecutor getExecutor() {
      return this.executor;
   }

   public void setExecutor(ThreadPoolExecutor executor) {
      this.executor = executor;
      if (this.memoryUsage != null) {
         this.memoryUsage.setExecutor(this.executor);
      }
      if (this.storeUsage != null) {
         this.storeUsage.setExecutor(this.executor);
      }
      if (this.tempUsage != null) {
         this.tempUsage.setExecutor(this.executor);
      }
      if (this.jobSchedulerUsage != null) {
         this.jobSchedulerUsage.setExecutor(this.executor);
      }
   }

   public String getCheckLimitsLogLevel() {
      return checkLimitsLogLevel;
   }

   public void setCheckLimitsLogLevel(String checkLimitsLogLevel) {
      this.checkLimitsLogLevel = checkLimitsLogLevel;
   }
}
