use indy::api::ErrorCode;
use indy::api::anoncreds::{
    indy_issuer_create_and_store_claim_def,
    indy_issuer_create_claim,
    indy_prover_create_master_secret,
    indy_prover_create_and_store_claim_req,
    indy_prover_store_claim,
    indy_prover_get_claims_for_proof_req,
    indy_prover_create_proof,
    indy_prover_store_claim_offer,
    indy_prover_get_claim_offers,
    indy_verifier_verify_proof,
    indy_prover_get_claims
};

use utils::callback::CallbackUtils;
use utils::timeout::TimeoutUtils;
use utils::wallet::WalletUtils;
use utils::types::{
    ProofClaimsJson,
    ClaimInfo
};
use utils::test::TestUtils;

use std::ffi::CString;
use std::ptr::null;
use std::sync::mpsc::channel;
use std::collections::{HashSet};
use std::sync::{Once, ONCE_INIT};
use std::mem;


pub struct AnoncredsUtils {}

static mut WALLET_HANDLE: i32 = 0;
static mut CLAIM_DEF_JSON: &'static str = "";
pub const ISSUER_DID: &'static str = "NcYxiDXkpYi6ov5FcYDi1e";

impl AnoncredsUtils {
    pub fn issuer_create_claim_definition(wallet_handle: i32, issuer_did: &str, schema: &str, signature_type: Option<&str>, create_non_revoc: bool) -> Result<String, ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err, claim_def_json| {
            sender.send((err, claim_def_json)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_issuer_create_claim_definition_cb(cb);

        let schema = CString::new(schema).unwrap();
        let signature_type_str = signature_type.map(|s| CString::new(s).unwrap()).unwrap_or(CString::new("").unwrap());
        let issuer_did = CString::new(issuer_did).unwrap();

        let err =
            indy_issuer_create_and_store_claim_def(command_handle,
                                                     wallet_handle,
                                                     issuer_did.as_ptr(),
                                                     schema.as_ptr(),
                                                     if signature_type.is_some() { signature_type_str.as_ptr() } else { null() },
                                                     create_non_revoc,
                                                     cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let (err, claim_def_json) = receiver.recv().unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(claim_def_json)
    }

    pub fn prover_create_master_secret(wallet_handle: i32, master_secret_name: &str) -> Result<(), ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err| {
            sender.send((err)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_prover_create_master_secret_cb(cb);

        let master_secret_name = CString::new(master_secret_name).unwrap();

        let err = indy_prover_create_master_secret(command_handle,
                                                     wallet_handle,
                                                     master_secret_name.as_ptr(),
                                                     cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let err = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(())
    }

    pub fn prover_store_claim_offer(wallet_handle: i32, claim_offer_json: &str) -> Result<(), ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err| {
            sender.send((err)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_claim_offer_json_cb(cb);

        let claim_offer_json = CString::new(claim_offer_json).unwrap();

        let err = indy_prover_store_claim_offer(command_handle,
                                                  wallet_handle,
                                                  claim_offer_json.as_ptr(),
                                                  cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let err = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(())
    }

    pub fn prover_get_claim_offers(wallet_handle: i32, filter_json: &str) -> Result<String, ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err, claim_offers_json| {
            sender.send((err, claim_offers_json)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_prover_get_claim_offers_cb(cb);

        let filter_json = CString::new(filter_json).unwrap();

        let err = indy_prover_get_claim_offers(command_handle,
                                                 wallet_handle,
                                                 filter_json.as_ptr(),
                                                 cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let (err, claim_offers_json) = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(claim_offers_json)
    }

    pub fn prover_create_and_store_claim_req(wallet_handle: i32, prover_did: &str, claim_offer_json: &str,
                                             claim_def_json: &str, master_secret_name: &str) -> Result<String, ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err, claim_req_json| {
            sender.send((err, claim_req_json)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_prover_create_claim_req_cb(cb);

        let prover_did = CString::new(prover_did).unwrap();
        let claim_offer_json = CString::new(claim_offer_json).unwrap();
        let claim_def_json = CString::new(claim_def_json).unwrap();
        let master_secret_name = CString::new(master_secret_name).unwrap();

        let err = indy_prover_create_and_store_claim_req(command_handle,
                                                           wallet_handle,
                                                           prover_did.as_ptr(),
                                                           claim_offer_json.as_ptr(),
                                                           claim_def_json.as_ptr(),
                                                           master_secret_name.as_ptr(),
                                                           cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let (err, claim_req_json) = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(claim_req_json)
    }

    pub fn issuer_create_claim(wallet_handle: i32, claim_req_json: &str, claim_json: &str) -> Result<(String, String), ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err, revoc_reg_update_json, xclaim_json| {
            sender.send((err, revoc_reg_update_json, xclaim_json)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_issuer_create_claim_cb(cb);

        let claim_req_json = CString::new(claim_req_json).unwrap();
        let claim_json = CString::new(claim_json).unwrap();

        let err = indy_issuer_create_claim(command_handle,
                                             wallet_handle,
                                             claim_req_json.as_ptr(),
                                             claim_json.as_ptr(),
                                             -1,
                                             -1,
                                             cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let (err, revoc_reg_update_json, xclaim_json) = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok((revoc_reg_update_json, xclaim_json))
    }

    pub fn prover_store_claim(wallet_handle: i32, claims_json: &str) -> Result<(), ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err| {
            sender.send(err).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_prover_store_claim_cb(cb);

        let claims_json = CString::new(claims_json).unwrap();

        let err = indy_prover_store_claim(command_handle,
                                            wallet_handle,
                                            claims_json.as_ptr(),
                                            cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let err = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(())
    }

    pub fn prover_get_claims(wallet_handle: i32, filter_json: &str) -> Result<String, ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err, claims_json| {
            sender.send((err, claims_json)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_prover_get_claims(cb);

        let filter_json = CString::new(filter_json).unwrap();

        let err = indy_prover_get_claims(command_handle,
                                           wallet_handle,
                                           filter_json.as_ptr(),
                                           cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let (err, claims_json) = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(claims_json)
    }

    pub fn prover_get_claims_for_proof_req(wallet_handle: i32, proof_request_json: &str) -> Result<String, ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err, claims_json| {
            sender.send((err, claims_json)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_prover_get_claims_for_proof_req_cb(cb);

        let proof_request_json = CString::new(proof_request_json).unwrap();

        let err = indy_prover_get_claims_for_proof_req(command_handle,
                                                         wallet_handle,
                                                         proof_request_json.as_ptr(),
                                                         cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let (err, claims_json) = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(claims_json)
    }

    pub fn prover_create_proof(wallet_handle: i32, proof_req_json: &str, requested_claims_json: &str,
                               schemas_json: &str, master_secret_name: &str, claim_defs_json: &str,
                               revoc_regs_json: &str) -> Result<String, ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err, proof_json| {
            sender.send((err, proof_json)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_prover_get_claims_for_proof_req_cb(cb);

        let proof_req_json = CString::new(proof_req_json).unwrap();
        let requested_claims_json = CString::new(requested_claims_json).unwrap();
        let schemas_json = CString::new(schemas_json).unwrap();
        let master_secret_name = CString::new(master_secret_name).unwrap();
        let claim_defs_json = CString::new(claim_defs_json).unwrap();
        let revoc_regs_json = CString::new(revoc_regs_json).unwrap();

        let err = indy_prover_create_proof(command_handle,
                                             wallet_handle,
                                             proof_req_json.as_ptr(),
                                             requested_claims_json.as_ptr(),
                                             schemas_json.as_ptr(),
                                             master_secret_name.as_ptr(),
                                             claim_defs_json.as_ptr(),
                                             revoc_regs_json.as_ptr(),
                                             cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let (err, proof_json) = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(proof_json)
    }

    pub fn verifier_verify_proof(proof_request_json: &str, proof_json: &str,
                                 schemas_json: &str, claim_defs_json: &str, revoc_regs_json: &str) -> Result<bool, ErrorCode> {
        let (sender, receiver) = channel();

        let cb = Box::new(move |err, valid| {
            sender.send((err, valid)).unwrap();
        });

        let (command_handle, cb) = CallbackUtils::closure_to_verifier_verify_proof_cb(cb);

        let proof_request_json = CString::new(proof_request_json).unwrap();
        let proof_json = CString::new(proof_json).unwrap();
        let schemas_json = CString::new(schemas_json).unwrap();
        let claim_defs_json = CString::new(claim_defs_json).unwrap();
        let revoc_regs_json = CString::new(revoc_regs_json).unwrap();

        let err = indy_verifier_verify_proof(command_handle,
                                               proof_request_json.as_ptr(),
                                               proof_json.as_ptr(),
                                               schemas_json.as_ptr(),
                                               claim_defs_json.as_ptr(),
                                               revoc_regs_json.as_ptr(),
                                               cb);

        if err != ErrorCode::Success {
            return Err(err);
        }

        let (err, valid) = receiver.recv_timeout(TimeoutUtils::short_timeout()).unwrap();

        if err != ErrorCode::Success {
            return Err(err);
        }

        Ok(valid)
    }

    pub fn get_claim_def_id(issuer_did: &str, schema_seq_no: i32) -> String {
        issuer_did.to_string() + ":" + &schema_seq_no.to_string()
    }

    pub fn get_gvt_schema_json(schema_seq_no: i32) -> String {
        format!(r#"{{
                    "seqNo":{},
                    "data": {{
                        "name":"gvt",
                        "version":"1.0",
                        "keys":["age","sex","height","name"]
                    }}
                 }}"#, schema_seq_no)
    }

    pub fn get_xyz_schema_json(schema_seq_no: i32) -> String {
        format!(r#"{{
                    "seqNo":{},
                    "data":{{
                        "name":"xyz",
                        "version":"1.0",
                        "keys":["status","period"]
                    }}
                 }}"#, schema_seq_no)
    }

    pub fn get_claim_offer(issuer_did: &str, schema_seq_no: i32) -> String {
        format!(r#"{{"issuer_did":"{}","schema_seq_no":{} }}"#,
                issuer_did, schema_seq_no)
    }

    pub fn get_gvt_claim_json() -> String {
        r#"{
               "sex":["male","5944657099558967239210949258394887428692050081607692519917050011144233115103"],
               "name":["Alex","1139481716457488690172217916278103335"],
               "height":["175","175"],
               "age":["28","28"]
        }"#.to_string()
    }

    pub fn get_xyz_claim_json() -> String {
        r#"{
               "status":["partial","51792877103171595686471452153480627530895"],
               "period":["8","8"]
        }"#.to_string()
    }

    pub fn get_gvt_claim_def() -> String {
        r#"{
            "ref":1,
            "signature_type":"CL",
            "origin":"CnEDk9HrMnmiHXEV1WFgbVCRteYnPqsJwrTdcZaNhFVW",
            "data":{
                "primary":{
                    "n":"83469852984476956871633111285697420678256060723156580163068122759469567425381600849138438902552107548539766861666590365174848381535291010418041757276710240953030842046122202402016906205924972182252295487319094577329593677544393592632224714613427822130473474379696616183721440743475053734247824037725487533789856061706740833324717788602268746116297029721621398888459529131593826880823126900285858832457134377949183677639585442886904844793608783831753240185678448312284269486845497720949217396146132958861735347072722092449280372574205841746312833280031873247525372459800132930201998084029506922484661426185450002143461",
                    "s":"36598527821865478336201608644402887021319976830281254144922838415193047189326184120876650311572665920640111967758238425066565864958446810892401358531545590342401290056836149703549220109981509774843525259400920352082531560361277411808530872594109525982462491998670199872903823657869742599086495624835178373073050767142081484206776345277546531080450529061958937980460303537107061046725579009809137197541389237618812289642185603461102513124991949835948586623327143696280240600789928383168220919049794681181861776889681393339729944540218566460627715413465709316412838042632482652979005394086058441511591756153781159121227",
                    "rms":"23836382972046033209463023456985914927629254782286444334728987813724281765327660893337383450653748691133061782449580026414785334582859397732571499366000805280757877601804441568263743400086744823885203441870748890135445454347495577599234477813361254101857848089907496868136222777024967328411073984887059985103475100012787049491016895625234124538894645853939428009610771524580099452739392988318945585946758355611531582519514003714424216836334706370901576611410508113637778751976890941210538254418937285168453791223070083264852447713991114719905171445881819334587600693321106919667204512182250084258986170095774914769107",
                    "r":{
                        "age":"15428480888651268593621235736458685943389726269437020388313417035842991073151072061010468945249435098482625869236498750525662874597991333642865524104221652457788998109101749530884821300954337535472137069380551054204373136155978715752232238326100335828797868667735730830741789880726890058203015780792583471770404478023662994191588489722949795849990796063953164194432710764145637578113087142419634074378464118254848566088943085760634805903735300398689750649630456000759025366784986694635635510206166144055869823907120081668956271923743188342071093889666111639924270726451727101864752767708690529389259470017692442002767",
                        "name":"74008461204977705404956807338714891429397387365673402608947856456696416827848931951447004905350314563364442667940680669672331872875260077257474781261367591510351742401708951175978700805098470304211391452678992053755170054677498844656517106987419550598382601263743442309896367374279461481792679346472671426558385003925845431219156475505665973289508268634194964491418902859845301351562575713510002838692825728016254605821829245646855474149449192539144107522081712005891593405826343897070114168186645885993480245755494685105636168333649181939830898090651120926156152753918086493335382129839850609934233944397884745134858",
                        "sex":"40646934914193532541511585946883243600955533193734077080129022860038019728021796610599139377923881754708640252789475144625086755150150612623804964347576907276705600241268266301487516824189453869193926251791711672689649199860304727280764676403810510047397326018392955950249975529169980045664874433828266623495515931483137318724210483205730962036282580749206735450480976847188040030165278917936054139926609849181885654646269083459580415131952938813839182742590617440550773580790446467896469090164142755499827316294406540664375065617280568303837662431668218593808092907551353093044984225946834165309588512359379032847125",
                        "height":"60077310006190228182950362501472785016827894350517184186566050586806482282196022414888288252599211919680339352529750982779980002923071031258837648242708410943080288964834346858544959217874890558006056551949543916094446891954292824146212277550956558692224016968153138097595802008943263818064605343108607131298420107448075252583276684858815371561492996587478784667827675142382692061950832554910690663724101360454494298013020706080246986445114235542283015624510836206522238238728405826712730615187235709554561445384409566940622412591208469650855059870671771721035756205878334354791247751663679130847366069215369484993653"
                    },
                    "rctxt":"36378575722516953828830668112614685244571602424420162720244033008706985740860180373728219883172046821464173434592331868657297711725743060654773725561634332269874655603697872022630999786617840856366807034806938874090561725454026277048301648000835861491030368037108847175790943895107305383779598585532854170748970999977490244057635358075906501562932970296830906796719844887269636297064678777638050708353254894155336111384638276041851818109156194135995350001255928374102089400812445206030019103440866343736761861049159446083221399575945128480681798837648578166327164995640582517916904912672875184928940552983440410245037",
                    "z":"65210811645114955910002482704691499297899796787292244564644467629838455625296674951468505972574512639263601600908664306008863647466643899294681985964775001929521624341158696866597713112430928402519124073453804861185882073381514901830347278653016300430179820703804228663001232136885036042101307423527913402600370741689559698469878576457899715687929448757963179899698951620405467976414716277505767979494596626867505828267832223147104774684678295400387894506425769550011471322118172087199519094477785389750318762521728398390891214426117908390767403438354009767291938975195751081032073309083309746656253788033721103313036"
                }
            }
        }"#.to_string()
    }

    pub fn get_gvt_claim_req() -> String {
        format!(r#"{{
            "blinded_ms":{{
                "prover_did":"CnEDk9HrMnmiHXEV1WFgbVCRteYnPqsJwrTdcZaNhFVW",
                "u":"72052674960029442327236458752017934128206007798774128392572211954456711136771871346204637748253860917837147111221378456345006764308173447177933384497678611527908801900335623480700015849806575534757455484512742315652166882850683721692964547448843598104385874050447011820051099399087175505815748958014671544911179795524159951193233504921329404534187047046492036161628814022862661479869322137573048331473599346645871295570237032991261433025344456232326409789544299441933427561947291495434188942844516539974096858281005872862193803356400358925349350554630231733687344283622639185011395343616612151755685912869590344206893",
                "ur":null
            }},
            "issuer_did":"{}",
            "schema_seq_no":1
        }}"#, ISSUER_DID)
    }

    pub fn get_unique_claims(proof_claims: &ProofClaimsJson) -> Vec<ClaimInfo> {
        let attrs_claims =
            proof_claims.attrs
                .values()
                .flat_map(|claims| claims)
                .map(|claim| claim.clone())
                .collect::<Vec<ClaimInfo>>();

        let predicates_claims =
            proof_claims.predicates
                .values()
                .flat_map(|claims| claims)
                .map(|claim| claim.clone())
                .collect::<Vec<ClaimInfo>>();

        attrs_claims.into_iter().collect::<HashSet<ClaimInfo>>()
            .union(&predicates_claims.into_iter().collect::<HashSet<ClaimInfo>>())
            .map(|v| v.clone()).collect::<Vec<ClaimInfo>>()
    }

    pub fn init_common_wallet() -> (i32, &'static str) {
        lazy_static! {
            static ref COMMON_WALLET_INIT: Once = ONCE_INIT;

        }

        unsafe {
            COMMON_WALLET_INIT.call_once(|| {
                //TODO Need clean after tests but not exists After function in Cargo
                TestUtils::cleanup_storage();

                //1. Create and Open wallet
                WALLET_HANDLE = WalletUtils::create_and_open_wallet("pool1", None).unwrap();

                //2. Create GVT ClaimDefinition
                let schema = AnoncredsUtils::get_gvt_schema_json(COMMON_SCHEMA_SEQ_NO);
                //TODO Fix it.....Convert String to &'static str
                let claim_def_json = AnoncredsUtils::issuer_create_claim_definition(WALLET_HANDLE, ISSUER_DID, &schema, None, false).unwrap();
                let res = mem::transmute(&claim_def_json as &str);
                mem::forget(claim_def_json);
                CLAIM_DEF_JSON = res;

                //3. Store three claim offers
                let claim_offer_json_1 = AnoncredsUtils::get_claim_offer(ISSUER_DID, COMMON_SCHEMA_SEQ_NO);
                let claim_offer_json_2 = AnoncredsUtils::get_claim_offer(ISSUER_DID, 2);
                let claim_offer_json_3 = AnoncredsUtils::get_claim_offer("CnEDk9HrMnmiHXEV1WFgbVCRteYnPqsJwrTdcZaNhFVW", 2);

                AnoncredsUtils::prover_store_claim_offer(WALLET_HANDLE, &claim_offer_json_1).unwrap();
                AnoncredsUtils::prover_store_claim_offer(WALLET_HANDLE, &claim_offer_json_2).unwrap();
                AnoncredsUtils::prover_store_claim_offer(WALLET_HANDLE, &claim_offer_json_3).unwrap();

                //4. Create MasterSecret
                AnoncredsUtils::prover_create_master_secret(WALLET_HANDLE, COMMON_MASTER_SECRET).unwrap();

                //5. Create and Store Claim Request
                let claim_req = AnoncredsUtils::prover_create_and_store_claim_req(WALLET_HANDLE,
                                                                                  "HEJ9gvWX64wW7UD",
                                                                                  &claim_offer_json_1,
                                                                                  CLAIM_DEF_JSON,
                                                                                  COMMON_MASTER_SECRET).unwrap();
                let claim_json = AnoncredsUtils::get_gvt_claim_json();

                //6. Create Claim
                let (_, xclaim_json) = AnoncredsUtils::issuer_create_claim(WALLET_HANDLE, &claim_req, &claim_json).unwrap();

                //7. Store Claim
                AnoncredsUtils::prover_store_claim(WALLET_HANDLE, &xclaim_json).unwrap();
            });

            (WALLET_HANDLE, CLAIM_DEF_JSON)
        }
    }
}

pub const COMMON_CLAIM_DEF_SEQ_NO: i32 = 1;
pub const COMMON_SCHEMA_SEQ_NO: i32 = 1;
pub const COMMON_MASTER_SECRET: &'static str = "common_master_secret_name";
