// ==========================================================================
// Author:  Tim Hsu
// Date:    9/11/2018
//
// Desc:    Simplified version of a blockchain, not DLT (distributed ledger technology)
//          Demonstrate creating a block, and chaining them together as you add block.
//          The chain is supported by its hash and the previous block hash, which can
//          be verified (as proof of work) to ensure data integrity
//
// References:
//          https://hackernoon.com/learn-blockchains-by-building-one-117428612f46
//          https://github.com/dvf/blockchain
// ==========================================================================

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace Blockchain
{
    class Block
    {
        public DateTime Timestamp { get; set; }
        public string PrevHash { get; set; }
        public string Content { get; set; }
    }

    class Blockchain
    {
        private string filepath = "\\BlockFolder";
        private string filename = "\\INDEX";

        public Blockchain()
        {
            // create folder
            this.filepath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + this.filepath;
            this.filename = this.filepath + this.filename;
            Directory.CreateDirectory(filepath);

            if (!File.Exists(filename))
                CreateEmptyBlock("");
        }

        private void CreateEmptyBlock(string hash_str)
        {
            Block b = new Block()
            {
                Timestamp = DateTime.UtcNow,
                PrevHash = hash_str,
                Content = string.Empty
            };
            File.WriteAllText(filename, JsonConvert.SerializeObject(b));
        }

        public void AddBlock(Block b)
        {
            string json_str = File.ReadAllText(this.filename);
            Block prev_block = JsonConvert.DeserializeObject<Block>(json_str);

            // prepare and add new block
            b.Timestamp = DateTime.UtcNow;
            b.PrevHash = prev_block.PrevHash;
            json_str = JsonConvert.SerializeObject(b);
            string hash_str = Utility.ComputeSha256Hash(json_str);

            File.WriteAllText(filename, json_str);
            File.Move(filename, this.filepath + "\\" + hash_str);
            CreateEmptyBlock(hash_str);
        }

        public Block GetBlock(int n)    // n is how far back from dummy block
        {
            string json_str = File.ReadAllText(this.filename);
            Block b = JsonConvert.DeserializeObject<Block>(json_str);

            for (int i = 0; i < n; i++)
            {
                string filename = this.filepath + "\\" + b.PrevHash;

                if (!File.Exists(filename))
                    return null;

                json_str = File.ReadAllText(filename);
                b = JsonConvert.DeserializeObject<Block>(json_str);
            }
            return b;
        }

        public bool VerifyBlockChain()  // proof of work
        {
            try
            {
                string json_str = File.ReadAllText(this.filename);
                Block b = JsonConvert.DeserializeObject<Block>(json_str);

                do
                {
                    string filename = this.filepath + "\\" + b.PrevHash;

                    if (!File.Exists(filename))
                        return false;

                    json_str = File.ReadAllText(filename);
                    string hash = Utility.ComputeSha256Hash(json_str);

                    if (b.PrevHash != hash)
                        return false;

                    b = JsonConvert.DeserializeObject<Block>(json_str);
                }
                while (!string.IsNullOrEmpty(b.PrevHash));
                return true;
            }
            catch { }
            return false;
        }
    }
}