// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 datauploader contains routines upload inventory data to SSM - Inventory service
package datauploader

import (
	"encoding/json"
	"fmt"
	"path/filepath"
	"sync"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"github.com/aws/amazon-ssm-agent/agent/context"
	"github.com/aws/amazon-ssm-agent/agent/fileutil"
	"github.com/aws/amazon-ssm-agent/agent/log"
)

var (
	lock             sync.RWMutex
	contentHashStore map[string]string
)

//TODO: add unit tests

// Optimizer defines operations of content optimizer which inventory plugin makes use of
type Optimizer interface {
	UpdateContentHash(inventoryItemName, hash string) (err error)
	GetContentHash(inventoryItemName string) (hash string)
}

// Impl implements content hash optimizations for inventory plugin
type Impl struct {
	log      log.T
	location string //where the content hash data is persisted in file-systems
}

func NewOptimizerImpl(context context.T) (*Impl, error) {
	return NewOptimizerImplWithLocation(context, appconfig.InventoryRootDirName, appconfig.InventoryContentHashFileName)
}

func NewOptimizerImplWithLocation(context context.T, rootDir string, fileName string) (*Impl, error) {
	var optimizer = Impl{}
	var machineID, content string
	var err error

	optimizer.log = context.Log()

	//get machineID - return if not able to detect machineID
	if machineID, err = context.Identity().InstanceID(); err != nil {
		err = fmt.Errorf("Unable to detect machineID because of %v - this will hamper execution of inventory plugin",
			err.Error())
		return &optimizer, err
	}

	optimizer.location = filepath.Join(appconfig.DefaultDataStorePath,
		machineID,
		rootDir,
		fileName)

	contentHashStore = make(map[string]string)

	//read old content hash values from file
	if fileutil.Exists(optimizer.location) {
		optimizer.log.Debugf("Found older set of content hash used by inventory plugin - %v", optimizer.location)

		//read file
		if content, err = fileutil.ReadAllText(optimizer.location); err == nil {
			optimizer.log.Debugf("Found older set of content hash used by inventory plugin at %v - \n%v",
				optimizer.location,
				content)

			if err = json.Unmarshal([]byte(content), &contentHashStore); err != nil {
				optimizer.log.Debugf("Unable to read content hash store of inventory plugin - thereby ignoring any older values")
			}
		}
	}

	return &optimizer, nil
}

func (i *Impl) UpdateContentHash(inventoryItemName, hash string) (err error) {
	lock.Lock()
	defer lock.Unlock()

	contentHashStore[inventoryItemName] = hash

	//persist the data in file system
	dataB, _ := json.Marshal(contentHashStore)

	if _, err = fileutil.WriteIntoFileWithPermissions(i.location, string(dataB), appconfig.ReadWriteAccess); err != nil {
		err = fmt.Errorf("Unable to update content hash in file - %v because - %v", i.location, err.Error())
		return
	}

	return
}

func (i *Impl) GetContentHash(inventoryItemName string) (hash string) {
	lock.RLock()
	defer lock.RUnlock()

	var found bool

	if hash, found = contentHashStore[inventoryItemName]; !found {
		// return empty string - if there is no content hash for given inventory data type
		hash = ""
	}

	return
}
