/*
Copyright 33.cn Corp. 2017 All Rights Reserved.

Chaincode for Meidi Corp.

Chang history:


2017/4/12 Building successfully.
2017/4/11 init version.


*/

package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "strconv"
    "time"

    "github.com/hyperledger/fabric/core/chaincode/shim"
    pb "github.com/hyperledger/fabric/protos/peer"
)

var (
    transactionNo int = 0
    layout            = "2006-01-02 15:04:05"
    loc           *time.Location
)

func init() {
    loc, _ = time.LoadLocation("Asia/Shanghai")
}

// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}

type Bill struct {
    Acceptor         string `json:"acceptor"`
    AcceptorActno    string `json:"acceptorActno"`
    AcceptorBankId   int    `json:"acceptorBankId"`
    AcceptorBankName string `json:"acceptorBankName"`
    CanTransfer      string `json:"canTransfer"`
    DraftNumber      string `json:"draftNumber"`
    DraftState       string `json:"draftState"`
    DraftType        string `json:"draftType"`
    FaceAmount       int64  `json:"faceAmount"`
    MaturityDate     string `json:"maturityDate"`
    PayeeAccount     string `json:"payeeAccount"`
    PayeeBankName    string `json:"payeeBankName"`
    PayeeName        string `json:"payeeName"`
    RemitDate        string `json:"remitDate"`
    RemitterAccount  string `json:"remitterAccount"`
    RemitterBankName string `json:"remitterBankName"`
    RemitterName     string `json:"remitterName"`
    CreateTime       string `json:"createTime"`
    UpdateTime       string `json:"updateTime"`
}

type BillFail struct {
    DraftNumber string `json:"draftNumber"`
    ErrInfo     string `json:"errInfo"`
}

type BatchResult struct {
    SuccNum   int        `json:"succNum"`
    FailNum   int        `json:"failNum"`
    FailInfos []BillFail `json:"billFails"`
}

// Record all operation include the create/change or the bill
type Transaction struct {
    BillId     string
    Operation  string
    BillStatus string
    Time       int64
    ID         int
}

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
    return shim.Success(nil)
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    function, args := stub.GetFunctionAndParameters()
    if function == "createBill" {
        return t.createBill(stub, args)
    } else if function == "createBillBatch" {
        return t.createBillBatch(stub, args)
    } else if function == "changeBillStatus" {
        return t.changeBillStatus(stub, args)
    } else if function == "queryBill" {
        return t.queryBill(stub, args)
    } else if function == "queryTransaction" {
        return t.queryTransaction(stub, args)
    }

    return shim.Error("Invalid invoke function name. Expecting \"createBill\" \"changeBillStaus\" \"queryBill\" \"queryTransaction\"")
}

func (t *SimpleChaincode) createBill(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 17 {
        fmt.Println("createBill: args numbers is:", len(args))
        return shim.Error("createBill(): Incorrect number of arguments. Expecting 11")
    }

    orbankId, err := strconv.Atoi(args[2])
    if err != nil {
        fmt.Println("createBill: args[2] is:", args[2])
        return shim.Error("Expecting integer value for asset holding")
    }

    amount, err := strconv.ParseInt(args[8], 10, 64)
    if err != nil {
        fmt.Println("createBill: args[8] is:", args[8])
        return shim.Error("Expecting integer value for asset holding")
    }

    datetime := time.Now().In(loc).Format(layout)

    bill := Bill{
        Acceptor:         args[0],
        AcceptorActno:    args[1],
        AcceptorBankId:   orbankId,
        AcceptorBankName: args[3],
        CanTransfer:      args[4],
        DraftNumber:      args[5],
        DraftState:       args[6],
        DraftType:        args[7],
        FaceAmount:       amount,
        MaturityDate:     args[9],
        PayeeAccount:     args[10],
        PayeeBankName:    args[11],
        PayeeName:        args[12],
        RemitDate:        args[13],
        RemitterAccount:  args[14],
        RemitterBankName: args[15],
        RemitterName:     args[16],
        CreateTime:       datetime,
        UpdateTime:       datetime,
    }

    err = writeBill(stub, bill)
    if err != nil {
        fmt.Println("createBill: writeBill fail:", err.Error())
        return shim.Error("createBill(): Fail to write the bill" + err.Error())
    }

    return shim.Success(nil)
}

func (t *SimpleChaincode) createBillBatch(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 1 {
        fmt.Println("createBillBatch: args numbers is:", len(args))
        return shim.Error("createBillBatch: Incorrect number of arguments. Expecting 11")
    }

    var billInfos []Bill
    err := json.Unmarshal([]byte(args[0]), &billInfos)
    if err != nil {
        fmt.Println("getBillById: Unmarshal fail:", err.Error())
        return shim.Error("createBillBatch(): json.Unmarshal fail:" + err.Error())
    }

    datetime := time.Now().In(loc).Format(layout)

    result := BatchResult{}
    for _, bill := range billInfos {
        bill.CreateTime = datetime
        bill.UpdateTime = datetime

        err = writeBill(stub, bill)
        if err != nil {
            b := BillFail{
                DraftNumber: bill.DraftNumber,
                ErrInfo:     err.Error(),
            }
            result.FailInfos = append(result.FailInfos, b)
            result.FailNum++
            fmt.Println("createBillBatch: writeBill fail:", err.Error())
        } else {
            result.SuccNum++
        }
    }

    billBytes, err := json.Marshal(&result)
    if err != nil {
        fmt.Println("createBillBatch: Marshal fail:", err.Error())
        return shim.Error("createBillBatch(): json.Marshal fail:" + err.Error())
    }

    return shim.Success(billBytes)
}

// Change the Bill status
func (t *SimpleChaincode) changeBillStatus(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 2 {
        fmt.Println("changeBillStatus: args numbers is :", len(args))
        return shim.Error("changeBillStatus(): Incorrect number of arguments. Expecting 2")
    }
    /*
       var bill Bill

       status, err := strconv.Atoi(args[1])
       if err != nil {
           return shim.Error("changeBillStatus(): status want Integer number")
       }

       bill, _, err = getBillById(stub, args[0])
       if err != nil {

           return shim.Error("changeBillStatus(): Fail to get Bill" + err.Error())

       }
       bill.Status = status

       err = writeBill(stub, bill)

       if err != nil {
           shim.Error("changeBillStatus():fail to write Bill" + err.Error())
       }

       transaction := Transaction{BillId: args[0], Operation: "changeStatus", BillStatus: status, Time: time.Now().Unix(), ID: transactionNo}

       err = writeTransaction(stub, transaction)
       if err != nil {
           return shim.Error("changeBillStatus(): Fail to write the transaction" + err.Error())
       }

       transactionNo += 1
    */
    return shim.Success(nil)
}

// Query Bill info
func (t *SimpleChaincode) queryBill(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 1 {
        fmt.Println("queryBill: args numbers is :", len(args))
        return shim.Error("queryBill(): Incorrect number of arguments. Expecting 1")
    }

    _, billBytes, err := getBillById(stub, args[0])
    if err != nil {
        fmt.Println("getBillById fail:", err.Error())
        return shim.Error("queryBill(): Fail to get Bill" + err.Error())
    }

    return shim.Success(billBytes)

}

// Query Transaction info
func (t *SimpleChaincode) queryTransaction(stub shim.ChaincodeStubInterface, args []string) pb.Response {

    if len(args) != 1 {
        fmt.Println("queryTransaction args numbers is:", len(args))
        return shim.Error("queryTransaction(): Incorrect number of arguments. Expecting 1")
    }

    _, transBytes, err := getTransactionById(stub, args[0])
    if err != nil {
        fmt.Println("getTransactionById fail:", err.Error())
        return shim.Error("queryBill(): Fail to get Bill" + err.Error())
    }

    return shim.Success(transBytes)

}

// Write Bill info into the fabric
func writeBill(stub shim.ChaincodeStubInterface, bill Bill) error {

    billBytes, err := json.Marshal(&bill)
    if err != nil {
        fmt.Println("writeBill: Marshal fail:", err.Error())
        return errors.New("writeBill():" + err.Error())
    }
    err = stub.PutState("Bill:"+bill.DraftNumber, billBytes)
    if err != nil {
        fmt.Println("writeBill: PutState fail:", err.Error())
        return errors.New("writeBill(): PutState Error" + err.Error())
    }
    return nil
}

//Write the transaction info into the Fabric
func writeTransaction(stub shim.ChaincodeStubInterface, transaction Transaction) error {
    var tsId string
    tsBytes, err := json.Marshal(&transaction)
    if err != nil {
        fmt.Println("writeTransaction: Marshal fail:", err.Error())
        return errors.New("writeTransaction():" + err.Error())
    }
    tsId = strconv.Itoa(transaction.ID)
    if err != nil {
        fmt.Println("writeTransaction: Itoa fail:", err.Error())
        return errors.New("writeTransaction(): want Integer number")
    }
    err = stub.PutState("Transaction:"+tsId, tsBytes)
    if err != nil {
        fmt.Println("writeTransaction: PutState fail:", err.Error())
        return errors.New("writeTransaction(): PutState Error" + err.Error())
    }
    return nil
}

func getBillById(stub shim.ChaincodeStubInterface, id string) (Bill, []byte, error) {
    var bill Bill
    billBytes, err := stub.GetState("Bill:" + id)
    if err != nil {
        fmt.Println("getBillById: GetState fail:", err.Error())
        fmt.Println("Error retrieving cpBytes")
    }
    err = json.Unmarshal(billBytes, &bill)
    if err != nil {
        fmt.Println("getBillById: Unmarshal fail:", err.Error())
        fmt.Println("Error unmarshalling Bill")
    }
    return bill, billBytes, nil
}

func getTransactionById(stub shim.ChaincodeStubInterface, id string) (Transaction, []byte, error) {
    var transaction Transaction
    transBytes, err := stub.GetState("transaction:" + id)
    if err != nil {
        fmt.Println("Error retrieving transBytes")
    }
    err = json.Unmarshal(transBytes, &transaction)
    if err != nil {
        fmt.Println("Error unmarshalling transaction")
    }
    return transaction, transBytes, nil
}

func main() {
    err := shim.Start(new(SimpleChaincode))
    if err != nil {
        fmt.Printf("Error starting Simple chaincode: %s", err)
    }
}
