import os
from pathlib import PurePath
import re
import sys
import json
import logging
from mythril.ether.ethcontract import ETHContract
from mythril.ether.soliditycontract import SourceMapping
from mythril.exceptions import CriticalError
from mythril.analysis.security import fire_lasers
from mythril.analysis.symbolic import SymExecWrapper
from mythril.analysis.report import Report

from mythril.ether import util
from mythril.laser.ethereum.util import get_instruction_index


def analyze_truffle_project(sigs, args):

    project_root = os.getcwd()

    build_dir = os.path.join(project_root, "build", "contracts")

    files = os.listdir(build_dir)

    for filename in files:

        if re.match(r".*\.json$", filename) and filename != "Migrations.json":

            with open(os.path.join(build_dir, filename)) as cf:
                contractdata = json.load(cf)

            try:
                name = contractdata["contractName"]
                bytecode = contractdata["deployedBytecode"]
                filename = PurePath(contractdata["sourcePath"]).name
            except KeyError:
                print(
                    "Unable to parse contract data. Please use Truffle 4 to compile your project."
                )
                sys.exit()
            if len(bytecode) < 4:
                continue

            sigs.import_from_solidity_source(
                contractdata["sourcePath"], solc_args=args.solc_args
            )
            sigs.write()

            ethcontract = ETHContract(bytecode, name=name)

            address = util.get_indexed_address(0)
            sym = SymExecWrapper(
                ethcontract,
                address,
                args.strategy,
                max_depth=args.max_depth,
                create_timeout=args.create_timeout,
                execution_timeout=args.execution_timeout,
            )
            issues = fire_lasers(sym)

            if not len(issues):
                if args.outform == "text" or args.outform == "markdown":
                    print("# Analysis result for " + name + "\n\nNo issues found.")
                else:
                    result = {
                        "contract": name,
                        "result": {"success": True, "error": None, "issues": []},
                    }
                    print(json.dumps(result))
            else:

                report = Report()
                # augment with source code

                disassembly = ethcontract.disassembly
                source = contractdata["source"]

                deployed_source_map = contractdata["deployedSourceMap"].split(";")

                mappings = []

                for item in deployed_source_map:
                    mapping = item.split(":")

                    if len(mapping) > 0 and len(mapping[0]) > 0:
                        offset = int(mapping[0])

                    if len(mapping) > 1 and len(mapping[1]) > 0:
                        length = int(mapping[1])

                    if len(mapping) > 2 and len(mapping[2]) > 0:
                        idx = int(mapping[2])

                    lineno = (
                        source.encode("utf-8")[0:offset].count("\n".encode("utf-8")) + 1
                    )

                    mappings.append(SourceMapping(idx, offset, length, lineno))

                for issue in issues:

                    index = get_instruction_index(
                        disassembly.instruction_list, issue.address
                    )

                    if index:
                        try:
                            offset = mappings[index].offset
                            length = mappings[index].length

                            issue.filename = filename
                            issue.code = source.encode("utf-8")[
                                offset : offset + length
                            ].decode("utf-8")
                            issue.lineno = mappings[index].lineno
                        except IndexError:
                            logging.debug("No code mapping at index %d", index)

                    report.append_issue(issue)

                if args.outform == "json":

                    result = {
                        "contract": name,
                        "result": {
                            "success": True,
                            "error": None,
                            "issues": list(map(lambda x: x.as_dict, issues)),
                        },
                    }
                    print(json.dumps(result))

                else:
                    if args.outform == "text":
                        print(
                            "# Analysis result for " + name + ":\n\n" + report.as_text()
                        )
                    elif args.outform == "markdown":
                        print(report.as_markdown())
