// Copyright 2015 The Cayley Authors. 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 bolt

import (
	"encoding/json"
	"fmt"

	"github.com/barakmich/glog"
	"github.com/boltdb/bolt"
	"github.com/google/cayley/graph"
	"github.com/google/cayley/graph/proto"
)

const latestDataVersion = 2
const nilDataVersion = 1

type upgradeFunc func(*bolt.DB) error

var migrateFunctions = []upgradeFunc{
	nil,
	upgrade1To2,
}

func upgradeBolt(path string, opts graph.Options) error {
	db, err := bolt.Open(path, 0600, nil)
	defer db.Close()

	if err != nil {
		glog.Errorln("Error, couldn't open! ", err)
		return err
	}
	var version int64
	err = db.View(func(tx *bolt.Tx) error {
		version, err = getInt64ForMetaKey(tx, "version", nilDataVersion)
		return err
	})
	if err != nil {
		glog.Errorln("error:", err)
		return err
	}

	if version == latestDataVersion {
		fmt.Printf("Already at latest version: %d\n", latestDataVersion)
		return nil
	}

	if version > latestDataVersion {
		err := fmt.Errorf("Unknown data version: %d -- upgrade this tool", version)
		glog.Errorln("error:", err)
		return err
	}

	for i := version; i < latestDataVersion; i++ {
		err := migrateFunctions[i](db)
		if err != nil {
			return err
		}
		setVersion(db, i+1)
	}

	return nil
}

type v1ValueData struct {
	Name string
	Size int64
}

type v1IndexEntry struct {
	History []int64
}

func upgrade1To2(db *bolt.DB) error {
	fmt.Println("Upgrading v1 to v2...")
	tx, err := db.Begin(true)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	fmt.Println("Upgrading bucket", string(logBucket))
	lb := tx.Bucket(logBucket)
	c := lb.Cursor()
	for k, v := c.First(); k != nil; k, v = c.Next() {
		var delta graph.Delta
		err := json.Unmarshal(v, &delta)
		if err != nil {
			return err
		}
		newd := deltaToProto(delta)
		data, err := newd.Marshal()
		if err != nil {
			return err
		}
		lb.Put(k, data)
	}
	if err := tx.Commit(); err != nil {
		return err
	}
	tx, err = db.Begin(true)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	fmt.Println("Upgrading bucket", string(nodeBucket))
	nb := tx.Bucket(nodeBucket)
	c = nb.Cursor()
	for k, v := c.First(); k != nil; k, v = c.Next() {
		var vd proto.NodeData
		err := json.Unmarshal(v, &vd)
		if err != nil {
			return err
		}
		data, err := vd.Marshal()
		if err != nil {
			return err
		}
		nb.Put(k, data)
	}
	if err := tx.Commit(); err != nil {
		return err
	}

	for _, bucket := range [4][]byte{spoBucket, ospBucket, posBucket, cpsBucket} {
		tx, err = db.Begin(true)
		if err != nil {
			return err
		}
		defer tx.Rollback()
		fmt.Println("Upgrading bucket", string(bucket))
		b := tx.Bucket(bucket)
		cur := b.Cursor()
		for k, v := cur.First(); k != nil; k, v = cur.Next() {
			var h proto.HistoryEntry
			err := json.Unmarshal(v, &h)
			if err != nil {
				return err
			}
			data, err := h.Marshal()
			if err != nil {
				return err
			}
			b.Put(k, data)
		}
		if err := tx.Commit(); err != nil {
			return err
		}
	}
	return nil
}
