/* (c) 2016, 2021 FFF Services. For details refers to LICENSE.txt */
#include <graphene/chain/decent_evaluator.hpp>
#include <graphene/chain/protocol/decent.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/seeder_object.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/buying_object.hpp>
#include <graphene/chain/seeder_object.hpp>
#include <graphene/chain/content_object.hpp>
#include <graphene/chain/subscription_object.hpp>
#include <graphene/chain/seeding_statistics_object.hpp>
#include <graphene/chain/transaction_detail_object.hpp>

#include <decent/encrypt/custodyutils.hpp>
#include <decent/encrypt/encryptionutils.hpp>

#include <boost/multiprecision/cpp_int.hpp>

namespace graphene { namespace chain {

namespace {

static decent::encrypt::CustodyUtils _custody_utils;

void content_payout(database& db, asset paid_price_after_exchange, const content_object& content){
   if( content.co_authors.empty() )
      db.adjust_balance( content.author, paid_price_after_exchange );
   else
   {
      asset price = paid_price_after_exchange;
      boost::multiprecision::int128_t price_for_co_author;
      for( auto const &element : content.co_authors )
      {
         price_for_co_author = ( paid_price_after_exchange.amount.value * element.second ) / 10000ll ;
         db.adjust_balance( element.first, asset( static_cast<share_type>(price_for_co_author), price.asset_id) );
         price.amount -= price_for_co_author;
      }

      if( price.amount != 0 ) {
         FC_ASSERT( price.amount > 0 );
         db.adjust_balance(content.author, price);
      }
   }
};

}

operation_result set_publishing_manager_evaluator::do_evaluate( const operation_type& o )
{try{
   for( const auto id : o.to )
      FC_ASSERT (db().find_object(id), "Account does not exist");
   FC_ASSERT( o.from == account_id_type(15) , "This operation is permitted only to DECENT account");

   return void_result();
}FC_CAPTURE_AND_RETHROW( (o) ) }

operation_result set_publishing_manager_evaluator::do_apply( const operation_type& o )
{try{
   for( auto to_id : o.to )
   {
      const account_object& to_acc = to_id(db());

      if( o.can_create_publishers == true ) {
         db().modify<account_object>(to_acc, [](account_object &ao) {
              ao.rights_to_publish.is_publishing_manager = true;

         });
         return void_result();
      }
      else
      {
         for( const account_id_type& publisher : to_acc.rights_to_publish.publishing_rights_forwarded )
         {
            auto& publisher_acc = db().get<account_object>(publisher);
            db().modify<account_object>( publisher_acc, [&](account_object& ao){
                 ao.rights_to_publish.publishing_rights_received.erase( publisher );
            });
         }
         db().modify<account_object>(to_acc, [](account_object& ao){
              ao.rights_to_publish.is_publishing_manager = false;
              ao.rights_to_publish.publishing_rights_forwarded.clear();
         });
      }
   }

   return void_result();
}FC_CAPTURE_AND_RETHROW( (o) ) }

operation_result set_publishing_right_evaluator::do_evaluate( const operation_type& o )
{try{
    const auto& from_acc = db().get<account_object>(o.from);
    for( const auto id : o.to )
       FC_ASSERT (db().find_object(id), "Account does not exist");
    FC_ASSERT( from_acc.rights_to_publish.is_publishing_manager, "Account does not have permission to give publishing rights" );

    return void_result();
}FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result set_publishing_right_evaluator::do_apply( const operation_type& o )
   {try{
      const auto& from_acc = db().get<account_object>(o.from);

      for( const account_id_type& element : o.to )
      {
         const auto& to_acc = db().get<account_object>( element );

         if(o.is_publisher)
         {
            db().modify<account_object>(from_acc, [&](account_object& ao){
                 ao.rights_to_publish.publishing_rights_forwarded.insert( element );
            });
            db().modify<account_object>(to_acc,[&](account_object& ao){
                 ao.rights_to_publish.publishing_rights_received.insert( o.from );
            });
         } else {
            db().modify<account_object>(from_acc, [&](account_object& ao){
                 ao.rights_to_publish.publishing_rights_forwarded.erase( element );
            });
            db().modify<account_object>(to_acc,[&](account_object& ao){
                 ao.rights_to_publish.publishing_rights_received.erase( o.from );
            });
         }
      }

      return void_result();
      }FC_CAPTURE_AND_RETHROW( (o) )
   }

   operation_result content_submit_evaluator::do_evaluate(const operation_type& o )
   {try{
      FC_ASSERT( (db().head_block_time() > HARDFORK_4_TIME) || (o.co_authors.size() <= 10) );

      if( o.key_parts.size() == 0 ) //simplified content
         FC_ASSERT(db().head_block_time() > HARDFORK_2_TIME);

      db().get<account_object>(o.author);
      //Submission rights feature is disabled
      //FC_ASSERT( !author_account.rights_to_publish.publishing_rights_received.empty(), "Author does not have permission to publish a content" );

      if( !o.co_authors.empty() )
      {
         // sum of basis points
         uint32_t sum_of_splits = 0;

         for( auto const &element : o.co_authors )
         {
            // test whether co-autors exist
            const auto& idx = db().get_index_type<account_index>().indices().get<graphene::db::by_id>();
            auto itr = idx.find(element.first);
            FC_ASSERT (itr != idx.end() , "Account ${account} doesn't exist.", ( "account", element.first ) );
            sum_of_splits += element.second;
         }

         // author can have unassigned payout split value.
         // In such a case, missing value is automatically calculated, and equals to remaining available basis points
         // test whether the author is included as co-author
         auto it = o.co_authors.find( o.author );

         // if author is not included in co_authors map
         if( it == o.co_authors.end() )
         {
            FC_ASSERT( sum_of_splits < 10000, "Sum of splits exceeds allowed limit ( no remaining basis points for author's payout split )." );
         }
            // if author is included in co_authors map
         else
         {
            FC_ASSERT( sum_of_splits == 10000, "Sum of splits doesn't have required value ( 10000 basis points)." );
         }
      }

      const auto& idx_asset = db().get_index_type<asset_index>().indices().get<graphene::db::by_id>();
      for( const auto& element : o.price )
      {
         const auto& itr_asset = idx_asset.find( element.price.asset_id );
         FC_ASSERT( itr_asset != idx_asset.end(), "Asset ${a} does not exist",("a",element.price) );
         FC_ASSERT( RegionCodes::s_mapCodeToName.count( element.region ) , "Invalid region code" );
      }

      FC_ASSERT( db().head_block_time() <= o.expiration);
      fc::microseconds duration = (o.expiration - db().head_block_time() );
      uint32_t days = static_cast<uint32_t>(duration.to_seconds() / 3600 / 24);
      FC_ASSERT( days != 0, "time to expiration has to be at least one day" );

      auto& idx = db().get_index_type<seeder_index>().indices().get<by_seeder>();

      const auto& content_idx = db().get_index_type<content_index>().indices().get<by_URI>();
      auto content_itr = content_idx.find( o.URI );
      if( content_itr != content_idx.end() ) // is resubmit?
      {
         is_resubmit = true;
         FC_ASSERT( content_itr->author == o.author );
         FC_ASSERT( content_itr->size == o.size );
         FC_ASSERT( content_itr->_hash == o.hash );
         FC_ASSERT( content_itr->quorum == o.quorum );
         FC_ASSERT( content_itr->key_parts.size() == o.seeders.size() );
         for( const auto& element : content_itr->key_parts )
         {
            FC_ASSERT( std::find(o.seeders.begin(), o.seeders.end(), element.first ) != o.seeders.end() );
         }
         if( content_itr->cd.valid() )
         {
            FC_ASSERT( *(content_itr->cd) == *(o.cd) );
            FC_ASSERT( content_itr->expiration == o.expiration ); // expiration can not be changed for non CDN submit
         }
         else
         {
            FC_ASSERT( (db().head_block_time() > HARDFORK_4_TIME) || (content_itr->expiration == o.expiration) );
         }

         /* Resubmit that changes other stuff is not supported.
         for ( auto &p : o.seeders ) //check if seeders exist and accumulate their prices
         {
            auto itr = idx.find( p );
            FC_ASSERT( itr != idx.end(), "seeder does not exist" );

            auto itr2 = content_itr->key_parts.begin();
            while( itr2 != content_itr->key_parts.end() )
            {
               if( itr2->first == p )
               {
                  break;
               }
               itr2++;
            }
            if( itr2 == content_itr->key_parts.end() )
               FC_ASSERT( itr->free_space > o.size ); // only newly added seeders are tested against free space

            total_price_per_day += itr-> price.amount * o.size;
         }
         FC_ASSERT( days * total_price_per
         for ( auto &p : o.seeders ) //check if seeders exist and accumulate their prices
         {
            auto itr = idx.find( p );
            FC_ASSERT( itr != idx.end(), "seeder does not exist" );

            auto itr2 = content_itr->key_parts.begin();
            while( itr2 != content_itr->key_parts.end() )
            {
               if( itr2->first == p )
               {
                  break;
               }
               itr2++;
            }
            if( itr2 == content_itr->key_parts.end() )
               FC_ASSERT( itr->free_space > o.size ); // only newly added seeders are tested against free space

            total_price_per_day += itr-> price.amount * o.size;
         }
         FC_ASSERT( days * total_price_per_day <= o.publishing_fee + content_itr->publishing_fee_escrow );*/
      } else
      {
         asset total_price_per_day;
         for ( const auto &p : o.seeders ) //check if seeders exist and accumulate their prices
         {
            const auto& itr = idx.find( p );
            FC_ASSERT( itr != idx.end(), "seeder does not exist" );
            FC_ASSERT( itr->free_space > o.size );
            total_price_per_day += itr-> price.amount * o.size;
         }
         FC_ASSERT( days * total_price_per_day <= o.publishing_fee );
      }

      return void_result();
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result content_submit_evaluator::do_apply(const operation_type& o)
   {try{
      graphene::chain::ContentObjectPropertyManager synopsis_parser(o.synopsis);
      std::string title = synopsis_parser.get<graphene::chain::ContentObjectTitle>();

      operation_result result;
      if( is_resubmit )
      {
         auto& content_idx = db().get_index_type<content_index>().indices().get<by_URI>();
         const auto& content_itr = content_idx.find( o.URI );

         graphene::chain::ContentObjectPropertyManager old_synopsis_parser(content_itr->synopsis);
         std::string old_title = old_synopsis_parser.get<graphene::chain::ContentObjectTitle>();
         auto& transaction_detail_idx = db().get_index_type<transaction_detail_index>().indices().get<by_description>();
         const auto& transaction_detail_itr = transaction_detail_idx.find( old_title );

         db().modify<transaction_detail_object>(*transaction_detail_itr,[&](transaction_detail_object& tdo) {
            tdo.m_str_description = title;
            tdo.m_timestamp = db().head_block_time();
         });

         db().modify<content_object>(*content_itr,[&](content_object& co) {
                                        std::map<uint32_t, asset> prices;
                                        for (auto const& item : o.price)
                                        {
                                           prices[item.region] = item.price;
                                        }

                                        auto it_no_regions = prices.find(RegionCodes::OO_none);
                                        if (it_no_regions != prices.end())
                                           co.price.SetSimplePrice(it_no_regions->second);
                                        else
                                        {
                                           // clearing the region code list first
                                           co.price.ClearPrices();
                                           for (auto const& price_item : prices)
                                           {
                                              co.price.SetRegionPrice(price_item.first, price_item.second);
                                           }
                                        }

                                        co.synopsis = o.synopsis;
                                        co.co_authors = o.co_authors;
                                        /*
                                        co.publishing_fee_escrow += o.publishing_fee;
                                        auto itr1 = o.seeders.begin();
                                        auto itr2 = o.key_parts.begin();
                                        co.key_parts.clear();
                                        co.last_proof.clear();
                                        while ( itr1 != o.seeders.end() && itr2 != o.key_parts.end() )
                                        {
                                           co.key_parts.emplace(std::make_pair( *itr1, *itr2 ));
                                           itr1++;
                                           itr2++;
                                        }
                                        co.quorum = o.quorum;*/
                                        if( !co.cd.valid() )
                                        {
                                          co.expiration = o.expiration;
                                        }
                                     });
      }
      else
      {
         const auto& content = db().create<content_object>([&](content_object& co)
                                     {  //create new content object and store all values from the operation
                                        co.author = o.author;
                                        co.co_authors = o.co_authors;
                                        std::map<uint32_t, asset> prices;
                                        for (auto const& item : o.price)
                                        {
                                           prices[item.region] = item.price;
                                        }

                                        auto it_no_regions = prices.find(RegionCodes::OO_none);
                                        if (it_no_regions != prices.end())
                                           co.price.SetSimplePrice(it_no_regions->second);
                                        else
                                        {
                                           for (auto const& price_item : prices)
                                           {
                                              co.price.SetRegionPrice(price_item.first, price_item.second);
                                           }
                                        }

                                        co.size = o.size;
                                        co.synopsis = o.synopsis;
                                        co.URI = o.URI;
                                        co.publishing_fee_escrow = o.publishing_fee;
                                        auto itr1 = o.seeders.begin();
                                        auto itr2 = o.key_parts.begin();
                                        while ( itr1 != o.seeders.end() && itr2 != o.key_parts.end() )
                                        {
                                           co.key_parts.emplace(std::make_pair( *itr1, *itr2 ));
                                           itr1++;
                                           itr2++;
                                        }
                                        co._hash = o.hash;
                                        co.cd = o.cd;
                                        co.quorum = o.quorum;
                                        co.expiration = o.expiration;
                                        co.created = db().head_block_time();
                                        co.times_bought = 0;
                                        co.AVG_rating = 0;
                                        co.num_of_ratings = 0;
                                     });

         result = content.id;
         db().adjust_balance(o.author,-o.publishing_fee);  //pay the escrow from author's account
         auto& idx = db().get_index_type<seeder_index>().indices().get<by_seeder>();
         // Reserve the space on seeder's boxes
         // TODO_DECENT - we should better reserve the disk space after the first PoC
         for ( const auto &p : o.seeders )
         {
            const auto& itr = idx.find( p );
            db().modify<seeder_object>( *itr, [&](seeder_object& so)
            {
               so.free_space -= o.size;
            });
            db().modify<content_object>(content, [&](content_object& co){
               co.seeder_price.emplace(std::make_pair(p, itr->price.amount));
            });
         }

         const auto& idx2 = db().get_index_type<seeding_statistics_index>().indices().get<by_seeder>();
         for( const auto& element : o.seeders )
         {
            const auto& stats = idx2.find( element );
            db().modify<seeding_statistics_object>( *stats, [](seeding_statistics_object& so){
               so.total_content_requested_to_seed += 1;
            });
         }

         auto& d = db();
         db().create<transaction_detail_object>([&o, &title, &d](transaction_detail_object& obj)
                                                {
                                                   obj.m_operation_type = (uint8_t)transaction_detail_object::content_submit;

                                                   obj.m_from_account = o.author;
                                                   obj.m_to_account = account_id_type();
                                                   obj.m_transaction_amount = o.publishing_fee;
                                                   obj.m_transaction_fee = o.fee;
                                                   obj.m_str_description = title;
                                                   obj.m_timestamp = d.head_block_time();
                                                });
      }

      return result;
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result content_cancellation_evaluator::do_evaluate(const operation_type& o)
   {
      try {
         auto& idx = db().get_index_type<content_index>().indices().get<by_URI>();
         const auto& content_itr = idx.find( o.URI );
         FC_ASSERT( content_itr != idx.end() );
         FC_ASSERT( o.author == content_itr->author );
         FC_ASSERT( content_itr->expiration > db().head_block_time() );
         FC_ASSERT( !content_itr->is_blocked );

         return void_result();
      }FC_CAPTURE_AND_RETHROW((o))
   }

   operation_result content_cancellation_evaluator::do_apply(const operation_type& o)
   {
      try {
         auto& idx = db().get_index_type<content_index>().indices().get<by_URI>();
         const auto& content_itr = idx.find( o.URI );
         db().modify<content_object>(*content_itr, [&](content_object &content_obj) {
            content_obj.is_blocked = true;
            if( content_obj.expiration > db().head_block_time() + (24 * 60 * 60) )
               content_obj.expiration = db().head_block_time() + (24 * 60 * 60);
         });

         return void_result();
      }FC_CAPTURE_AND_RETHROW((o))
   }

   operation_result request_to_buy_evaluator::do_evaluate(const operation_type& o )
   {try{
      database& d = db();

      auto& idx = d.get_index_type<content_index>().indices().get<by_URI>();
      const auto& content = idx.find( o.URI );
      FC_ASSERT( content != idx.end() );
      FC_ASSERT( o.price <= d.get_balance( o.consumer, o.price.asset_id ) );
      FC_ASSERT( content->expiration > db().head_block_time() );

      fc::optional<asset> content_price_got = content->price.GetPrice(o.region_code_from);

      FC_ASSERT( content_price_got.valid(), "content is not available for this region" );
      content_price = *content_price_got;

      FC_ASSERT( !content->is_blocked , "content has been canceled" );

      auto &range = d.get_index_type<subscription_index>().indices().get<by_from_to>();
      const auto &subscription = range.find(boost::make_tuple(o.consumer, content->author));

      /// Check whether subscription exists. If so, consumer doesn't need pay for content
      if (subscription != range.end() && subscription->expiration > d.head_block_time() ) {
         is_subscriber = true;
         return void_result();
      }
      FC_ASSERT( d.are_assets_exchangeable( o.price.asset_id(d), content_price.asset_id(d) ), "price for the content and price of the content are not exchangeable");

      /*
       * Case 1: paid in some asset, price in the same asset
       * Case 2: paid in DCT, price in MIA
       * Case 3: paid in DCT, price in UIA
       * Case 4: paid in UIA, price in DCT
       * Case 5: paid in UIA, price in MIA
       */

      paid_price = o.price;

      if( content_price.asset_id == o.price.asset_id ){ // no need to convert
         paid_price_after_conversion = paid_price;
         FC_ASSERT(paid_price_after_conversion >= content_price );
         return void_result();
      }

      asset paid_price_in_dct;
      FC_ASSERT( paid_price.asset_id(d).can_convert(paid_price, paid_price_in_dct, d) && paid_price_in_dct.asset_id == asset_id_type() );
      FC_ASSERT( content_price.asset_id(d).can_convert(paid_price_in_dct, paid_price_after_conversion, d), "cannot convert ${f} to ${t}");
      FC_ASSERT( paid_price_after_conversion.asset_id == content_price.asset_id );
      FC_ASSERT( paid_price_after_conversion >= content_price );

      return void_result();
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result request_to_buy_evaluator::do_apply(const operation_type& o )
   {try{
      database& d = db();

      const auto& idx = d.get_index_type<content_index>().indices().get<by_URI>();
      const auto& content = idx.find( o.URI );

      if( is_subscriber ) //if it is subscription, price is ignored
      {
         paid_price = asset( 0 );
         paid_price_after_conversion = asset( 0 );
      }

      if( content_price.asset_id == o.price.asset_id ){ // no need to convert
         paid_price_after_conversion = paid_price;
      }else {
         asset paid_price_in_dct = paid_price.asset_id(d).convert(paid_price, d);
         paid_price_after_conversion = content_price.asset_id(d).convert(paid_price_in_dct, d);
      }

      bool delivered = false;
      asset escrow = paid_price_after_conversion;
      if( content->key_parts.size() == 0 ){ //simplified content buying
         delivered = true;
         escrow = asset(0);
      }

      const auto& object = db().create<buying_object>([&](buying_object& bo)
                                                      { //create new buying object
                                                         bo.consumer = o.consumer;
                                                         bo.URI = o.URI;
                                                         bo.expiration_time = d.head_block_time() + 24*3600;
                                                         bo.pubKey = o.pubKey;

                                                         bo.price = escrow; // escrow, will be reset to zero
                                                         bo.paid_price_before_exchange = paid_price;
                                                         bo.paid_price_after_exchange = paid_price_after_conversion;

                                                         bo.synopsis = content->synopsis;
                                                         bo.size = content->size;
                                                         bo.created = content->created;
                                                         bo.delivered = delivered;
                                                         bo.region_code_from = o.region_code_from;
                                                         bo.expiration_or_delivery_time = db().head_block_time();
                                                      });

      d.adjust_balance( o.consumer, -paid_price );
      if(delivered) {
         content_payout(d, paid_price_after_conversion, *content);
         d.modify<content_object>( *content, []( content_object& co ){ co.times_bought++; });

         finish_buying_operation op;
         op.author = content->author;
         op.co_authors = content->co_authors;
         op.payout = paid_price_after_conversion;
         op.consumer = object.consumer;
         op.buying = object.id;

         db().push_applied_operation(op);
      }
      const auto& idx2 = d.get_index_type<seeding_statistics_index>().indices().get<by_seeder>();
      for( auto& element : content->key_parts )
      {
         const auto& stats = idx2.find( element.first );
         d.modify<seeding_statistics_object>( *stats, [](seeding_statistics_object& so){
            so.missed_delivered_keys++;
         });
      }

      db().create<transaction_detail_object>([&](transaction_detail_object& obj)
                                             {
                                                obj.m_operation_type = (uint8_t)transaction_detail_object::content_buy;

                                                const auto& idx = d.get_index_type<content_index>().indices().get<by_URI>();
                                                auto itr = idx.find(o.URI);
                                                if (itr != idx.end())
                                                {
                                                   obj.m_from_account = itr->author;
                                                   graphene::chain::ContentObjectPropertyManager synopsis_parser(itr->synopsis);
                                                   obj.m_str_description = synopsis_parser.get<graphene::chain::ContentObjectTitle>();
                                                }

                                                obj.m_to_account = o.consumer;
                                                obj.m_transaction_amount = paid_price;
                                                obj.m_transaction_fee = o.fee;
                                                obj.m_timestamp = d.head_block_time();
                                             });

      return object.id;
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result deliver_keys_evaluator::do_evaluate(const operation_type& o )
   {try{
      const auto& buying = db().get<buying_object>(o.buying);
      auto& idx = db().get_index_type<content_index>().indices().get<by_URI>();

      const auto& content = idx.find( buying.URI );
      FC_ASSERT( content != idx.end() );

      auto& sidx = db().get_index_type<seeder_index>().indices().get<by_seeder>();
      const auto& seeder = sidx.find(o.seeder);

      const auto& seeder_pubKey = seeder->pubKey;
      const auto& buyer_pubKey = buying.pubKey;
      const auto& firstK = content->key_parts.at( o.seeder );
      const auto& secondK = o.key;
      const auto& proof = o.proof;
      if(!(db().get_node_properties().skip_flags&db().skip_undo_history_check)) {
         FC_ASSERT(decent::encrypt::verify_delivery_proof(proof, firstK, secondK, seeder_pubKey, buyer_pubKey));
      }

      return void_result();
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result deliver_keys_evaluator::do_apply(const operation_type& o )
   {try{
      //start with getting the buying and content objects...
      const auto& buying = db().get<buying_object>(o.buying);
      bool expired = ( buying.expiration_time < db().head_block_time() );
      auto& idx = db().get_index_type<content_index>().indices().get<by_URI>();
      const auto& content = idx.find( buying.URI );
      bool delivered;
      // if the response (key particle) has not been seen before, note it
      if( std::find(buying.seeders_answered.begin(), buying.seeders_answered.end(), o.seeder) == buying.seeders_answered.end() )
      {
         db().modify<buying_object>(buying, [&](buying_object& bo){
            bo.seeders_answered.push_back( o.seeder );
            bo.key_particles.push_back( decent::encrypt::Ciphertext(o.key) );
         });

         const auto& idx2 = db().get_index_type<seeding_statistics_index>().indices().get<by_seeder>();
         const auto& stats = idx2.find( o.seeder );
         db().modify<seeding_statistics_object>( *stats, [](seeding_statistics_object& so){
            so.missed_delivered_keys--;
            so.total_delivered_keys++;
         } );
      }
      delivered = buying.seeders_answered.size() >= content->quorum;
      //if the content has already been delivered or expired, just note the key particles and go on
      if( buying.delivered || buying.expired )
         return void_result();
      //The content just has been successfuly delivered, take care of the payment
      if( delivered )
      {
         db().modify<content_object>( *content, []( content_object& co ){ co.times_bought++; });

         content_payout(db(), buying.paid_price_after_exchange, *content);

         db().modify<buying_object>(buying, [&](buying_object& bo){
              bo.price.amount = 0;
              bo.delivered = true;
              bo.expiration_or_delivery_time = db().head_block_time();
         });

         finish_buying_operation op;
         op.author = content->author;
         op.co_authors = content->co_authors;
         op.payout = buying.paid_price_after_exchange;
         op.consumer = buying.consumer;
         op.buying = buying.id;

         db().push_applied_operation(op);
      } else if (expired) //the content just expired, clean up
      {
         db().buying_expire(buying);
      }

      return void_result();
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result leave_rating_evaluator::do_evaluate(const operation_type& o )
   {try{
      //check in buying history if the object exists
      auto& idx = db().get_index_type<content_index>().indices().get<by_URI>();
      const auto& content = idx.find( o.URI );
      auto& bidx = db().get_index_type<buying_index>().indices().get<by_consumer_URI>();
      const auto& bo = bidx.find( std::make_tuple(o.consumer, o.URI) );
      FC_ASSERT( content != idx.end() && bo != bidx.end() );
      FC_ASSERT( bo->delivered, "not delivered" );
      FC_ASSERT( !bo->rated_or_commented, "already rated or commented" );

      return void_result();
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result leave_rating_evaluator::do_apply(const operation_type& o )
   {try{
      //adjust content statistics
      auto& bidx = db().get_index_type<buying_index>().indices().get<by_consumer_URI>();
      const auto& bo = bidx.find( std::make_tuple(o.consumer, o.URI) );
      auto& idx = db().get_index_type<content_index>().indices().get<by_URI>();
      const auto& content = idx.find( o.URI );

      db().modify<buying_object>( *bo, [&]( buying_object& b ){
           b.rated_or_commented = true;
           b.rating = static_cast<uint32_t>(o.rating);
           b.comment = o.comment;
      });

      db().modify<content_object> ( *content, [&](content_object& co){
           co.AVG_rating = (co.AVG_rating * co.num_of_ratings + static_cast<uint32_t>(o.rating) * 1000) / (co.num_of_ratings + 1);
           co.num_of_ratings++;
      });

      auto& d = db();
      db().create<transaction_detail_object>([&o, &d](transaction_detail_object& obj)
                                             {
                                                obj.m_operation_type = (uint8_t)transaction_detail_object::content_rate;

                                                const auto& idx = d.get_index_type<content_index>().indices().get<by_URI>();
                                                auto itr = idx.find(o.URI);
                                                if (itr != idx.end())
                                                {
                                                   obj.m_to_account = itr->author;

                                                   graphene::chain::ContentObjectPropertyManager synopsis_parser(itr->synopsis);
                                                   obj.m_str_description = synopsis_parser.get<graphene::chain::ContentObjectTitle>();
                                                }

                                                obj.m_from_account = o.consumer;

                                                obj.m_transaction_amount = asset();
                                                obj.m_transaction_fee = o.fee;
                                                obj.m_str_description = std::to_string(o.rating) + " (" + obj.m_str_description + ")";
                                                obj.m_timestamp = d.head_block_time();
                                             });

      return void_result();
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result ready_to_publish_obsolete_evaluator::do_evaluate(const operation_type& o)
   {try{
      return void_result();
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result ready_to_publish_obsolete_evaluator::do_apply(const operation_type& o)
   {try{
      auto& idx = db().get_index_type<seeder_index>().indices().get<by_seeder>();
      const auto& sor = idx.find( o.seeder );
      operation_result result;
      if( sor == idx.end() ) { //this is initial publish request
         const seeding_statistics_object &sso = db().create<seeding_statistics_object>([&o](seeding_statistics_object &sso) {
              sso.seeder = o.seeder;
              sso.total_upload = 0;
         });
         result = sso.id;
         db().create<seeder_object>([&](seeder_object& so) {
              so.seeder = o.seeder;
              so.free_space = o.space;
              so.pubKey = o.pubKey;
              so.price = asset(o.price_per_MByte);
              so.expiration = db().head_block_time() + DECENT_RTP_VALIDITY;
              so.ipfs_ID = o.ipfs_ID;
              so.stats = sso.id;
         });
      } else
      { //this is republish case
         db().modify<seeder_object>(*sor,[&](seeder_object &so) {
            so.free_space = o.space;
            so.price = asset(o.price_per_MByte);
            so.pubKey = o.pubKey;
            so.expiration = db().head_block_time() + 24 * 3600;
            so.ipfs_ID = o.ipfs_ID;

         });
      }

      return result;
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result ready_to_publish_evaluator::do_evaluate(const operation_type& o )
   {try{
      FC_ASSERT(db().head_block_time() >= HARDFORK_1_TIME );
      return void_result();
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result ready_to_publish_evaluator::do_apply(const operation_type& o )
   {try{
      auto& idx = db().get_index_type<seeder_index>().indices().get<by_seeder>();
      const auto& sor = idx.find( o.seeder );
      operation_result result;
      if( sor == idx.end() ) { //this is initial publish request
         const seeding_statistics_object &sso = db().create<seeding_statistics_object>([&o](seeding_statistics_object &sso) {
              sso.seeder = o.seeder;
              sso.total_upload = 0;
         });
         result = sso.id;
         db().create<seeder_object>([&](seeder_object& so) {
              so.seeder = o.seeder;
              so.free_space = o.space;
              so.pubKey = o.pubKey;
              so.price = asset(o.price_per_MByte);
              so.expiration = db().head_block_time() + DECENT_RTP_VALIDITY;
              so.ipfs_ID = o.ipfs_ID;
              so.stats = sso.id;
              if( o.region_code.valid() )
                 so.region_code = *o.region_code;
              else
                 so.region_code = "";
         });
      } else
      { //this is republish case
         db().modify<seeder_object>(*sor,[&](seeder_object &so) {
              so.free_space = o.space;
              so.price = asset(o.price_per_MByte);
              so.pubKey = o.pubKey;
              so.expiration = db().head_block_time() + 24 * 3600;
              so.ipfs_ID = o.ipfs_ID;

              if( o.region_code.valid() )
                 so.region_code = *o.region_code;
              else
                 so.region_code = "";
         });
      }

      return result;
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result proof_of_custody_evaluator::do_evaluate(const operation_type& o )
   {try{
      auto& idx = db().get_index_type<content_index>().indices().get<by_URI>();
      const auto& content = idx.find( o.URI );
      FC_ASSERT( content != idx.end(), "content not found" );
      FC_ASSERT( content->expiration > db().head_block_time(), "content expired" );
      //verify that the seed is not too old...
      if (o.proof.valid() )
      {
         auto& proof = *o.proof;
         fc::ripemd160 bid = db().get_block_id_for_num(proof.reference_block);
         FC_ASSERT(bid == proof.seed,"Block ID does not match; wrong chain?");
         FC_ASSERT(db().head_block_num() <= proof.reference_block + 6,"Block reference is too old");
      }
      //
      FC_ASSERT( content->cd.valid() == o.proof.valid() );

      if(!(db().get_node_properties().skip_flags&db().skip_validate)) {
         FC_ASSERT( !(content->cd.valid() ) || _custody_utils.verify_by_miner( *(content->cd), *(o.proof) ) == 0, "Invalid proof of custody" );
      }
      //ilog("proof_of_custody OK");

      return void_result();
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result proof_of_custody_evaluator::do_apply(const operation_type& o )
   {try{
      //get the seeder and content
      auto& idx = db().get_index_type<content_index>().indices().get<by_URI>();
      const auto& content = idx.find( o.URI );
      const auto& sidx = db().get_index_type<seeder_index>().indices().get<by_seeder>();
      const auto& sitr = sidx.find(o.seeder);
      FC_ASSERT(sitr!=sidx.end(), "seeder not found");
      const seeder_object& seeder = *sitr;

      auto last_proof = content->last_proof.find( o.seeder );
      if( last_proof == content->last_proof.end() ) //initial PoR
      {
         //the initial proof, no payments yet
         db().modify<content_object>(*content, [&](content_object& co){
              co.last_proof.emplace(std::make_pair(o.seeder, db().head_block_time()));
         });

         const auto& idx2 = db().get_index_type<seeding_statistics_index>().indices().get<by_seeder>();
         const auto& stats = idx2.find( o.seeder );
         db().modify<seeding_statistics_object>( *stats, [&content](seeding_statistics_object& so){
            so.total_content_seeded += ( content->size * 1000 * 1000 ) / content->key_parts.size();
            so.num_of_content_seeded++;
            if( so.total_content_requested_to_seed > 0 )
               so.total_content_requested_to_seed -= 1;
         });
      }else{
         const auto& idx2 = db().get_index_type<seeding_statistics_index>().indices().get<by_seeder>();
         const auto& stats = idx2.find( o.seeder );
         db().modify<seeding_statistics_object>( *stats, [](seeding_statistics_object& so){
            so.num_of_pors++;
         });
         //recurrent PoR, calculate payment
         //the PoR shall be ideally broadcasted once per 24h. if the seeder pushes them too often, he is penalized by a
         // loss factor equal to one forth of the time remaining to 24h. E.g. by pushing it in 12h he is penalized by
         // loss = (12/24)/4 = 12,5%; if it is pushed in 18h (i.e. 6 hours prematurely) the loss = (6/24)/4=6,25%.
         fc::microseconds diff = db().head_block_time() - last_proof->second;
         if( diff > fc::days( 1 ) )
            diff = fc::days( 1 ) ;
         uint64_t ratio = 10000 * diff.count() / fc::days( 1 ).count();
         uint64_t loss = ( 10000 - ratio ) / 4;
         uint64_t total_reward_ratio = ( ratio * ( 10000 - loss ) ) / 10000;
         asset reward(0);
         if (db().head_block_num() > 404727) {
            const auto& itr = content->seeder_price.find(o.seeder);
            FC_ASSERT(itr != content->seeder_price.end());
            reward.amount = itr->second * total_reward_ratio * content->size / 10000;
         }
         else
            reward.amount = seeder.price.amount * total_reward_ratio * content->size / 10000 ;
         //Fix due to block halt at #404726
         if( db().head_block_num() > 404727 && reward.amount > content->publishing_fee_escrow.amount ) {
            reward.amount = std::max(int64_t(0), content->publishing_fee_escrow.amount.value );
         }
         //take care of the payment
         db().modify<content_object>( *content, [&] (content_object& co ){
            co.last_proof[o.seeder] = db().head_block_time();
            co.publishing_fee_escrow -= reward;
         });
         db().adjust_balance(seeder.seeder, reward );
         pay_seeder_operation op;
         op.author = content->author;
         op.seeder = seeder.seeder;
         op.payout = reward;
         db().push_applied_operation(op);
      }

      return void_result();
   }FC_CAPTURE_AND_RETHROW( (o) ) }

   operation_result report_stats_evaluator::do_evaluate(const operation_type& o)
   {
	   try {
         for (const auto& item : o.stats)
         {
            FC_ASSERT( db().find_object(item.first), "Invalid seeder account specified." );
         }
	   }FC_CAPTURE_AND_RETHROW((o))

      return void_result();
   }

   operation_result report_stats_evaluator::do_apply(const operation_type& o)
   {
      try {
         auto& idx = db().get_index_type<seeding_statistics_index>().indices().get<by_seeder>();
         for (const auto& item : o.stats)
         {
            const auto &so = idx.find(item.first);
            db().modify<seeding_statistics_object>(*so, [&item](seeding_statistics_object &sso) {
               sso.total_upload += item.second;
            });
         }
      }FC_CAPTURE_AND_RETHROW((o))

      return void_result();
   }

}} // graphene::chain


/*
 *
 *    data: 2019-5-20
 *    auther : Ma Mengyu@National University of Defense Technology
 *    e-mail : mamengyu10@nudt.edu.cn
 *    description :
 *    Multi-Thread Visualization Server
 *    run :
 *
 */

#include <ogrsf_frmts.h>
#include <ogr_p.h>
#include "crow.h"
#include <cpl_conv.h>
#include <cpl_string.h>
#include "Redis.h"
#include <stdio.h>
#include <cstring>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <dirent.h>
#include <omp.h>
#include <sys/time.h>
#include <regex.h>
#include <math.h>
#include <stdlib.h>
#include <png.h>
#include <stdlib.h>
#include <stack>
#include <signal.h>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/foreach.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#define MAX_NODE_SIZE	8
#define MAX_DOUBLE	1000000000

namespace bg	= boost::geometry;
namespace bgi	= boost::geometry::index;
namespace bgm	= boost::geometry::model;
namespace bi	= boost::interprocess;

using namespace std;
#define TILE_SIZE	256
#define L		20037508.34

/**
 * @brief Startup parameters
 */
class HiVisionPara
{
public:
	char	*shpPath	= NULL;         /* Path of the input shapefiles */
	char	*indexPath	= NULL;         /* Path to store the spatial indexes */
	char	*patternPath	= NULL;         /* Path of the input patterns */
	char	*redisHost	= NULL;         /* Host IP of Redis */
	int	redisPort	= 6379;         /* Host Port of Redis */
	int	servicePort	= 10080;        /* Port of the HiVision service */
};
HiVisionPara hvpara;

/**
 * @brief Split a string into a list where each word is a list item
 * @param argv          Input string
 * @param result[]  Output Word list
 * @param flag          Separator used to split the string
 * @param count         Output Word list length
 *
 */
void GetList( char* argv, char* result[], char* flag, int & count )
{
	char	* string = strdup( argv );
	char	* p;
	int	i = 0;
	while ( (p = strsep( &string, flag ) ) != NULL )
	{
		result[i] = p;
		i++;
	}
	result[i]	= string;
	count		= i;
}

/**
 * @brief Create Spatial Indexes and Write Dataset meta-data for Linestring Datasets
 * @param shpFile       Input Linestring shpfile path
 * @param outIndex      Output spatial index path
 * @param redishost     Host IP of Redis
 * @param rediskey      Data id
 * @param redisport     Host Port of Redis
 *
 */
bool Linestring( char* shpFile, char* outIndex, char* redishost, char* rediskey, int redisport )
{
	/* Record the running time of the program */
	struct timeval	t1, t2;
	double		timeuse;
	gettimeofday( &t1, NULL );

	/* Init */
	typedef bgm::d2::point_xy<double>								point;
	typedef bgm::segment<point>									segment;
	typedef bgi::quadratic<MAX_NODE_SIZE>								params;
	typedef bgi::indexable<segment>									indexable_segment;
	typedef bgi::equal_to<segment>									equal_to_segment;
	typedef bi::allocator<segment, bi::managed_mapped_file::segment_manager>			allocator_segment;
	typedef bgi::rtree<segment, params, indexable_segment, equal_to_segment, allocator_segment>	rtree_segment;
	long	size	= 300000; /* Max size (MB) of the spatial index mapped file */
	double	minXOut = MAX_DOUBLE, minYOut = MAX_DOUBLE, maxXout = -1 * MAX_DOUBLE, maxYOut = -1 * MAX_DOUBLE;
	CPLSetConfigOption( "GDAL_FILENAME_IS_UTF8", "NO" );
	CPLSetConfigOption( "SHAPE_ENCODING", "UTF-8" );

	/* Check whether the dataset has been registered */
	fstream f;
	f.open( outIndex, ios::in );
	if ( f )
	{
		printf( "[ERROR] Dataset already registered! shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	f.close();
	remove( outIndex );

	/* Connect redis */
	Redis *redis = new Redis();
	if ( !redis->connect( redishost, redisport ) )
	{
		printf( "[ERROR] connect redis error! shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}

	/* Read shpfile */
	GDALAllRegister();
	OGRRegisterAll();
	OGRLayer	* shpLayer;
	OGRFeature	*shpFeature;
	GDALDataset	* shpDS;
	shpDS = (GDALDataset *) GDALOpenEx( shpFile, GDAL_OF_VECTOR, NULL, NULL, NULL );
	if ( shpDS == NULL )
	{
		printf( "[ERROR] Open shpFile failed. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	shpLayer = shpDS->GetLayer( 0 );
	if ( shpLayer == NULL )
	{
		printf( "[ERROR] Open shpFile failed. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	OGREnvelope env;
	if ( shpLayer->GetExtent( &env ) != 0 )
	{
		printf( "[ERROR] Get extent failed. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	minXOut = minXOut < env.MinX ? minXOut : env.MinX;
	minYOut = minYOut < env.MinY ? minYOut : env.MinY;
	maxXout = maxXout > env.MaxX ? maxXout : env.MaxX;
	maxYOut = maxYOut > env.MaxY ? maxYOut : env.MaxY;
	shpLayer->ResetReading();

	/* Create coordinate transformation to the Web Mercator projection */
	OGRSpatialReference	* fRef;
	OGRSpatialReference	tRef;
	fRef = shpLayer->GetSpatialRef();
	if ( fRef == NULL )
	{
		printf( "[ERROR] No SpatialRef. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	tRef.importFromEPSG( 3857 );
	OGRCoordinateTransformation *coordTrans;
	coordTrans = OGRCreateCoordinateTransformation( fRef, &tRef );

	/* Write Dataset meta-data to redis */
	coordTrans->Transform( 1, &minXOut, &minYOut );
	coordTrans->Transform( 1, &maxXout, &maxYOut );
	char * tmp = new char[256];
	sprintf( tmp, "%lf,%lf,%lf,%lf", minXOut, minYOut, maxXout, maxYOut );
	redis->set( rediskey, tmp );

	/* Create spatial index */
	bi::managed_mapped_file file( bi::create_only, outIndex, size * 1024 * 1024 );
	allocator_segment	alloc( file.get_segment_manager() );
	rtree_segment		* rtree_ptr = file.construct<rtree_segment>( "rtree" ) ( params(), indexable_segment(), equal_to_segment(), alloc );
	double			px0, py0, px1, py1;
	while ( (shpFeature = shpLayer->GetNextFeature() ) != NULL )
	{
		if ( shpFeature == NULL )
			continue;
		OGRGeometry *poGeometry = shpFeature->GetGeometryRef();
		if ( poGeometry == NULL )
			continue;
		int eType = wkbFlatten( poGeometry->getGeometryType() );
		if ( eType == wkbLineString )
		{
			OGRLineString	* pOGRLineString	= (OGRLineString *) poGeometry;
			int		pointCount		= pOGRLineString->getNumPoints();
			px0	= pOGRLineString->getX( 0 );
			py0	= pOGRLineString->getY( 0 );
			coordTrans->Transform( 1, &px0, &py0 );
			for ( int i = 1; i < pointCount; i++ )
			{
				px1	= pOGRLineString->getX( i );
				py1	= pOGRLineString->getY( i );
				coordTrans->Transform( 1, &px1, &py1 );
				rtree_ptr->insert( segment( point( px0, py0 ), point( px1, py1 ) ) );
				px0	= px1;
				py0	= py1;
			}
		}else if ( eType == wkbMultiLineString )
		{
			OGRMultiLineString	* pOGRMultiLineString	= (OGRMultiLineString *) poGeometry;
			int			lineCount		= pOGRMultiLineString->getNumGeometries();
			for ( int j = 0; j < lineCount; j++ )
			{
				OGRLineString	* pOGRLineString	= (OGRLineString *) pOGRMultiLineString->getGeometryRef( j );
				int		pointCount		= pOGRLineString->getNumPoints();
				px0	= pOGRLineString->getX( 0 );
				py0	= pOGRLineString->getY( 0 );
				coordTrans->Transform( 1, &px0, &py0 );
				for ( int i = 1; i < pointCount; i++ )
				{
					px1	= pOGRLineString->getX( i );
					py1	= pOGRLineString->getY( i );
					coordTrans->Transform( 1, &px1, &py1 );
					rtree_ptr->insert( segment( point( px0, py0 ), point( px1, py1 ) ) );
					px0	= px1;
					py0	= py1;
				}
			}
		}
	}
	if ( rtree_ptr->size() < 1 )
	{
		printf( "[ERROR] No feature found. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		remove( outIndex );
		return(false);
	}

	/* Shrink the spatial index file to minimize the space waste */
	bi::managed_mapped_file::shrink_to_fit( outIndex );

	/* Write process log */
	gettimeofday( &t2, NULL );
	timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec) / 1000000.0;
	printf( "[DONE] %s Use Time:%f\n", outIndex, timeuse );

	return(true);
}

/**
 * @brief Create Spatial Indexes and Write Dataset meta-data for Point Datasets
 * @param shpFile       Input Point shpfile path
 * @param outIndex      Output spatial index path
 * @param redishost     Host IP of Redis
 * @param rediskey      Data id
 * @param redisport     Host Port of Redis
 *
 */
bool Point( char* shpFile, char* outIndex, char* redishost, char* rediskey, int redisport )
{
	/* Record the running time of the program */
	struct timeval	t1, t2;
	double		timeuse;
	gettimeofday( &t1, NULL );

	/* Init */
	typedef bgm::d2::point_xy<double>						point;
	typedef bgi::quadratic<MAX_NODE_SIZE>						params_t;
	typedef bgi::indexable<point>							indexable_t;
	typedef bgi::equal_to<point>							equal_to_t;
	typedef bi::allocator<point, bi::managed_mapped_file::segment_manager>		allocator_t;
	typedef bgi::rtree<point, params_t, indexable_t, equal_to_t, allocator_t>	rtree_t;
	long	size	= 300000; /* Max size (MB) of the spatial index mapped file */
	double	minXOut = MAX_DOUBLE, minYOut = MAX_DOUBLE, maxXout = -1 * MAX_DOUBLE, maxYOut = -1 * MAX_DOUBLE;
	CPLSetConfigOption( "GDAL_FILENAME_IS_UTF8", "NO" );
	CPLSetConfigOption( "SHAPE_ENCODING", "UTF-8" );

	/* Check whether the dataset has been registered */
	fstream f;
	f.open( outIndex, ios::in );
	if ( f )
	{
		printf( "[ERROR] Dataset already registered! shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	f.close();
	remove( outIndex );

	/* Connect redis */
	Redis *redis = new Redis();
	if ( !redis->connect( redishost, redisport ) )
	{
		printf( "[ERROR] connect redis error!\n" );
		return(false);
	}

	/* Read shpfile */
	OGRRegisterAll();
	OGRLayer	* shpLayer;
	OGRFeature	*shpFeature;
	GDALDataset	* shpDS;
	shpDS = (GDALDataset *) GDALOpenEx( shpFile, GDAL_OF_VECTOR, NULL, NULL, NULL );
	if ( shpDS == NULL )
	{
		printf( "[ERROR] Open shpFile failed. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	shpLayer = shpDS->GetLayer( 0 );
	if ( shpLayer == NULL )
	{
		printf( "[ERROR] Open shpFile failed. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	OGREnvelope env;
	if ( shpLayer->GetExtent( &env ) != 0 )
	{
		printf( "[ERROR] Get extent failed. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	minXOut = minXOut < env.MinX ? minXOut : env.MinX;
	minYOut = minYOut < env.MinY ? minYOut : env.MinY;
	maxXout = maxXout > env.MaxX ? maxXout : env.MaxX;
	maxYOut = maxYOut > env.MaxY ? maxYOut : env.MaxY;
	shpLayer->ResetReading();

	/* Create coordinate transformation to the Web Mercator projection */
	OGRSpatialReference	* fRef;
	OGRSpatialReference	tRef;
	fRef = shpLayer->GetSpatialRef();
	if ( fRef == NULL )
	{
		printf( "[ERROR] No SpatialRef. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	tRef.importFromEPSG( 3857 );
	OGRCoordinateTransformation *coordTrans;
	coordTrans = OGRCreateCoordinateTransformation( fRef, &tRef );

	/* Write Dataset meta-data to redis */
	coordTrans->Transform( 1, &minXOut, &minYOut );
	coordTrans->Transform( 1, &maxXout, &maxYOut );
	char * tmp = new char[256];
	sprintf( tmp, "%lf,%lf,%lf,%lf", minXOut, minYOut, maxXout, maxYOut );
	redis->set( rediskey, tmp );

	/* Create spatial index */
	bi::managed_mapped_file file( bi::create_only, outIndex, size * 1024 * 1024 );
	allocator_t		alloc( file.get_segment_manager() );
	rtree_t			* rtree_ptr = file.find_or_construct<rtree_t>( "rtree" ) ( params_t(), indexable_t(), equal_to_t(), alloc );
	double			px0, py0;
	while ( (shpFeature = shpLayer->GetNextFeature() ) != NULL )
	{
		if ( shpFeature == NULL )
			continue;
		OGRGeometry *poGeometry = shpFeature->GetGeometryRef();
		if ( poGeometry == NULL )
			continue;
		int eType = wkbFlatten( poGeometry->getGeometryType() );
		if ( eType == wkbPoint )
		{
			OGRPoint* pOGRPoint = (OGRPoint *) poGeometry;
			px0	= pOGRPoint->getX();
			py0	= pOGRPoint->getY();
			coordTrans->Transform( 1, &px0, &py0 );
			rtree_ptr->insert( point( px0, py0 ) );
		}else if ( eType == wkbMultiPoint )
		{
			OGRMultiPoint	* pOGRMultiPoint	= (OGRMultiPoint *) poGeometry;
			int		pointCount		= pOGRMultiPoint->getNumGeometries();
			for ( int j = 0; j < pointCount; j++ )
			{
				OGRPoint* pOGRPoint = (OGRPoint *) pOGRMultiPoint->getGeometryRef( j );
				px0	= pOGRPoint->getX();
				py0	= pOGRPoint->getY();
				coordTrans->Transform( 1, &px0, &py0 );
				rtree_ptr->insert( point( px0, py0 ) );
			}
		}
	}
	if ( rtree_ptr->size() < 1 )
	{
		printf( "[ERROR] No feature found. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		remove( outIndex );
		return(false);
	}

	/* Shrink the spatial index file to minimize the space waste */
	bi::managed_mapped_file::shrink_to_fit( outIndex );

	/* Write process log */
	gettimeofday( &t2, NULL );
	timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec) / 1000000.0;
	printf( "[DONE] %s Use Time:%f\n", outIndex, timeuse );
	return(true);
}

/**
 * @brief Create Spatial Indexes and Write Dataset meta-data for Polygon Datasets
 * @param shpFile       Input Polygon shpfile path
 * @param outIndex      Output spatial index path
 * @param redishost     host IP of Redis
 * @param rediskey      Data id
 * @param redisport     host Port of Redis
 *
 */
bool Polygon( char* shpFile, char* outIndex, char* redishost, char* rediskey, int redisport )
{
	/* Record the running time of the program */
	struct timeval	t1, t2;
	double		timeuse;
	gettimeofday( &t1, NULL );

	/* Init */
	typedef bgm::d2::point_xy<double>									point;
	typedef bgm::box<point>											box;
	typedef bgm::segment<point>										segment;
	typedef boost::tuple<segment, unsigned long, bool>							polygon_segment;
	typedef std::pair<box, unsigned long>									polygon_box;
	typedef bgi::quadratic<MAX_NODE_SIZE>									params;
	typedef bgi::indexable<polygon_segment>									indexable_segment;
	typedef bgi::equal_to<polygon_segment>									equal_to_segment;
	typedef bi::allocator<polygon_segment, bi::managed_mapped_file::segment_manager>			allocator_segment;
	typedef bgi::rtree<polygon_segment, params, indexable_segment, equal_to_segment, allocator_segment>	rtree_segment;
	typedef bgi::indexable<polygon_box>									indexable_box;
	typedef bgi::equal_to<polygon_box>									equal_to_box;
	typedef bi::allocator<polygon_box, bi::managed_mapped_file::segment_manager>				allocator_box;
	typedef bgi::rtree<polygon_box, params, indexable_box, equal_to_box, allocator_box>			rtree_box;
	char	* outMBRIndex	= new char[256];        /* Output spatial index MBR path */
	long	size		= 300000;               /* Max size (MB) of the spatial index mapped file */
	double	tolerance	= 0.00001;
	double	minXOut		= MAX_DOUBLE, minYOut = MAX_DOUBLE, maxXout = -1 * MAX_DOUBLE, maxYOut = -1 * MAX_DOUBLE;
	CPLSetConfigOption( "GDAL_FILENAME_IS_UTF8", "NO" );
	CPLSetConfigOption( "SHAPE_ENCODING", "UTF-8" );

	/* Check whether the dataset has been registered */
	fstream f;
	f.open( outIndex, ios::in );
	if ( f )
	{
		printf( "[ERROR] Dataset already registered! shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	f.close();
	sprintf( outMBRIndex, "%s_mbr", outIndex );
	remove( outIndex );
	remove( outMBRIndex );

	/* Connect redis */
	Redis *redis = new Redis();
	if ( !redis->connect( redishost, redisport ) )
	{
		printf( "[ERROR] connect redis error! shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}

	/* Read shpfile */
	OGRRegisterAll();
	OGRLayer	* shpLayer;
	OGRFeature	*shpFeature;
	GDALDataset	* shpDS;
	shpDS = (GDALDataset *) GDALOpenEx( shpFile, GDAL_OF_VECTOR, NULL, NULL, NULL );
	if ( shpDS == NULL )
	{
		printf( "[ERROR] Open shpFile failed. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	shpLayer = shpDS->GetLayer( 0 );
	if ( shpLayer == NULL )
	{
		printf( "[ERROR] No such layer. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	OGREnvelope env;
	if ( shpLayer->GetExtent( &env ) != 0 )
	{
		printf( "[ERROR] Get extent failed. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}
	minXOut = minXOut < env.MinX ? minXOut : env.MinX;
	minYOut = minYOut < env.MinY ? minYOut : env.MinY;
	maxXout = maxXout > env.MaxX ? maxXout : env.MaxX;
	maxYOut = maxYOut > env.MaxY ? maxYOut : env.MaxY;
	shpLayer->ResetReading();

	/* Create Coordinate Transformation to the Web Mercator projection */
	OGRSpatialReference	* fRef;
	OGRSpatialReference	tRef;
	fRef = shpLayer->GetSpatialRef();
	if ( fRef == NULL )
	{
		printf( "[ERROR] No SpatialRef. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		return(false);
	}

	tRef.importFromEPSG( 3857 );
	OGRCoordinateTransformation *coordTrans;
	coordTrans = OGRCreateCoordinateTransformation( fRef, &tRef );

	/* Write Dataset meta-data to redis */
	coordTrans->Transform( 1, &minXOut, &minYOut );
	coordTrans->Transform( 1, &maxXout, &maxYOut );
	char * tmp = new char[256];
	sprintf( tmp, "%lf,%lf,%lf,%lf", minXOut, minYOut, maxXout, maxYOut );
	redis->set( rediskey, tmp );

	/* Create spatial index */
	bi::managed_mapped_file file( bi::create_only, outIndex, size * 1024 * 1024 );
	allocator_segment	alloc( file.get_segment_manager() );
	rtree_segment		* rtree_ptr = file.construct<rtree_segment>( "rtree" ) ( params(), indexable_segment(), equal_to_segment(), alloc );
	bi::managed_mapped_file filembr( bi::create_only, outMBRIndex, size * 128 * 1024 );
	allocator_segment	alloc_mbr( filembr.get_segment_manager() );
	rtree_box		* rtree_ptr_mbr = filembr.construct<rtree_box>( "rtree" ) ( params(), indexable_box(), equal_to_box(), alloc_mbr );
	unsigned long		polygonID	= 0;
	double			px0, py0, px1, py1;
	double			change;
	double			minx, miny, maxx, maxy;
	while ( (shpFeature = shpLayer->GetNextFeature() ) != NULL )
	{
		if ( shpFeature == NULL )
			continue;
		OGRGeometry *poGeometry = shpFeature->GetGeometryRef();
		if ( poGeometry == NULL )
			continue;
		int eType = wkbFlatten( poGeometry->getGeometryType() );
		if ( eType == wkbPolygon )
		{
			OGRPolygon	* pOGRPolygon	= (OGRPolygon *) poGeometry;
			OGRLinearRing	*pLinearRing	= pOGRPolygon->getExteriorRing();
			if ( pLinearRing == NULL )
			{
				continue;
			}
			int pointCount = pLinearRing->getNumPoints();
			px0	= pLinearRing->getX( 0 );
			py0	= pLinearRing->getY( 0 );
			change	= py0 - pLinearRing->getY( pointCount - 2 );
			coordTrans->Transform( 1, &px0, &py0 );
			minx	= px0;
			maxx	= px0;
			miny	= py0;
			maxy	= py0;
			for ( int i = 1; i < pointCount; i++ )
			{
				px1	= pLinearRing->getX( i );
				py1	= pLinearRing->getY( i );
				coordTrans->Transform( 1, &px1, &py1 );
				minx	= minx < px1 ? minx : px1;
				miny	= miny < py1 ? miny : py1;
				maxx	= maxx > px1 ? maxx : px1;
				maxy	= maxy > py1 ? maxy : py1;
				if ( fabs( py1 - py0 ) < tolerance )
				{
					rtree_ptr->insert( boost::make_tuple( segment( point( px0, py0 ), point( px1, py1 ) ), polygonID, false ) );
				}else if ( change * (py0 - py1) < 0 )
				{
					if ( py0 > py1 )
						rtree_ptr->insert( boost::make_tuple( segment( point( px0 - tolerance * (px1 - px0) / (py1 - py0), py0 - tolerance ), point( px1, py1 ) ), polygonID, true ) );
					else
						rtree_ptr->insert( boost::make_tuple( segment( point( px0 + tolerance * (px1 - px0) / (py1 - py0), py0 + tolerance ), point( px1, py1 ) ), polygonID, true ) );
				}else
					rtree_ptr->insert( boost::make_tuple( segment( point( px0, py0 ), point( px1, py1 ) ), polygonID, true ) );
				px0	= px1;
				py0	= py1;
				change	= py1 - py0;
			}

			int innerCount = pOGRPolygon->getNumInteriorRings();
			for ( int j = 0; j < innerCount; j++ )
			{
				pLinearRing	= pOGRPolygon->getInteriorRing( j );
				pointCount	= pLinearRing->getNumPoints();
				px0		= pLinearRing->getX( 0 );
				py0		= pLinearRing->getY( 0 );
				change		= py0 - pLinearRing->getY( pointCount - 2 );
				coordTrans->Transform( 1, &px0, &py0 );
				minx	= minx < px0 ? minx : px0;
				miny	= miny < py0 ? miny : py0;
				maxx	= maxx > px0 ? maxx : px0;
				maxy	= maxy > py0 ? maxy : py0;
				for ( int i = 1; i < pointCount; i++ )
				{
					px1	= pLinearRing->getX( i );
					py1	= pLinearRing->getY( i );
					coordTrans->Transform( 1, &px1, &py1 );
					minx	= minx < px1 ? minx : px1;
					miny	= miny < py1 ? miny : py1;
					maxx	= maxx > px1 ? maxx : px1;
					maxy	= maxy > py1 ? maxy : py1;
					if ( fabs( py1 - py0 ) < tolerance )
						rtree_ptr->insert( boost::make_tuple( segment( point( px0, py0 ), point( px1, py1 ) ), polygonID, false ) );
					else if ( change * (py0 - py1) < 0 )
					{
						if ( py0 > py1 )
							rtree_ptr->insert( boost::make_tuple( segment( point( px0 - tolerance * (px1 - px0) / (py1 - py0), py0 - tolerance ), point( px1, py1 ) ), polygonID, true ) );
						else
							rtree_ptr->insert( boost::make_tuple( segment( point( px0 + tolerance * (px1 - px0) / (py1 - py0), py0 + tolerance ), point( px1, py1 ) ), polygonID, true ) );
					}else
						rtree_ptr->insert( boost::make_tuple( segment( point( px0, py0 ), point( px1, py1 ) ), polygonID, true ) );
					px0	= px1;
					py0	= py1;
					change	= py1 - py0;
				}
			}
			rtree_ptr_mbr->insert( std::make_pair( box( point( minx, miny ), point( maxx, maxy ) ), polygonID ) );
			polygonID++;
		}else if ( eType == wkbMultiPolygon )
		{
			OGRMultiPolygon * pOGRMultiPolygon	= (OGRMultiPolygon *) poGeometry;
			int		polygonCount		= pOGRMultiPolygon->getNumGeometries();
			for ( int k = 0; k < polygonCount; k++ )
			{
				OGRPolygon	* pOGRPolygon	= (OGRPolygon *) pOGRMultiPolygon->getGeometryRef( k );
				OGRLinearRing	*pLinearRing	= pOGRPolygon->getExteriorRing();
				if ( pLinearRing == NULL )
				{
					continue;
				}
				int pointCount = pLinearRing->getNumPoints();
				px0	= pLinearRing->getX( 0 );
				py0	= pLinearRing->getY( 0 );
				change	= py0 - pLinearRing->getY( pointCount - 2 );
				coordTrans->Transform( 1, &px0, &py0 );
				minx	= px0;
				maxx	= px0;
				miny	= py0;
				maxy	= py0;
				for ( int i = 1; i < pointCount; i++ )
				{
					px1	= pLinearRing->getX( i );
					py1	= pLinearRing->getY( i );
					coordTrans->Transform( 1, &px1, &py1 );
					minx	= minx < px1 ? minx : px1;
					miny	= miny < py1 ? miny : py1;
					maxx	= maxx > px1 ? maxx : px1;
					maxy	= maxy > py1 ? maxy : py1;
					if ( fabs( py1 - py0 ) < tolerance )
						rtree_ptr->insert( boost::make_tuple( segment( point( px0, py0 ), point( px1, py1 ) ), polygonID, false ) );
					else if ( change * (py0 - py1) < 0 )
					{
						if ( py0 > py1 )
							rtree_ptr->insert( boost::make_tuple( segment( point( px0 - tolerance * (px1 - px0) / (py1 - py0), py0 - tolerance ), point( px1, py1 ) ), polygonID, true ) );
						else
							rtree_ptr->insert( boost::make_tuple( segment( point( px0 + tolerance * (px1 - px0) / (py1 - py0), py0 + tolerance ), point( px1, py1 ) ), polygonID, true ) );
					}else
						rtree_ptr->insert( boost::make_tuple( segment( point( px0, py0 ), point( px1, py1 ) ), polygonID, true ) );
					px0	= px1;
					py0	= py1;
					change	= py1 - py0;
				}

				int innerCount = pOGRPolygon->getNumInteriorRings();
				for ( int j = 0; j < innerCount; j++ )
				{
					pLinearRing	= pOGRPolygon->getInteriorRing( j );
					pointCount	= pLinearRing->getNumPoints();
					px0		= pLinearRing->getX( 0 );
					py0		= pLinearRing->getY( 0 );
					change		= py0 - pLinearRing->getY( pointCount - 2 );
					coordTrans->Transform( 1, &px0, &py0 );
					minx	= minx < px0 ? minx : px0;
					miny	= miny < py0 ? miny : py0;
					maxx	= maxx > px0 ? maxx : px0;
					maxy	= maxy > py0 ? maxy : py0;
					for ( int i = 1; i < pointCount; i++ )
					{
						px1	= pLinearRing->getX( i );
						py1	= pLinearRing->getY( i );
						coordTrans->Transform( 1, &px1, &py1 );
						minx	= minx < px1 ? minx : px1;
						miny	= miny < py1 ? miny : py1;
						maxx	= maxx > px1 ? maxx : px1;
						maxy	= maxy > py1 ? maxy : py1;
						if ( fabs( py1 - py0 ) < tolerance )
							rtree_ptr->insert( boost::make_tuple( segment( point( px0, py0 ), point( px1, py1 ) ), polygonID, false ) );
						else if ( change * (py0 - py1) < 0 )
						{
							if ( py0 > py1 )
								rtree_ptr->insert( boost::make_tuple( segment( point( px0 - tolerance * (px1 - px0) / (py1 - py0), py0 - tolerance ), point( px1, py1 ) ), polygonID, true ) );
							else
								rtree_ptr->insert( boost::make_tuple( segment( point( px0 + tolerance * (px1 - px0) / (py1 - py0), py0 + tolerance ), point( px1, py1 ) ), polygonID, true ) );
						}else
							rtree_ptr->insert( boost::make_tuple( segment( point( px0, py0 ), point( px1, py1 ) ), polygonID, true ) );
						px0	= px1;
						py0	= py1;
						change	= py1 - py0;
					}
				}
				rtree_ptr_mbr->insert( std::make_pair( box( point( minx, miny ), point( maxx, maxy ) ), polygonID ) );
				polygonID++;
			}
		}
	}
	if ( rtree_ptr->size() < 1 )
	{
		printf( "[ERROR] No feature found. shpFile:%s outIndex:%s\n", shpFile, outIndex );
		remove( outIndex );
		remove( outMBRIndex );
		return(false);
	}

	/* Shrink the spatial index file to minimize the space waste */
	bi::managed_mapped_file::shrink_to_fit( outIndex );
	bi::managed_mapped_file::shrink_to_fit( outMBRIndex );

	/* Write process log */
	gettimeofday( &t2, NULL );
	timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec) / 1000000.0;
	printf( "[DONE] %s Use Time:%f\n", outIndex, timeuse );
	return(true);
}

class ServerLogHandler : public crow::ILogHandler {
public:
	void log( std::string /*message*/, crow::LogLevel /*level*/ ) override
	{
	}
};

struct ServerMiddleware
{
	std::string message;

	ServerMiddleware()
	{
		message = "foo";
	}

	void setMessage( std::string newMsg )
	{
		message = newMsg;
	}

	struct context
	{
	};

	void before_handle( crow::request & /*req*/, crow::response & /*res*/, context & /*ctx*/ )
	{
		CROW_LOG_DEBUG << " - MESSAGE: " << message;
	}

	void after_handle( crow::request & /*req*/, crow::response & /*res*/, context & /*ctx*/ )
	{
		/* no-op */
	}
};

/**
 * @brief HiVision service main function
 * @param nArgc         Input parameters copunt
 * @param papszArgv Input startup parameters
 *
 */
int main( int nArgc, char ** papszArgv )
{
	hvpara.shpPath		= papszArgv[1];         /* Path of the input shapefiles */
	hvpara.indexPath	= papszArgv[2];         /* Path to store the spatial indexes */
	hvpara.patternPath	= papszArgv[3];         /* Path of the input patterns */
	hvpara.redisHost	= papszArgv[4];         /* Host IP of Redis */
	hvpara.redisPort	= atoi( papszArgv[5] ); /* Host Port of Redis */
	hvpara.servicePort	= atoi( papszArgv[6] ); /* Port of the HiVision services */

	crow::App<ServerMiddleware> app;

	/**
	 * Vector Data Registration Service
	 * http://hostIP:servicePort/HiVision/add/{shapefile_name}/{id}/{type}
	 * Point: type=0
	 * LineString: type=1
	 * Polygon: type=2
	 */
	CROW_ROUTE( app, "/HiVision/add/<string>/<string>/<int>" ).name( "HiVisionAdd" )
		([] (const crow::request & req, crow::response & res, string shp, string appid, int type) {
			char* redisHost = hvpara.redisHost;
			int redisPort = hvpara.redisPort;
			char* shpPath = hvpara.shpPath;
			char* indexPath = hvpara.indexPath;
			try{
				char * tmp1 = new char[256];
				char * tmp2 = new char[256];
				char * tmp3 = new char[256];
				if ( type < 3 )
				{
					sprintf( tmp1, "%s/%s.shp", shpPath, shp.c_str() );
					fstream f;
					f.open( tmp1, ios::in );
					if ( !f )
					{
						res.write( "[ERROR] Dataset not found!" );
						throw "Dataset not found!";
					}
					f.close();
				}else{
					sprintf( tmp1, "%s", shp.c_str() );
				}
				if ( type == 0 ) /* type=0 Register point dataset */
				{
					sprintf( tmp2, "%sp%s", indexPath, appid.c_str() );
					sprintf( tmp3, "p%s", appid.c_str() );
					if ( Point( tmp1, tmp2, redisHost, tmp3, redisPort ) )
						res.write( "[DONE] Register done!" );
					else
						throw "Failed";
				}else if ( type == 1 ) /* type=1  Register LineString dataset */
				{
					sprintf( tmp2, "%sl%s", indexPath, appid.c_str() );
					sprintf( tmp3, "l%s", appid.c_str() );
					if ( Linestring( tmp1, tmp2, redisHost, tmp3, redisPort ) )
						res.write( "[DONE] Register done!" );
					else
						throw "Failed";
				}else if ( type == 2 ) /* type=2 Register Polygon dataset */
				{
					sprintf( tmp2, "%sa%s", indexPath, appid.c_str() );
					sprintf( tmp3, "a%s", appid.c_str() );
					if ( Polygon( tmp1, tmp2, redisHost, tmp3, redisPort ) )
						res.write( "[DONE] Register done!" );
					else
						throw "Failed";
				}else{
					res.write( "[ERROR] Wrong type!" );
				}
				res.end();
			}
			catch ( const char* msg )
			{
				res.write( "[ERROR] Register failed" );
				res.end();
			}
		} );

	/**
	 * Vector Data Visualization WMTS (Scattor Plot)
	 * http://hostIP:servicePort/HiVision/{id}/{R}/{G}/{B}/{A}/{z}/{x}/{y}.png
	 * Point: type=0
	 * LineString: type=1
	 * Polygon: type=2
	 */
	CROW_ROUTE( app, "/HiVision/<string>/<int>/<int>/<int>/<double>/<int>/<int>/<int>.png" ).name( "HiVision" )
		([] (const crow::request & req, crow::response & res, string appid, int R, int G, int B, double AD, int z, int x, int y) {
			char* redisHost = hvpara.redisHost;
			int redisPort = hvpara.redisPort;
			std::ostringstream os;
			Redis *redis = new Redis();
			if ( !redis->connect( redisHost, redisPort ) )
			{
				printf( "connect redis error!\n" );
			}
			int A = AD * 64;
			int AH = AD * 256;
			int Ax = (1 - AD) * 64;
			int Ay = -64;
			char* key = new char[32];
			char* newkey = new char[32];
			vector<char> pos;
			long size;
			png_bytep * row_pointers = (png_bytep *) malloc( 256 * sizeof(png_bytep) );
			for ( int i = 0; i < TILE_SIZE; i++ )
				row_pointers[i] = (png_bytep) malloc( 1024 );

	                /* Generate tiles */
			try{
				char * tmp = new char[256];
				int count;
				char* bbox[8];
				sprintf( tmp, "%s", redis->get( appid ).c_str() );
				GetList( tmp, bbox, (char *) ",", count );
				double minx = atof( bbox[0] );
				double miny = atof( bbox[1] );
				double maxx = atof( bbox[2] );
				double maxy = atof( bbox[3] );
				double tile_minx = ( (256 * x + 0.5) / (128 << z) - 1) * L;
				double tile_miny = (1 - (256 * y + 255.5) / (128 << z) ) * L;
				double tile_maxx = ( (256 * x + 255.5) / (128 << z) - 1) * L;
				double tile_maxy = (1 - (256 * y - 0.5) / (128 << z) ) * L;
				delete tmp;

	                        /* Filter out tiles that are not in the spatial scope of data MBRs */
				if ( tile_minx < maxx && tile_miny < maxy && tile_maxx > minx && tile_maxy > miny )
				{
					sprintf( key, "%s/%d/%d/%d", appid.c_str(), z, x, y );
					char (*buffer_area)[TILE_SIZE] = (char(*)[TILE_SIZE])malloc( TILE_SIZE * TILE_SIZE );

	                        /* Filter out tiles that are previously processed and the visualization results that are still preserved in the Result Pool */
					if ( !redis->zget( key, (char *) buffer_area ) )
					{
	                        /* Create new tasks in the Task Pool */
						memset( newkey, 0, 32 );
						redisContext* rc = redisConnect( redisHost, redisPort );
						redisReply* reply = (redisReply *) redisCommand( rc, "subscribe HiVisiontiles" );
						redis->lpush( "HiVisiontasklist", key );
						while ( strcmp( newkey, key ) != 0 )
						{
							if ( redisGetReply( rc, (void * *) &reply ) == REDIS_OK && reply->type == REDIS_REPLY_ARRAY )
								sprintf( newkey, "%s", reply->element[2]->str );
						}
						redis->zget( key, (char *) buffer_area );
						freeReplyObject( reply );
						redisFree( rc );
					}

	                                /* collect results from Result Pool and render tiles according the given styles */
					png_structp png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL );
					png_infop info_ptr = png_create_info_struct( png_ptr );
					FILE *temp_png = tmpfile();
					png_init_io( png_ptr, temp_png );
					png_set_IHDR( png_ptr, info_ptr, TILE_SIZE, TILE_SIZE, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE );
					png_write_info( png_ptr, info_ptr );
					for ( int i = 0; i < TILE_SIZE; i++ )
						for ( int j = 0; j < TILE_SIZE; j++ )
						{
							if ( buffer_area[i][j] > 4 )
							{
								row_pointers[i][4 * j] = R;     /* red */
								row_pointers[i][4 * j + 1] = G; /* green */
								row_pointers[i][4 * j + 2] = B; /* blue */
								row_pointers[i][4 * j + 3] = AH + Ax * (buffer_area[i][j] - 4) - 1;
							}else if ( buffer_area[i][j] > 0 )
							{
								row_pointers[i][4 * j] = R;     /* red */
								row_pointers[i][4 * j + 1] = G; /* green */
								row_pointers[i][4 * j + 2] = B; /* blue */
								row_pointers[i][4 * j + 3] = buffer_area[i][j] * A - 1;
							}else if ( buffer_area[i][j] < 0 )
							{
								row_pointers[i][4 * j] = R;     /* red */
								row_pointers[i][4 * j + 1] = G; /* green */
								row_pointers[i][4 * j + 2] = B; /* blue */
								row_pointers[i][4 * j + 3] = buffer_area[i][j] * Ay - 1;
							}else  {
								row_pointers[i][4 * j] = 0;     /* red */
								row_pointers[i][4 * j + 1] = 0; /* green */
								row_pointers[i][4 * j + 2] = 0; /* blue */
								row_pointers[i][4 * j + 3] = 0;
							}
						}
					free( buffer_area );
					png_write_image( png_ptr, row_pointers );
					png_write_end( png_ptr, NULL );
					fseek( temp_png, 0, SEEK_END );
					size = ftell( temp_png );
					rewind( temp_png );
					pos.resize( size );
					fread( &pos[0], 1, size, temp_png );
					fclose( temp_png );
					string pos_tostr = string( pos.begin(), pos.end() );
					res.write( pos_tostr );
					res.set_header( "Content-Type", "image/png" );
					res.set_header( "Access-Control-Allow-Origin", "*" );
				}else
					throw "Tile out range!";
				redis->freeredis();
				delete key;
				delete newkey;
				for ( int j = 0; j < 256; j++ )
					free( row_pointers[j] );
				free( row_pointers );
				res.end();
			}
			catch ( const char* msg )
			{
				redis->freeredis();
				delete key;
				delete newkey;

				for ( int j = 0; j < 256; j++ )
					free( row_pointers[j] );
				free( row_pointers );
				res.code = 500;
				os << "ERROR: " << msg << "\n";
				res.write( os.str() );
				res.set_header( "Content-Type", "text/html" );
				res.end();
			}
		} );

	/**
	 * Vector Data Visualization WMTS (Patterns Filling for Polygon Objects)
	 * http://hostIP:servicePort/HiVision/{id}/{R}/{G}/{B}/{pattern_id}/{A}/{z}/{x}/{y}.png
	 */
	CROW_ROUTE( app, "/HiVision/<string>/<int>/<int>/<int>/<string>/<double>/<int>/<int>/<int>.png" ).name( "HiVision" )
		([] (const crow::request & req, crow::response & res, string appid, int R, int G, int B, string pattern, double AD, int z, int x, int y) {
	                /* Init */
			char* redisHost = hvpara.redisHost;
			int redisPort = hvpara.redisPort;
			std::ostringstream os;
			Redis *redis = new Redis();
			if ( !redis->connect( redisHost, redisPort ) )
			{
				printf( "connect redis error!\n" );
			}
			int A = AD * 64;
			int AH = AD * 256;
			int Ax = (1 - AD) * 64;
			int Ay = -64;
			char* key = new char[32];
			char* newkey = new char[32];
			vector<char> pos;
			long size;
			FILE *pic_fp;
			png_structp png_ptr, pattern_png_ptr;
			png_infop info_ptr, pattern_info_ptr;
			pattern_png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );
			pattern_info_ptr = png_create_info_struct( pattern_png_ptr );
			setjmp( png_jmpbuf( pattern_png_ptr ) );
			png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );
			info_ptr = png_create_info_struct( png_ptr );
			setjmp( png_jmpbuf( png_ptr ) );
			png_bytep * row_pointers = (png_bytep *) malloc( 256 * sizeof(png_bytep) );
			for ( int i = 0; i < TILE_SIZE; i++ )
				row_pointers[i] = (png_bytep) malloc( 1024 );
	                /* Generate tiles */
			try{
				char * pngfile = new char[256];
				sprintf( pngfile, "%s%s.png", hvpara.patternPath, pattern.c_str() );
				pic_fp = fopen( pngfile, "rb" );
				if ( pic_fp == NULL )
					throw "Open pattern file failed!";
				rewind( pic_fp );
				png_init_io( pattern_png_ptr, pic_fp );
				png_read_png( pattern_png_ptr, pattern_info_ptr, PNG_TRANSFORM_EXPAND, 0 );
				int pattern_width, pattern_height;
				png_bytep* pattern_row_pointers;
				pattern_row_pointers = png_get_rows( pattern_png_ptr, pattern_info_ptr );
				pattern_width = png_get_image_width( pattern_png_ptr, pattern_info_ptr );
				pattern_height = png_get_image_height( pattern_png_ptr, pattern_info_ptr );
				char * tmp = new char[256];
				int count;
				char* bbox[8];
				sprintf( tmp, "%s", redis->get( appid ).c_str() );
				GetList( tmp, bbox, (char *) ",", count );
				double minx = atof( bbox[0] );
				double miny = atof( bbox[1] );
				double maxx = atof( bbox[2] );
				double maxy = atof( bbox[3] );
				double tile_minx = ( (256 * x + 0.5) / (128 << z) - 1) * L;
				double tile_miny = (1 - (256 * y + 255.5) / (128 << z) ) * L;
				double tile_maxx = ( (256 * x + 255.5) / (128 << z) - 1) * L;
				double tile_maxy = (1 - (256 * y - 0.5) / (128 << z) ) * L;
				delete tmp;
				if ( tile_minx < maxx && tile_miny < maxy && tile_maxx > minx && tile_maxy > miny )     /* Filter out tiles that are not in the spatial scope of data MBRs */
				{
					sprintf( key, "%s/%d/%d/%d", appid.c_str(), z, x, y );
					char (*buffer_area)[TILE_SIZE] = (char(*)[TILE_SIZE])malloc( TILE_SIZE * TILE_SIZE );
					if ( !redis->zget( key, (char *) buffer_area ) )                                /* Filter out tiles that are previously processed and the visualization results that are still preserved in the Result Pool */
					{
	                                                                               /* Create new tasks in the Task Pool */
						memset( newkey, 0, 32 );
						redisContext* rc = redisConnect( redisHost, redisPort );
						redisReply* reply = (redisReply *) redisCommand( rc, "subscribe HiVisiontiles" );
						redis->lpush( "HiVisiontasklist", key );
						while ( strcmp( newkey, key ) != 0 )
						{
							if ( redisGetReply( rc, (void * *) &reply ) == REDIS_OK && reply->type == REDIS_REPLY_ARRAY )
								sprintf( newkey, "%s", reply->element[2]->str );
						}
						redis->zget( key, (char *) buffer_area );
						freeReplyObject( reply );
						redisFree( rc );
					}
	                                /* collect results from Result Pool and render tiles according the given styles */
					png_structp png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL );
					png_infop info_ptr = png_create_info_struct( png_ptr );
					FILE *temp_png = tmpfile();
					png_init_io( png_ptr, temp_png );
					png_set_IHDR( png_ptr, info_ptr, TILE_SIZE, TILE_SIZE, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE );
					png_write_info( png_ptr, info_ptr );
					for ( int i = 0; i < TILE_SIZE; i++ )
						for ( int j = 0; j < TILE_SIZE; j++ )
						{
							if ( buffer_area[i][j] > 4 )
							{
								row_pointers[i][4 * j] = R;
								row_pointers[i][4 * j + 1] = G;
								row_pointers[i][4 * j + 2] = B;
								row_pointers[i][4 * j + 3] = AH + Ax * (buffer_area[i][j] - 4) - 1;
							}else if ( buffer_area[i][j] == 4 )
							{
								row_pointers[i][4 * j] = pattern_row_pointers[(i + 256 * y) % pattern_height][4 * ( (j + 256 * x) % pattern_width)];
								row_pointers[i][4 * j + 1] = pattern_row_pointers[(i + 256 * y) % pattern_height][4 * ( (j + 256 * x) % pattern_width) + 1];
								row_pointers[i][4 * j + 2] = pattern_row_pointers[(i + 256 * y) % pattern_height][4 * ( (j + 256 * x) % pattern_width) + 2];
								row_pointers[i][4 * j + 3] = pattern_row_pointers[(i + 256 * y) % pattern_height][4 * ( (j + 256 * x) % pattern_width) + 3];;
							}else if ( buffer_area[i][j] > 0 )
							{
								row_pointers[i][4 * j] = R;     /* red */
								row_pointers[i][4 * j + 1] = G; /* green */
								row_pointers[i][4 * j + 2] = B; /* blue */
								row_pointers[i][4 * j + 3] = buffer_area[i][j] * A - 1;
							}else if ( buffer_area[i][j] < 0 )
							{
								row_pointers[i][4 * j] = R;     /* red */
								row_pointers[i][4 * j + 1] = G; /* green */
								row_pointers[i][4 * j + 2] = B; /* blue */
								row_pointers[i][4 * j + 3] = buffer_area[i][j] * Ay - 1;
							}else  {
								row_pointers[i][4 * j] = 0;     /* red */
								row_pointers[i][4 * j + 1] = 0; /* green */
								row_pointers[i][4 * j + 2] = 0; /* blue */
								row_pointers[i][4 * j + 3] = 0;
							}
						}
					free( buffer_area );
					png_write_image( png_ptr, row_pointers );
					png_write_end( png_ptr, NULL );
					fseek( temp_png, 0, SEEK_END );
					size = ftell( temp_png );
					rewind( temp_png );
					pos.resize( size );
					fread( &pos[0], 1, size, temp_png );
					fclose( temp_png );
					string pos_tostr = string( pos.begin(), pos.end() );
					res.write( pos_tostr );
					res.set_header( "Content-Type", "image/png" );
					res.set_header( "Access-Control-Allow-Origin", "*" );
				}else
					throw "Tile out range!";
				redis->freeredis();
				delete key;
				delete newkey;
				png_destroy_read_struct( &png_ptr, &info_ptr, 0 );
				png_destroy_read_struct( &pattern_png_ptr, &pattern_info_ptr, 0 );
				fclose( pic_fp );
				for ( int j = 0; j < 256; j++ )
					free( row_pointers[j] );
				free( row_pointers );
				res.end();
			}
			catch ( const char* msg )
			{
				redis->freeredis();
				delete key;
				delete newkey;
				png_destroy_read_struct( &png_ptr, &info_ptr, 0 );
				fclose( pic_fp );
				for ( int j = 0; j < 256; j++ )
					free( row_pointers[j] );
				free( row_pointers );
				res.code = 500;
				os << "ERROR: " << msg << "\n";
				res.write( os.str() );
				res.set_header( "Content-Type", "text/html" );
				res.end();
			}
		} );

	app.port( hvpara.servicePort )
	.multithreaded()
	.run();
}


#include "Mesh.h"
#include <igl/per_vertex_normals.h>
#include <igl/polygon_mesh_to_triangle_mesh.h>
#include <igl/per_face_normals.h>
#include <igl/draw_mesh.h>
#include <Eigen/Geometry>

void Mesh::draw_and_cache()
{
  using namespace Eigen;
  using namespace igl;
  if(wireframe || show_weights)
  {
    glPushAttrib(GL_POLYGON_BIT);
    glPushAttrib(GL_ENABLE_BIT);
    glPushAttrib(GL_LIGHTING_BIT);
    glPushAttrib(GL_LINE_BIT);
    if(wireframe)
    {
      glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    }
    glEnable(GL_COLOR_MATERIAL);
    if(show_weights)
    {
      glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT);
      glColor3f(0.1,0.1,0.1);
      glColorMaterial(GL_FRONT_AND_BACK,GL_SPECULAR);
      glColor3f(0.4,0.4,0.4);
    }else
    {
      glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT);
      glColor3f(0.,0,0);
      glColorMaterial(GL_FRONT_AND_BACK,GL_SPECULAR);
      glColor3f(0.,0,0);
    }
    glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE);
    if(wireframe)
    {
      glColor3f(0,0,0);
    }
    if(!glIsList(dl) || stale)
    {
      assert(V.rows() == U.rows());
      if(show_weights && W.cols()>0)
      {
        per_vertex_normals(U,F,N);
        C.resize(V.rows(),3);
        for(size_t v = 0;v<V.rows();v++)
        {
          C(v,0) = 1;
          double w = 0;
          for(size_t selected_weight : selected_weights)
          {
            w+=W(v,selected_weight);
          }
          C(v,1) = 1.-w;
          C(v,2) = 1.-w;
        }
      }
      if(glIsList(dl))
      {
        glDeleteLists(dl,1);
      }
      dl = glGenLists(1);
      glNewList(dl,GL_COMPILE_AND_EXECUTE);
      if(show_weights)
      {
        draw_mesh(U,F,N,C);
      }else
      {
        draw_mesh(U,Q,N);
      }
      glEndList();
    }else
    {
      glCallList(dl);
    }
    glPopAttrib();
    glPopAttrib();
    glPopAttrib();
    glPopAttrib();
  } else
  {

    if(!glIsBuffer(ibo))
    {
      T.resize(Q.rows()*2,3);
      Matrix<GLuint,Dynamic,Dynamic,RowMajor> TCT(T.rows(),3);
      for(int q=0;q<Q.rows();q++)
      {
        T(q*2+0,0) = Q(q,0);
        T(q*2+0,1) = Q(q,1);
        T(q*2+0,2) = Q(q,2);
        T(q*2+1,0) = Q(q,0);
        T(q*2+1,1) = Q(q,2);
        T(q*2+1,2) = Q(q,3)==-1?Q(q,2):Q(q,3);
      }
      T1 = Q.leftCols(3).eval();
      for(int f=0;f<T.rows();f++)
      {
        for(int c = 0;c<3;c++)
        {
          TCT(f,c) = f*3+c;
        }
      }
      UCT.resize(3*T.rows(),3);
      NCT.resize(3*T.rows(),3);
      glGenBuffers(1,&ibo);
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ibo);
      glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(GLuint)*TCT.size(),TCT.data(),GL_STATIC_DRAW);
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
      glGenBuffers(1,&vbo);
      glGenBuffers(1,&nbo);
    }

    const int Trows = T.rows();
#   pragma omp parallel for if (Trows>10000)
    for(int f=0;f<Trows;f+=2)
    {
      for(int c = 0;c<3;c++)
      {
        UCT.row(f*3+c) = U.row(T(f,c)).cast<float>();
        UCT.row((f+1)*3+c) = U.row(T(f+1,c)).cast<float>();
      }
      Matrix<float,1,3,RowMajor> v1 = UCT.row(f*3+1)-UCT.row(f*3+0);
      Matrix<float,1,3,RowMajor> v2 = UCT.row(f*3+2)-UCT.row(f*3+0);
      Matrix<float,1,3,RowMajor> n = v1.cross(v2);
      for(int c = 0;c<3;c++)
      {
        for(int d = 0;d<3;d++)
        {
          const float nd = n(d);
          NCT(f*3+c,d) = nd;
          NCT((f+1)*3+c,d) = nd;
        }
      }
    }

      glEnableClientState(GL_VERTEX_ARRAY);
      glBindBuffer(GL_ARRAY_BUFFER,vbo);
      glBufferData(GL_ARRAY_BUFFER,sizeof(float)*UCT.size(),UCT.data(),GL_DYNAMIC_DRAW);
      glVertexPointer(3,GL_FLOAT,0,0);
      glEnableClientState(GL_NORMAL_ARRAY);
      glBindBuffer(GL_ARRAY_BUFFER,nbo);
      glBufferData(GL_ARRAY_BUFFER,sizeof(float)*NCT.size(),NCT.data(),GL_DYNAMIC_DRAW);
      glNormalPointer(GL_FLOAT,0,0);
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ibo);
      glDrawElements(GL_TRIANGLES,T.size(),GL_UNSIGNED_INT,0);
      glBindBuffer(GL_ARRAY_BUFFER,0);
    }

  stale = false;
}