// Copyright 2024 Google Inc. 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.
// 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 blueprint

import (
	"bytes"
	"errors"
	"fmt"
	"path/filepath"

	"github.com/akrylysov/pogreb"
	"github.com/google/blueprint/dbtools"
	"github.com/google/blueprint/gobtools"
)

//go:generate go run gobtools/codegen/gob_gen.go

const moduleActionsDbName = "module_actions.db"
const singletonActionsDbName = "singleton_actions.db"
const providersDbName = "providers.db"
const referencesDbName = "references.db"
const ninjaDbName = "ninja.db"

// @auto-generate: gob
type BuildActionCacheKey struct {
	Id string
}

func (k *BuildActionCacheKey) bytes() []byte {
	return []byte(k.Id)
}

// @auto-generate: gob
type CachedProvider struct {
	Id    *providerKey
	Value *any
}

// @auto-generate: gob
type ProviderCachedData struct {
	Providers []CachedProvider
}

// @auto-generate: gob
type ProviderHash struct {
	Id   *providerKey
	Hash uint64
}

// @auto-generate: gob
type ModuleActionCachedData struct {
	InputHash        uint64
	ProviderHashes   []ProviderHash
	OrderOnlyStrings []string
	GlobCache        []globResultCache
}

// @auto-generate: gob
type SingletonActionCachedData struct {
	ProviderHashes map[int]uint64
}

type BuildActionCache struct {
	moduleActionsDb    dbtools.KeyValueStore
	singletonActionsDb dbtools.KeyValueStore
	// Use a separate DB for providers so that we only read them when necessary.
	providerDb   dbtools.KeyValueStore
	referencesDb dbtools.KeyValueStore
	ninjaDb      dbtools.KeyValueStore
}

func (b *BuildActionCache) openForTests() error {
	b.moduleActionsDb = &dbtools.InMemKeyValueStore{}
	b.singletonActionsDb = &dbtools.InMemKeyValueStore{}
	b.providerDb = &dbtools.InMemKeyValueStore{}
	b.referencesDb = &dbtools.InMemKeyValueStore{}
	b.ninjaDb = &dbtools.InMemKeyValueStore{}
	return nil
}

func (b *BuildActionCache) open(dbPath string) error {
	return errors.Join(
		openDb(dbPath, moduleActionsDbName, &b.moduleActionsDb),
		openDb(dbPath, singletonActionsDbName, &b.singletonActionsDb),
		openDb(dbPath, providersDbName, &b.providerDb),
		openDb(dbPath, referencesDbName, &b.referencesDb),
		openDb(dbPath, ninjaDbName, &b.ninjaDb),
	)
}

func openDb(dbPath string, dbName string, dbToOpen *dbtools.KeyValueStore) error {
	if *dbToOpen != nil {
		panic(fmt.Errorf("db %s is already open", dbName))
	}
	db, err := pogreb.Open(filepath.Join(dbPath, dbName), nil)
	if err != nil {
		return err
	}
	*dbToOpen = db
	return nil
}

func (b *BuildActionCache) close() error {
	return errors.Join(
		b.moduleActionsDb.Close(),
		b.singletonActionsDb.Close(),
		b.providerDb.Close(),
		b.referencesDb.Close(),
		b.ninjaDb.Close())
}

func (b *BuildActionCache) reset(c *Context, dbPath string) error {
	return errors.Join(
		c.fs.Remove(filepath.Join(dbPath, moduleActionsDbName)),
		c.fs.Remove(filepath.Join(dbPath, singletonActionsDbName)),
		c.fs.Remove(filepath.Join(dbPath, providersDbName)),
		c.fs.Remove(filepath.Join(dbPath, referencesDbName)),
		c.fs.Remove(filepath.Join(dbPath, ninjaDbName)))
}

func (b *BuildActionCache) readModuleBuildAction(ctx gobtools.EncContext, key *BuildActionCacheKey) (*ModuleActionCachedData, error) {
	var ret ModuleActionCachedData
	if err := read(ctx, b.moduleActionsDb, key, &ret); err != nil {
		return nil, err
	}
	return &ret, nil
}

func (b *BuildActionCache) readSingletonBuildAction(ctx gobtools.EncContext, key *BuildActionCacheKey) (*SingletonActionCachedData, error) {
	var ret SingletonActionCachedData
	if err := read(ctx, b.singletonActionsDb, key, &ret); err != nil {
		return nil, err
	}
	return &ret, nil
}

func (b *BuildActionCache) readProviders(ctx gobtools.EncContext, key *BuildActionCacheKey) (*ProviderCachedData, error) {
	var ret ProviderCachedData
	if err := read(ctx, b.providerDb, key, &ret); err != nil {
		return nil, err
	}
	return &ret, nil
}

func (b *BuildActionCache) readNinjaStatements(key *BuildActionCacheKey) ([]byte, error) {
	return b.ninjaDb.Get(key.bytes())
}

func read(ctx gobtools.EncContext, db dbtools.KeyValueStore, key *BuildActionCacheKey, ret gobtools.CustomDec) error {
	v, err := db.Get(key.bytes())
	if err != nil {
		return err
	}
	if v == nil {
		return nil
	}

	buf := bytes.NewReader(v)
	return ret.Decode(ctx, buf)
}

func (b *BuildActionCache) writeModuleBuildAction(ctx gobtools.EncContext, key *BuildActionCacheKey, data *ModuleActionCachedData) error {
	return write(ctx, b.moduleActionsDb, key, data)
}

func (b *BuildActionCache) writeSingletonBuildAction(ctx gobtools.EncContext, key *BuildActionCacheKey, data *SingletonActionCachedData) error {
	return write(ctx, b.singletonActionsDb, key, data)
}

func (b *BuildActionCache) writeProviders(ctx gobtools.EncContext, key *BuildActionCacheKey, data *ProviderCachedData) error {
	return write(ctx, b.providerDb, key, data)
}

func (b *BuildActionCache) writeNinjaStatements(key *BuildActionCacheKey, data []byte) error {
	return b.ninjaDb.Put(key.bytes(), data)
}

func write(ctx gobtools.EncContext, db dbtools.KeyValueStore, key *BuildActionCacheKey, data gobtools.CustomEnc) error {
	buf := &bytes.Buffer{}
	err := data.Encode(ctx, buf)
	if err != nil {
		return err
	}
	err = db.Put(key.bytes(), buf.Bytes())
	if err != nil {
		return err
	}
	return nil
}

// @auto-generate: gob
type OrderOnlyStringsCache map[string][]string

type ModuleBuildActionCacheInput struct {
	PropertiesHash uint64
	ProvidersHash  [][]uint64
}

type Incremental interface {
	IncrementalSupported() bool
}

type IncrementalModule struct{}

func (m *IncrementalModule) IncrementalSupported() bool {
	return true
}
