text:
NOW Incs Results Bounce Off the Bottom
By Matthew DiLallo Fool.com
The rebound in oil prices off the bottom that occurred earlier this year is finally starting to flow through and positively impact NOW's (NYSE: DNOW) financial results. That recovery bumped up revenue from the prior quarter, which, when combined with continued cost reductions, led to a narrowing loss. That said, while the company believes the oil market hit bottom, it does see "a choppy recovery" up ahead.
NOW results: The raw numbers
Data source: NOW Inc.
What happened with NOW this quarter?
Acquisitions and sales in the U.S. turned around the revenue decline:
Sales in the U.S. rose 10% over last quarter, to $372 million. Without the impact of acquisitions, revenue would have only increased by 5%. Meanwhile, revenue is still down year over year, by 25% -- 36% when excluding acquisitions -- though that has outperformed the 45% plunge in the U.S. rig count.
Canadian sales slumped to $67 million, which was down 22% sequentially and 29% below the year-ago quarter. Driving down sales were fluctuations in the level of operating activity, though the rig count in the country rebounded 147% over the last quarter.
Sales internationally dropped 26%, to $81 million, and are now half what they were a year ago. The continued deterioration of the offshore market and customers using up their inventory drove down sales.
Rising revenue plus continued cost-containment initiatives narrowed NOW's loss. Meanwhile, it continues to generate cash flow, delivering $31 million in operating cash flow during the quarter and $186 million so far this year.
What management had to say
CEO Robert Workman commented on the quarter by saying that:
Workman noted that the uptick in drilling activity drove a slight improvement in its results during the quarter as producers started to put more rigs to work drilling shale wells. That fueled a noticeable uptick industrywide in shale-focused services and equipment sales.
For example, oil-field service giant Halliburton (NYSE: HAL) noted that sales in its shale-focused North American business gained 9% over the prior quarter. That improvement in its core market plus "relentlessly managing cost" enabled Halliburton to report a surprise profit last quarter.
That said, while the need for services spiked last quarter, equipment sales were mixed, which weighed on former NOW parent company National Oilwell Varco (NYSE: NOV) during the quarter. However, while its results still have not hit bottom yet, it did see a turn in some of its business segments. In fact, National Oilwell Varco noted that its North American revenue grew about 15% over the prior quarter.
While NOW believes that the market hit bottom during the second quarter, it does not expect a vast improvementin sales in the near term. Instead, Workman warned that "international offshore softness and seasonal holiday implications suggest a choppy recovery." This outlook lines up with what National Oilwell Varco and Halliburton recently said.
For example, National Oilwell Varco said that, while "international, offshore and capital equipment markets remain challenging, we believe declining global production and improving commodity prices are setting the stage for a broader recovery in 2017. Meanwhile, Halliburton said that it's cautious on the fourth quarter due to the holidays and seasonal-related downtime, but this weakness "does not change our view that things are getting better for us and our customers."
These outlooks all suggest that 2017 could be a much better year for NOW.
A secret billion-dollar stock opportunity The world's biggest tech company forgot to show you something, but a few Wall Street analysts and the Fool didn't miss a beat: There's a small company that's powering their brand-new gadgets and the coming revolution in technology. And we think its stock price has nearly unlimited room to run for early in-the-know investors! To be one of them, just click here.
Matt DiLallo owns shares of National Oilwell Varco. The Motley Fool owns shares of and recommends National Oilwell Varco and NOW. The Motley Fool owns shares of Halliburton. Try any of our Foolish newsletter services free for 30 days. We Fools may not all hold the same opinions, but we all believe that considering a diverse range of insights makes us better investors. The Motley Fool has a disclosure policy.


text:
Two stage leaching process for selective...
Two stage leaching process for selective metal extraction from spent nickel metal hydride batteries
Meshram P., Somani H., Pandey B. D., Mankhand T. R., DEVECİ H., Abhilash A.
JOURNAL OF CLEANER PRODUCTION, vol.157, pp.322-332, 2017 (SCI-Expanded)
Doi Number: 10.1016/j.jclepro.2017.04.144
Journal Name: JOURNAL OF CLEANER PRODUCTION
Keywords: Spent NiMH batteries, Baking, Sulfuric acid, Water, Reductive leaching, RARE-EARTH-ELEMENTS, SOLVENT-EXTRACTION, RECOVERY, KINETICS, COBALT
In this study, a closed loop two-stage leaching process has been experimentally and theoretically established for the selective dissolution of metals from electrode material of spent Ni-MH batteries. In the first step, baking parameters, such as temperature, acid concentration, and duration were optimized as 300 degrees C, 2 mL H2SO4 for 1.5 g of cathode powder, 90 min. The thermodynamic feasibility of the process ascertained the spontaneous formation of water-soluble sulfates of nickel, zinc, and rare earth elements during the acid baking. Post-baking, a two-stage leaching was performed for leaching of different metals present in the baked material. In first stage leaching with water, 91.73% Ni, 94% Zn, 91% rare earth elements were extracted with a low recovery of 36.5% Co, 22.1% Fe, 23.5% Mn at 75 C in 1 h. Owing to the low recovery of Co and Mn, 2nd stage leaching was performed using NaHSO3 in H2SO4 at 95 degrees C for 1 h, improving extraction to Co (55%) and Mn (74%). The overall recovery of the metals was found to be 98.2% Ni, 91.4% Co, 98% Zn, 97.8% Mn and 96% rare earth elements. The two-stage process (baking and leaching) outperforms direct sulfuric acid leaching of cathode powder due to lower acid consumption and its selectivity of metals. (C) 2017 Elsevier Ltd. All rights reserved.

redpajama_set_name: RedPajamaCommonCrawl


All transition operations can perform automated processing. Some of the automated processing is controlled by transition properties in the replication property files. Other automated processing is based on the state of the database that is being transitioned.


text:
Home | Contact us | Useful links | Map
KINGDOM OF MOROCCO
MOROCCAN SAHARA
Map of Morocco
The Kingdom of Morocco
Language and Religion
National Anthem and Armorial bearing
Physical and human framework
Since the beginning of history there have been Berbers in North Africa end they were already well established when the Phoenicians made their first incursions in 1200 BC. Their origins are uncertain but thought to be Euro-Asiatic, The generic name Berbers, was imposed on them by the Arabs meaning those who were not Arabs.
Sanhaja, Masmoda, and Zenata are the three tribes constituting the Berbers .
The Sanhaja, from which sprang the Almoravide dynasty (the founders of Marrakesh) were nomads who in the 11C conquered the desert and much of the region to the south of it for Islam; the Masmouda were quiet farming people who lived in the north and west and in the High and Anti Atlas mountains and it was they who gave rise (from out Tin Mal , S of Marrakesh to the Almohade Dynasty which displaced the Almoravides; the Zenata a sub-group of which the - Beni Marin- swept in from the empty region between the Tafilalet and Algeria to become the great Merinide dynasty, were tough, horse-riding nomads of the cold high plateaux of the interior.
Joined to the Arabs only by Islam, the Berhers have always held themselves proudly separate in all other matters, especially in the rural and mountain areas. There is no standard form of Berber language since each tribal group has always used its own version, and there is no recognized Berber script or literature. Their strongest form of self-expression is music and dancing, which is rhythmic but with little harmony, compelling, loud and often quite intoxicating.
The Phoenicians and Carthaginians:
The first invaders are believed to have been the Phoenicians, coming from the land known then as Caanan in the Eastern Mediterranean in the 12C BC. Gradually they established trading posts along the north coast of Africa and traces at their occupation have been found at Lixus (Liks), which was probably the earliest, Tangier (Tangis)Mellilia (Russadir) Chellah part of Rahat and Tamuda (near Tetouan). These traces are usually in the form of fish-salting factories and are often heavily overlaid by Roman remains. The Phoenicians were essentially a maritime people, not interested in conquering or colonizing, and paying scant attention to he primitive berber tribes and poor agricultural land of the interior; therefore, their colonies were little more than enclaves along the coast, separated by great open spaces of wasteland which they did not need. Their main center of influence was Carthage (Tunisia). When Carthage became an independent state, the more civilized Carthaginians arrived and turned the north coast settlements into prosperous towns:they are known to have developed the fish salting and preserving into quite a major industry and their anchovy paste, called "garum" was widely exported. They also grew wheat and probably introduced the grape.
The Carthaginians exercised a considerable cultural influence on the Berbers even long after the Sack of Carthage in 146 BC; indeed, it probably increased at that time as hundreds of Carthagians fled westwards and took refuge from the Romans in the friendly enclaves along the coast .
The Romans:
After they had taken Carthage, the Romans moved westwards into the Berber kingdoms of Mauritania and Numidia(Algeria now) which became part of the Roman Empire. In 13 BC the Emperor Octavius granted the kingdom of Mauritania to the young Berber prince, Juba, son of Juba I of Numidia who had committed suicide 13 years earlier after the defeat by the Romans at the battle of Thapsus. In 25 8(2 they added the whole of Numidia to his realm. Educated in Rome and married to the daughter of Mark Antony and Cleopatra, known as Cleopatra -Stlene, hts pedigree was unpeccabte dnd he ruled wisely, probably living in Volubilis. This had already become a h3erher town of sonic statiding betore the Rornans arrived, due in part to the natural fertility of the region surrounding it and in part to the te~ching of the Carthaginians enabling the Berbers to get the best out ol the land.
The next 400 years formed Morocco 's Dark Age and very little is known about this period. The Vandals and Goths who were sweeping through Spain may have touched the northern tip of Morocco on their way eastwards to Carthage but there are no traces that they have stayed. The Berbers in the mountains and the desert continued life much as before. The Romnanised, part-Christian, Berber Mauritanians of the cities of Volubilis, Sala Colonia,(Chella) Tingis and others held on to their mixed cultural heritage and maintained a degree of civilization, as evidenced by one or two Latin inscriptions, found in several places, which date from as late as the mid 7C. But the weak and divided nature of the country was to prove no match at all for the next wave of invaders.
The Idrissides.
By the 7C AD the Arabs were in full expansion. They were inspired primarily by their fierce desire to spread their own religion of Islam throughout the World. but they were doubtless particularly attracted to North Africa by the endless stretches of desert sand which were to them like home. It was in 670 that the first Arab invasions of the North African coastal plain took place under Oqba Ben Nafi, commander of the Umayed dynasty in Damascus.
He is best known for having founded the city of Kairwan (S of Tunis) and for having built the first ever mosque in North Africa, He swept with his army into what is now Morocco in the year 683. Which he called this Maghreb al Aqsa or farthest West
When a second Ummayed leader, Musa lbn Nouasser, arrived in 703,the Berbers were not unwilling to participate in the Islamic expansion into southern Spain and into the more southerly areas of Morocco, However, the progress of Islam remained patchy and small enclaves of Christians still existed in the interior though many fled to Spain). This lack of national unity persisted until the arrival of ldriss Ben Abdallah, a descendant of the prophet Mohammed, in 788. There are very few original Arab sources available for reference about this early period but that which is most frequently cited by historians is the Raoud El Kartas, a chronicle by the 13C writer from Fez, Ibn Abi-Zar-El-fasi: from this we learn that ldriss Ben Abdallah fled into Egypt from the Abbasides .He arrived by way of Kairwan, first in Tangier and then in the former Roman city of Volubilis where was received by Berbers already fully converted to Islam by the earlier Arab arrivals. The Berbers chief proclaimed Idriss King and pledged the support of his own and neighboring tribes. It seems that the arrival of an assured leader who would guide the country out of the spiritual uncertainties which had increased since the death of Oqba ben Nafi was welcome. Idriss II was born after his father's death and was educated and prepared for his awesome task. He became King at the age of 12, in 804.
He founded Fez which in his time was well prospered. In 818, 8000 Arab families arrived after being expelled by Christians from the Emirate of Cordoba in Spain. Seven years 2000 families came from Kairwan. These 'refugies' were welcomed and installed, respectively, on the right and left banks of the river which divides the town. It was very largely as a result of the of these people, with their refinements and skills, that Fez became a great spiritual and intellectual center whose influence very much reached to the far north of the country and, later, beyond. Idriss II who died in 828In Morocco came the next dynasty, from the south The Almoravides.
They were camel-riding Berber of the Sanhaja group of tribes, to whom cultivation of the soil was unknown. For a century or more they Have been conquering and converting to Islam the black countries of the Sahara, inspired by their search for the source of gold which had been flowing into Morocco from somewhere in the region of the Niger river.
The campaigns fought by the Almoravides were violent and successful and they soon controlled the whole of the south, under the leadership of Ibn Tachafine ( the founder of Marrakech in 1062, along with Al Koutoubia Mosque). Much of Spain became part of the Almoravide empire. A period of peace and prosperity followed, enriched by the refined culture of the Andalucian courts to which had been added a healthy dose of Berber virility and discipline.
The Almohades
A new power was emerging. The Almohades were Masmoda berbers from the high and the Atlas mountains .their leader, Mohamed Ibn Toumart, was a man of extarordinary power. The foundation of his doctorine was absolute unity with God, from which stemmed the name of Mouwahhidine, meaning unitarian.
Yacoub Al Mansour was a great statesman. The whole country prospered at his reign: spiritually, intellectually, economically and architecturally. Marrakesh was still the capital. Fez flowered as never before, and the end of the 12C is generally regarded as an apogée in Morocco's history.
The Merinides:
The Beni Marin were a tribe of Nomadic Zenata Berbers who came from an area between Taza and Algeria.
The policy of the Merinides in running the affairs of Morocco was enlightened they the first Moroccans to introduce a simple form of civil service. They were also the first to introduce the Mellah, or Jewish quarters in all major tows, so that the Jewish could live secure and unmolested. The Merinides were also the first to introduce the concept of Medersa(originated from Baghdad and later on introduced to Egypt). Fez is liberally scattered with fine examples within easy walking distance of the Karawiyine. Sultan Abu Inan built the Bou Inania Medersa in Fez.
The Merinide Soltans surrounded themselves with scholars who could lecture not only about Koran but also about science and law , poetry and geography. The well-known traveller Ibn Batuta( 1304-78) was an honored member of the court of Abou Inan who gave him a secretary to write down stories of his travels as in the black Sea and Tambouktoo. Ibn Khaldoun, the 14 C historian and a Spanish Muslim spent many years as adviser and close associate of Merinide Sultans.
When the dynasty was feebled, Spain and Portugal were turning eyes towards Morocco .At that time, there was another ruler, Ibn Wattas, who came from Asilah to Fez. When he left Asilah, the Portugueuse invaded Asilah and took many family membersof Ibn Wattas and 5000 people as slaves, then Ibn Wattas signed a treaty with Portugal which allowed the portugueuse to invade Asilah, Tangier , Essaouira(Mogador)Mazagan (El Jadida) Zemmour, Safi and Agadir and Ceuta.
So, for a time, almost the entire west coast of Morocco became a seperate Portugueuse colony.
The Saadians
Were decsendants from the prophet Mohamed. They originally came from Arabiain the 12C, and settled in the valley of the Draa in the South of Morocco. They moved to Fez and were easily given power by the Wattasides. In the 16 C, they rebuilt the town of Taroudant as their capital.
During the Saadians'reign, the portugueuse had always dreamt of regaining power in the Moroccan territories. King Sebastian who was asked helped from one of the sultans nephews, landed in Asilah with a massive force of soldiers, there followed a memorable battle in 1578 at Ksar Kbir. The battle was known as the battle of the Three Kings, in which the portugueuse army was defeated, and in which King Sebastian, the Pretender and the Sultan Abd El Malik died.
Glorious in their victory, the Saadians under the reign of Ahmed Saadi(1578-1603) settled down in Fez. The Badi Palace and the Ethereal Mausoleum ( les tombeux Saadians) in Marrakesh are proofs of the wealth of Saadians.
The Alaouites
They were also decsended from the prophet Mohamed. They had arrived from Arabia some three ceunturies earlier to settle near Rissani in the Tafilalet region in the south.( They are referred to as Filali). Unlike preceding dynasties they did not move and seize power but were formally invited by the people of Fez to come to the capital and take over the throne of Morocco.
The first Alaouite ruler ,Moulay Rachid, reigned in 1666. He restored order with a firm hand, revived the life of all mosques and drove out all the pretenders. Under the reign of Moulay Ismael( 1672-1727) Morocco was made again a great country.He exchanged ambassadorts with many leading Powers. Meknes was chosen by Moulay Ismael as the imperial city which he made his capital. Today, the miles of ruined walls, palaces and stables bear witness to his energy and ambition and also to the scale of his success.
In 1757, another wise and strong Alaouite ruler came to the throne. He was Mohamed ben Abdellah. Hebuilt the city of Essaouira and invited the English, the French, and the jewish people to settle and to trade there.
Moulay el hassan acceded to the throne in 1873. He had the task of pacifying the tribes and was the first monarch to enter the wild Souss Area, where the tribes never acknowledged the authority of the state.
During his reign, the European governments suggested ways of reforming administration , such as fixed salaries, civil servants and a more structured method tax collection.
Attacks on foreigners were frequent and the tribes took power into their own hands. At that time, the French occupied Morocco, The Spaniards, for historical reasons, insisted on sharing the influence on Morocco. In 1906, the Conference of Algeciras( in which 30 nations were present) took place and had the effect of internationalizing the whole affair. Tangier was an international free port, and the whole country was under the protectorate of the French government.
In 1912, Sultan Moulay Hafid signed the Treaty of FEZ . He was relieved from the power to govern. The country was under the controle of a French Resident- General called Lyauty.He aimed to pacify and to construct. He also built the ports of Casablanca and Kenitra, the new towns of Rabat, Fez, Meknes and Marrakech, while the old medina of theses cities remained untouched. A modern educational system was introduced, the administration was modernised and the legal system reformed. Still the tribes in the south of Morocco were very rebellious. By 1920, there was a more structured rebellionand resistance in the Rif Mountains, led by Abdelkarim Khattabi. The French began by driving a wedge between Berbers and Arabs. The Sultan, at that time, signed a beber decree in 1930, which on the contrary of what the French calculated, brought the two parties even closer. Then , a serious movement of national independance was born especially formed by young intellectuals from Rabat and Fez.
In 1927, Moulay Youssef was succeded by his son Mohamed V, aged 17 years old. It was not until after World War II that the independence movement really gathered momentum. The troops Moroccans provided for the French army had conducted themselves with honor. At that time, an official independence party was formed called Istiqlal,whose first act was to send a memorundum to the sultan and the French authorities asking for independence and a democratic constitution. The immediate reaction to this request was the arrest of several Istiqlal leaders , whereas the sultan refused to sign any more decrees concerning his people.
In August 1953 the royal family was deported to Corsica and Madagascar, and another person was designed by the French to sit on the throne. Violence towards the French officials was the reaction of the Moroccan people, who claimed the return of the king.
In December 1956, The king was taken to France , where he signed a declaration promising that there would be a constitutional monarchy which would move towards ademocratic state.
In March 1956, the French signed an agreement in which they granted full independence to Morocco. The Spanish did the same and Tangier lost its international status during the same year.
The Sultan formed a government and French Officials were gradually replaced by Moroccans.
In 1961, King Mohamed V was succeded by Crown Prince Hassan II who presented a new constitution. Tthe first elected parliament assembled on 1963.
The Green March, which took place in 1975, is the most important event in the Reign of Late Hassan II. 350.000 unarmed Moroccans marched south into the desert to reassert the sovereinty of the Sahara which was in the hands of the Spanish at that time. A movement called The polisario was formed by the help of Algeria and Libya to oppose Moroccan rule and to fight for self determination of the Sahrawi.
© Copyright Embassy of The Kingdom of Morocco 2009


Metropolis Drachen | Bag for Skywatch Eole or Meteos | Buy online now!
Wind speed and temperature meter, including a calculation mode of the wind-chill factor (temperature felt when the wind is blowing). Used by fire fighters, farmers, scientists, etc.
The Slide from Elliot is an allround kite, which also has a wide range of trick potential.


text:
UNOPS Logo
Sub Navigation - Competencies
From supplying solar panels to medical supplies, UNOPS is always looking for new suppliers.
The challenges the world faces have never been greater. Join us to help build the future.
Discover our latest press releases, statements, publications and other resources to learn more about our work.
We're committed to operating with transparency and accountability, and building trust between UNOPS and our governing bodies, our partners and the communities we serve.
We want to hear from you. Learn how to get in touch with us!
Projects & Locations
Multi-Country Programmes
UNOPS SPOTLIGHT
UNOPS latest report paves a path towards responding to the climate crisis and building a sustainabl...
New livelihood opportunities are helping lift Guinea's youth out of poverty and seek a brighter fut...
We bring industry best practice to infrastructure projects, delivering results that last – fit for the people we serve.
With over 20 years of experience, UNOPS brings the expertise to get projects done on time and on budget.
We specialize in transparent and sustainable procurement, helping governments make public procurement more efficient.
Our tailored human resources solutions are designed to help bring the best people on board, to get the job done right.
Managing portfolios around the world, our financial management services blend private sector efficiency with UN values.
We're committed to helping advance climate action and building community resilience to climate change.
With resilience and sustainability in mind, we're committed to helping build a healthier future for all
We strive to advance gender, diversity and inclusion – in our workforce and in the communities we serve.
Mission. Vision. Purpose.
Mandate & Reforms
Speeches & Statements
Statement in response to independent reviews of UNOPS S3i
The importance of inclusive infrastructure for climate action
gender equality climate action executive director jobs partnerships humanitarian UNOPS mission business opportunities
New bridge in Uganda better connects refugees, host communities to services
Nyara Bridge will increase access to social and protection services as well as improve the efficiency of humanitarian assistance.
In the Rhino Camp Refugee Settlement in Terego, Uganda, it previously took nearly an hour and a half to drive a 54-kilometre route between two areas split by the Nyara River. Crossing the river during the rainy season was especially dangerous, with some community members losing their lives in the process.
The new Nyara Bridge – implemented by UNOPS using $2.4 million in funding from the government of Japan and Uganda's Ministry of Works and Transport – changes that. The bridge reduces the travel distance between the two areas to just 10 kilometres, cutting the travel time to less than 15 minutes – and makes the journey across the river much safer.
It is our sincere hope that the bridge will address access issues, especially during the rainy season, as well as support local integration of communities using the bridge.
Rainer Frauenfeld - Director of UNOPS Multi-Country Office in Kenya
"The Nyara Bridge will play a role as a holistic path of local integration and a promotion of peaceful coexistence of refugees and host communities," said H.E. Fukuzawa Hidemoto, Ambassador of Japan to Uganda.
As part of the project, a local contractor was used to build the bridge, providing livelihood opportunities to refugees and members of the local communities. In addition, the enhanced road transport capacity that the bridge brings will increase access to business opportunities, help grow local economies in host communities and increase the long-term self-reliance of refugees.
"The entire West Nile region is expected to benefit from the construction of this bridge," said Bageya Waiswa, Permanent Secretary Ministry of Works and Transport.
In coordination with Uganda's Ministry of Works and Transport, UNOPS also conducted a technical training session on bridge inspection, operations and maintenance. This will help ensure the long-term maintenance of the bridge. The technical knowledge provided by the training will also be vital for future bridge construction projects.
UNOPS-managed EU-IGAD programme highlighted at Paris Peace Forum
The European Union-Intergovernmental Authority on Development (EU-IGAD) COVID-19 Response programme has benefitted millions across 7 countries in east Africa.
A chance at a fresh start
Improving access to healthcare services in eastern Sudan
In partnership with the government of Italy, UNOPS is working with Sudan's Ministry of Health and local health authorities in Kassala State to enhance health infrastructure.
UNOPS Headquarters
2100 Copenhagen, Denmark


Tell me about a situation in which you had to work with fellow team members who did not like you.
As is often the case, if you are in a management role, you will have employees under you who may not like you. Now, if your entire team despises you, you may have a much bigger problem. One in which you shouldn't tell the interviewer about. Keep your answer short and simple, and focus on one employee whose personality didn't mesh with yours. And you were able to figure out a way to motivate and not let this personality clash affect your performance together. Also, do not mention names of employees.
"When I first started in my current role, I was replacing a very beloved manager who had retired. The team that I inherited was not happy about the change, and they initially set out to give me a hard time. I addressed it within the first week, acknowledging that I had big shoes to fill. I believe that the team respected this honest approach and slowly everyone came around once they realized that I had their best interest in mind."
"When I worked as a temporary administrative assistant, I did not always jive with the head administrators. Because I was in a temp role, I felt overlooked or treated poorly. I did my work to the best of my ability regardless, because I wanted to keep a good reputation with my agency. I don't need everyone to like me, but I do need to deliver great work!"
"I worked with someone in my last role who had a very different style leadership style than I. For whatever reason; they were unwilling to make an effort to adapt to rapid change or the fast pace in which the team worked. I was always asking questions to learn the most I could think about the business, while this person would have preferred everything stay status quo. I consider myself to be fairly perceptive to people around me and took it upon myself to approach this person in private to build a relationship with them outside of the work group. It helped."
"That's a loaded question! I am not sure I have had anyone outright say they don't like me; however, I have butted heads with team members at times. Generally speaking, these situations are surrounding a difference of opinion on the action to take. I am from the new school of marketing and follow algorithms carefully where I have one team member who is all about print advertising. We have learned to work with each other's strengths and weaknesses at the same time."
"When promoted to manager of my current department, I was the youngest on the team. This silly fact made me not very popular in their eyes, and they thought they could treat me as though I were their daughter, rather than their boss. I made it clear that while I respect their experience, I would not accept that treatment. I did so kindly over a cup of coffee while emphasizing their importance to the team and me, explaining my credentials and goals for the team. We've all done well together since and I find our department to be a delightful place to work!"
"I'm all about tackling a problem when you see it, and being transparent, so the issues are quick to surface. This approach doesn't go over well with my low-achievers who are hoping to skate by doing the bare minimum. Anytime this happens, I speak with the employee in question off of the floor to assess what's going on and how we can get them up to speed to hit numbers. Sometimes they rise to the occasion, and sometimes they don't or can't. In either case, I am sure to explain what the problem is, how I hope to fix it, solicit their input, and work to find a plan of action together. I don't take their dislike of my style personally, but instead try to bridge the gap to see a cohesive, collaborative environment for all."
"I am used to students not always liking me. You cannot win everyone over! Situations like that do not sway me or affect my teaching style in the least. As far as my colleagues, I do not always get along famously with everyone at all times, but we all respect each other despite differences in our teaching style."
"As an addiction nurse, I realize my care is crucial in patient outcomes. This is stressful, but I always try to compartmentalize my stress and channel it, in a healthy way. I have a strong support network and yoga is my 'go to' stress reliever, after a shift."
"I took the initiative to bring a recent criminal background check with me today. I understand that you are obligated to run a variety of checks on me, to which I am happy to comply."
2. What type of manager brings out the best in you?
3. Tell me about a time when you disagreed with your boss.


Hey ho, Lords and Ladies! Welcome back to another edition of Under the Hood; an article series where we look into the mechanical history of Magic and have a root around for what we can find! This week comes a blast from the past: the knightly mechanic of Flanking.
We first saw Flanking in Mirage block, as a flavourful manifestation of knightly combat (and does a better job than Horsemanship, I must say). When a creature with flanking is blocked by a creature without flanking, that creature is inherently at a disadvantage because of the creature's manoeuvrability (represented in this case by -1/-1). Being flavoured around knights – small creatures by Magic's standards – the creatures it was on were never particularly large, but the addition of flanking plus white and red's affinity for combat tricks makes even the smallest flanking creature a cause for concern to a would-be blocker. It's important to note that it only works when attacking – for the very good reason that you could multiblock and get multiple triggers on one attacker, but can't multi-attack onto one blocker, save the creatures like Hundred-Handed One (or banding, but screw that). The presence of flanking in a format also makes X/1s much worse, as a Suq'ata Lancer doesn't need to worry about a Thalia, Guardian of Thraben if it hits before first strike even matters. I for one may be alone in this, but I can't wait to see the return of flanking in force – it's a simple, yet flavourful mechanic that shapes the way combat works without overwhelming it, like First Strike.
In terms of standard impact, a few flankers made it into top decks during Mirage block standard, having their time in the sun before the awkward period of Combo Winter descended upon us. In this batch of Decklists from PT Paris '97, we can see that flankers such as Suq'ata Lancer, Cadaverous Knight or Searing Spear Askari showed up in various aggro builds; though notably not many of the white cards of this primarily white mechanic made much of a showing; though from this list (and others of the era), it seems like white didn't make much of an impact on this era, and when it did it was in a Wx control shell, often benefitting heavily from blue's tempo creatures such as Man-o'-war or Waterspout Djinn.
As for any other format; nothing of note has been seen. The quality of creatures have vastly improved, and dropping a Cadaverous Knight is laughed at by Delver of Secrets, Tarmogoyf or Deathrite Shaman. Answers to the pseudo-vanilla beaters are just too good, and without a decent enough body flanking creatures will continue to struggle to make an impact on any format… Except EDH!
In the wide open plain of EDH, the effectiveness on flankers will vary depending on the meta. If the meta plays heavily to ramping out fatties with decks like Omnath, Locus of Rage, or Nylea, god of the Hunt, then you might as well be playing vanilla 1/1s. Likewise, in a meta that doesn't play on the board and prefers the stack; decks like Mizzix of the Izmagnus, Riku, Zur the Enchanter, then you may just prefer playing something more aggressive.
But, there are openings. Ones that hate on those players, but typically run answers so narrow that weenie aggro can open it right up. Something staxxy, that runs paper-thin creatures such as Aven Mindcensor, Thalia, Guardian of Thraben, Spirit of the Labyrinth… These all die before they can damage a flanker. Even some of the bigger hatebears such as Gaddock Teeg or Ethersworn Canonist can't kill the average flanker. It takes very specific conditions, but I believe your average flanker can be great.
Flanking's come up again in recent times as well, with the printing of Sidar Kondo of Jamuraa. While he only hits some of the flanking colours (primary in Red as well as White), he's a great start towards the sort of strategy that a flanking deck would be interested in; providing some much needed evasion to small bodies while also not being an easy combat target himself, with the Partner ability to allow for another colour enabler to get him his sweet Telim'tor.
In terms of allstars of the flanking variety, I have a couple in mind. First of all, the most flexible piece of flanking tech, Jabari's Banner. With this, you can turn any creature into a flanker; be it big and trampling, or something like Phage or Guiltfeeder that you'd prefer to not be chump blockable. Having no colour identity allows it to fit into any deck, though obviously not every strategy would be able to take full advantage of it due to flanking's aggressive nature as a mechanic.
As for the Flanking deck, Shout out to my guys Cavalry Master and Telim'Tor. When running a critical mass of creatures with Flanking, these creatures make life very uncomfortable for opposing decks. Suddenly x/2's can't block safely either, or they're being threatened by the red version of Pathbreaker Ibex (and they might be given even less time to react, if you have Concordant Crossroads or Hall of the Bandit Lord).
Perhaps my favourite (and possibly the singly most powerful flanker) is the Pentarch Paladin. While a pricey (and difficult to splash) inclusion in a deck, this guy hates colours like no other paladin would. Once he loses summoning sickness, he lets loose on every permanent of that colour. Creatures, Planeswalkers, he doesn't care. You pick the colour, and you can destroy it. And to top it, he gets flanking too; he can take out creatures mid-attack, blockers in an unfavorable trade, Planeswalkers on the brink of ultimate; this guy does it all, and he does it (relatively) cheap, to boot.
Terrible puns aside, this deck is a Knight tribal deck, boasting a hefty 39 creatures, half of them possessing Flanking. Lead by Sidar Kondo (and assisted by Bruse Tarl), this Naya deck aims to ride on the field and gradually overwhelm with efficient weenies. The commanders synergise with an aggro strategy, Kondo enables evasion on your weenies, and Bruse allows your knights to hit hard, and make combat even more difficult to be on the receiving end of.
Its built to be an aggressive build, featuring a low 35 land, and a rock suite to allow you to accelerate to the top of your curve. You won't have much of a hand, and for that there are card advantage effects such as Brass Herald or Sylvan Library to power your draws once your hand runs thin.
Aggro is difficult in multiplayer, and thats why there are plenty of lords along with the necessary tutors in order to find them to keep pressing the advantage. Knight Exemplar gives our board resilience to board wipe effects on top of an anthem, and Kinsbaile Cavalier doubles the damage your knights can put out at a moment's notice. Double strike synergises well with our enchantments, as Break through the Line allows us to ping chunk of 2-4 (sometimes more by pumping after the unblockable effect has been applied – thanks Zhalfirin Commander). The true haymaker comes in Shared Animosity; +X/+0 is a lot across the team, and upwards of 2 every combat becomes difficult to deal with over time; doubling that with Kinsbaile Cavalier shortens the clock dramatically.
Thats it for now, guys! Like Flanking? Hate it? Indifferent? Be sure to let me know – any suggestions for future additions for Under the Hood are appreciated. Also if you like what you see, be sure to like our Facebook feed for updates on future articles! For now though, Unmistakable out.


text:
#include "map/framework.hpp"

#include "map/ge0_parser.hpp"
#include "map/geourl_process.hpp"
#include "map/storage_bridge.hpp"

#include "defines.hpp"

#include "routing/online_absent_fetcher.hpp"
#include "routing/osrm_router.hpp"
#include "routing/road_graph_router.hpp"
#include "routing/route.hpp"
#include "routing/routing_algorithm.hpp"

#include "search/intermediate_result.hpp"
#include "search/result.hpp"
#include "search/search_engine.hpp"
#include "search/search_query_factory.hpp"

#include "drape_frontend/gui/country_status_helper.hpp"
#include "drape_frontend/visual_params.hpp"
#include "drape_frontend/watch/cpu_drawer.hpp"
#include "drape_frontend/watch/feature_processor.hpp"

#include "indexer/categories_holder.hpp"
#include "indexer/classificator_loader.hpp"
#include "indexer/drawing_rules.hpp"
#include "indexer/feature.hpp"
#include "indexer/map_style_reader.hpp"
#include "indexer/scales.hpp"

/// @todo Probably it's better to join this functionality.
//@{
#include "indexer/feature_algo.hpp"
#include "indexer/feature_utils.hpp"
//@}

#include "storage/country_info_getter.hpp"

#include "platform/local_country_file_utils.hpp"
#include "platform/measurement_utils.hpp"
#include "platform/mwm_version.hpp"
#include "platform/platform.hpp"
#include "platform/preferred_languages.hpp"
#include "platform/settings.hpp"

#include "coding/internal/file_data.hpp"
#include "coding/zip_reader.hpp"
#include "coding/url_encode.hpp"
#include "coding/file_name_utils.hpp"
#include "coding/png_memory_encoder.hpp"

#include "geometry/angles.hpp"
#include "geometry/distance_on_sphere.hpp"

#include "base/math.hpp"
#include "base/timer.hpp"
#include "base/scope_guard.hpp"

#include "std/algorithm.hpp"
#include "std/bind.hpp"
#include "std/target_os.hpp"
#include "std/vector.hpp"

#include "api/internal/c/api-client-internals.h"
#include "api/src/c/api-client.h"

#include "3party/Alohalytics/src/alohalytics.h"

#define KMZ_EXTENSION ".kmz"

#define DEFAULT_BOOKMARK_TYPE "placemark-red"

using namespace storage;
using namespace routing;
using namespace location;

using platform::CountryFile;
using platform::LocalCountryFile;

#ifdef FIXED_LOCATION
Framework::FixedPosition::FixedPosition()
{
  m_fixedLatLon = Settings::Get("FixPosition", m_latlon);
  m_fixedDir = Settings::Get("FixDirection", m_dirFromNorth);
}
#endif

namespace
{
  static const int BM_TOUCH_PIXEL_INCREASE = 20;
  static const int kKeepPedestrianDistanceMeters = 10000;
  char const kRouterTypeKey[] = "router";
  char const kMapStyleKey[] = "MapStyleKeyV1";
}

pair<MwmSet::MwmId, MwmSet::RegResult> Framework::RegisterMap(
    LocalCountryFile const & localFile)
{
  LOG(LINFO, ("Loading map:", localFile.GetCountryName()));
  return m_model.RegisterMap(localFile);
}

void Framework::OnLocationError(TLocationError /*error*/)
{
  CallDrapeFunction(bind(&df::DrapeEngine::CancelMyPosition, _1));
}

void Framework::OnLocationUpdate(GpsInfo const & info)
{
#ifdef FIXED_LOCATION
  GpsInfo rInfo(info);

  // get fixed coordinates
  m_fixedPos.GetLon(rInfo.m_longitude);
  m_fixedPos.GetLat(rInfo.m_latitude);

  // pretend like GPS position
  rInfo.m_horizontalAccuracy = 5.0;

  if (m_fixedPos.HasNorth())
  {
    // pass compass value (for devices without compass)
    CompassInfo compass;
    m_fixedPos.GetNorth(compass.m_bearing);
    OnCompassUpdate(compass);
  }

#else
  GpsInfo rInfo(info);
#endif
  location::RouteMatchingInfo routeMatchingInfo;
  CheckLocationForRouting(rInfo);

  MatchLocationToRoute(rInfo, routeMatchingInfo);

  CallDrapeFunction(bind(&df::DrapeEngine::SetGpsInfo, _1, rInfo,
                         m_routingSession.IsNavigable(), routeMatchingInfo));
}

void Framework::OnCompassUpdate(CompassInfo const & info)
{
#ifdef FIXED_LOCATION
  CompassInfo rInfo(info);
  m_fixedPos.GetNorth(rInfo.m_bearing);
#else
  CompassInfo const & rInfo = info;
#endif

  CallDrapeFunction(bind(&df::DrapeEngine::SetCompassInfo, _1, rInfo));
}

void Framework::SwitchMyPositionNextMode()
{
  CallDrapeFunction(bind(&df::DrapeEngine::MyPositionNextMode, _1));
}

void Framework::InvalidateMyPosition()
{
  CallDrapeFunction(bind(&df::DrapeEngine::InvalidateMyPosition, _1));
}

void Framework::SetMyPositionModeListener(location::TMyPositionModeChanged const & fn)
{
  m_myPositionListener = fn;
}

void Framework::OnUserPositionChanged(m2::PointD const & position)
{
  MyPositionMarkPoint * myPosition = UserMarkContainer::UserMarkForMyPostion();
  myPosition->SetUserPosition(position);

  if (IsRoutingActive())
    m_routingSession.SetUserCurrentPosition(position);
}

void Framework::CallDrapeFunction(TDrapeFunction const & fn)
{
  if (m_drapeEngine)
    fn(m_drapeEngine.get());
}

void Framework::StopLocationFollow()
{
  CallDrapeFunction(bind(&df::DrapeEngine::StopLocationFollow, _1));
}

Framework::Framework()
  : m_bmManager(*this)
  , m_fixedSearchResults(0)
{
  m_activeMaps.reset(new ActiveMapsLayout(*this));
  m_globalCntTree = storage::CountryTree(m_activeMaps);
  m_storageBridge = make_unique_dp<StorageBridge>(m_activeMaps, bind(&Framework::UpdateCountryInfo, this, _1, false));

  // Restore map style before classificator loading
  int mapStyle = MapStyleLight;
  if (!Settings::Get(kMapStyleKey, mapStyle))
    mapStyle = MapStyleClear;
  GetStyleReader().SetCurrentStyle(static_cast<MapStyle>(mapStyle));

  m_ParsedMapApi.SetBookmarkManager(&m_bmManager);

  // Init strings bundle.
  // @TODO. There are hardcoded strings below which are defined in strings.txt as well.
  // It's better to use strings form strings.txt intead of hardcoding them here.
  m_stringsBundle.SetDefaultString("country_status_added_to_queue", "^\nis added to the downloading queue");
  m_stringsBundle.SetDefaultString("country_status_downloading", "Downloading\n^\n^");
  m_stringsBundle.SetDefaultString("country_status_download", "Download map\n(^ ^)");
  m_stringsBundle.SetDefaultString("country_status_download_failed", "Downloading\n^\nhas failed");
  m_stringsBundle.SetDefaultString("country_status_download_without_routing", "Download map\nwithout routing (^ ^)");
  m_stringsBundle.SetDefaultString("try_again", "Try Again");
  m_stringsBundle.SetDefaultString("not_enough_free_space_on_sdcard", "Not enough space for downloading");

  m_stringsBundle.SetDefaultString("dropped_pin", "Dropped Pin");
  m_stringsBundle.SetDefaultString("my_places", "My Places");
  m_stringsBundle.SetDefaultString("my_position", "My Position");
  m_stringsBundle.SetDefaultString("routes", "Routes");

  m_stringsBundle.SetDefaultString("routing_failed_unknown_my_position", "Current location is undefined. Please specify location to create route.");
  m_stringsBundle.SetDefaultString("routing_failed_has_no_routing_file", "Additional data is required to create the route. Download data now?");
  m_stringsBundle.SetDefaultString("routing_failed_start_point_not_found", "Cannot calculate the route. No roads near your starting point.");
  m_stringsBundle.SetDefaultString("routing_failed_dst_point_not_found", "Cannot calculate the route. No roads near your destination.");
  m_stringsBundle.SetDefaultString("routing_failed_cross_mwm_building", "Routes can only be created that are fully contained within a single map.");
  m_stringsBundle.SetDefaultString("routing_failed_route_not_found", "There is no route found between the selected origin and destination.Please select a different start or end point.");
  m_stringsBundle.SetDefaultString("routing_failed_internal_error", "Internal error occurred. Please try to delete and download the map again. If problem persist please contact us at support@maps.me.");

  m_model.InitClassificator();
  m_model.SetOnMapDeregisteredCallback(bind(&Framework::OnMapDeregistered, this, _1));
  LOG(LDEBUG, ("Classificator initialized"));

  // To avoid possible races - init country info getter once in constructor.
  InitCountryInfoGetter();
  LOG(LDEBUG, ("Country info getter initialized"));

  // To avoid possible races - init search engine once in constructor.
  InitSearchEngine();
  LOG(LDEBUG, ("Search engine initialized"));

  RegisterAllMaps();
  LOG(LDEBUG, ("Maps initialized"));

  // Init storage with needed callback.
  m_storage.Init(bind(&Framework::UpdateLatestCountryFile, this, _1));
  LOG(LDEBUG, ("Storage initialized"));

  auto const routingStatisticsFn = [](map<string, string> const & statistics)
  {
    alohalytics::LogEvent("Routing_CalculatingRoute", statistics);
  };
//#ifdef DEBUG
//  auto const routingVisualizerFn = [this](m2::PointD const & pt)
//  {
//    GetPlatform().RunOnGuiThread([this,pt]()
//    {
//      m_bmManager.UserMarksGetController(UserMarkContainer::DEBUG_MARK).CreateUserMark(pt);
//      Invalidate();
//    });
//  };
//#else
  routing::RouterDelegate::TPointCheckCallback const routingVisualizerFn = nullptr;
//#endif
  m_routingSession.Init(routingStatisticsFn, routingVisualizerFn);

  SetRouterImpl(RouterType::Vehicle);

  LOG(LDEBUG, ("Routing engine initialized"));

  LOG(LINFO, ("System languages:", languages::GetPreferred()));
}

Framework::~Framework()
{
  m_drapeEngine.reset();

  m_storageBridge.reset();
  m_activeMaps.reset();
  m_model.SetOnMapDeregisteredCallback(nullptr);
}

void Framework::DrawWatchFrame(m2::PointD const & center, int zoomModifier,
                               uint32_t pxWidth, uint32_t pxHeight,
                               df::watch::FrameSymbols const & symbols,
                               df::watch::FrameImage & image)
{
  ASSERT(IsWatchFrameRendererInited(), ());

  int resultZoom = -1;
  ScreenBase screen = m_cpuDrawer->CalculateScreen(center, zoomModifier, pxWidth, pxHeight, symbols, resultZoom);
  ASSERT_GREATER(resultZoom, 0, ());

  uint32_t const bgColor = drule::rules().GetBgColor(resultZoom);
  m_cpuDrawer->BeginFrame(pxWidth, pxHeight, dp::Extract(bgColor, 255 - (bgColor >> 24)));

  m2::RectD renderRect = m2::RectD(0, 0, pxWidth, pxHeight);
  m2::RectD selectRect;
  m2::RectD clipRect;
  double const inflationSize = 24 * m_cpuDrawer->GetVisualScale();
  screen.PtoG(m2::Inflate(renderRect, inflationSize, inflationSize), clipRect);
  screen.PtoG(renderRect, selectRect);

  uint32_t const tileSize = static_cast<uint32_t>(df::CalculateTileSize(pxWidth, pxHeight));
  int const drawScale = df::GetDrawTileScale(screen, tileSize, m_cpuDrawer->GetVisualScale());
  df::watch::FeatureProcessor doDraw(make_ref(m_cpuDrawer), clipRect, screen, drawScale);

  int const upperScale = scales::GetUpperScale();
  m_model.ForEachFeature(selectRect, doDraw, min(upperScale, drawScale));

  m_cpuDrawer->Flush();
  m_cpuDrawer->DrawMyPosition(screen.GtoP(center));

  if (symbols.m_showSearchResult)
  {
    if (!screen.PixelRect().IsPointInside(screen.GtoP(symbols.m_searchResult)))
      m_cpuDrawer->DrawSearchArrow(ang::AngleTo(center, symbols.m_searchResult));
    else
      m_cpuDrawer->DrawSearchResult(screen.GtoP(symbols.m_searchResult));
  }

  m_cpuDrawer->EndFrame(image);
}

void Framework::InitWatchFrameRenderer(float visualScale)
{
  using namespace df::watch;

  ASSERT(!IsWatchFrameRendererInited(), ());
  if (m_cpuDrawer == nullptr)
  {
    string resPostfix = df::VisualParams::GetResourcePostfix(visualScale);
    m_cpuDrawer = make_unique_dp<CPUDrawer>(CPUDrawer::Params(resPostfix, visualScale));
  }
}

void Framework::ReleaseWatchFrameRenderer()
{
  if (IsWatchFrameRendererInited())
    m_cpuDrawer.reset();
}

bool Framework::IsWatchFrameRendererInited() const
{
  return m_cpuDrawer != nullptr;
}

void Framework::DeleteCountry(storage::TIndex const & index, MapOptions opt)
{
  switch (opt)
  {
    case MapOptions::Nothing:
      return;
    case MapOptions::Map:  // fall through
    case MapOptions::MapWithCarRouting:
    {
      CountryFile const & countryFile = m_storage.GetCountryFile(index);
      // m_model will notify us when latest map file will be deleted via
      // OnMapDeregistered call.
      if (m_model.DeregisterMap(countryFile))
        InvalidateRect(GetCountryBounds(countryFile.GetNameWithoutExt()));

      // TODO (@ldragunov, @gorshenin): rewrite routing session to use MwmHandles. Thus,
      // it won' be needed to reset it after maps update.
      m_routingSession.Reset();
      return;
    }
    case MapOptions::CarRouting:
      m_routingSession.Reset();
      m_storage.DeleteCountry(index, opt);
      return;
  }
}

void Framework::DownloadCountry(TIndex const & index, MapOptions opt)
{
  m_storage.DownloadCountry(index, opt);
}

TStatus Framework::GetCountryStatus(TIndex const & index) const
{
  return m_storage.CountryStatusEx(index);
}

string Framework::GetCountryName(TIndex const & index) const
{
  string group, name;
  m_storage.GetGroupAndCountry(index, group, name);
  return (!group.empty() ? group + ", " + name : name);
}

m2::RectD Framework::GetCountryBounds(string const & file) const
{
  m2::RectD const bounds = m_infoGetter->CalcLimitRect(file);
  ASSERT(bounds.IsValid(), ());
  return bounds;
}

m2::RectD Framework::GetCountryBounds(TIndex const & index) const
{
  CountryFile const & file = m_storage.GetCountryFile(index);
  return GetCountryBounds(file.GetNameWithoutExt());
}

void Framework::ShowCountry(TIndex const & index)
{
  StopLocationFollow();

  ShowRect(GetCountryBounds(index));
}

void Framework::UpdateLatestCountryFile(LocalCountryFile const & localFile)
{
  // Soft reset to signal that mwm file may be out of date in routing caches.
  m_routingSession.Reset();

  if (!HasOptions(localFile.GetFiles(), MapOptions::Map))
    return;

  // Add downloaded map.
  auto p = m_model.RegisterMap(localFile);
  MwmSet::MwmId const & id = p.first;
  if (id.IsAlive())
    InvalidateRect(id.GetInfo()->m_limitRect);

  m_searchEngine->ClearViewportsCache();
}

void Framework::OnMapDeregistered(platform::LocalCountryFile const & localFile)
{
  m_storage.DeleteCustomCountryVersion(localFile);
}

void Framework::RegisterAllMaps()
{
  ASSERT(!m_storage.IsDownloadInProgress(),
         ("Registering maps while map downloading leads to removing downloading maps from "
          "ActiveMapsListener::m_items."));

  m_storage.RegisterAllLocalMaps();

  int minFormat = numeric_limits<int>::max();

  vector<shared_ptr<LocalCountryFile>> maps;
  m_storage.GetLocalMaps(maps);
  for (auto const & localFile : maps)
  {
    auto p = RegisterMap(*localFile);
    if (p.second != MwmSet::RegResult::Success)
      continue;

    MwmSet::MwmId const & id = p.first;
    ASSERT(id.IsAlive(), ());
    minFormat = min(minFormat, static_cast<int>(id.GetInfo()->m_version.format));
  }

  m_activeMaps->Init(maps);

  m_searchEngine->SupportOldFormat(minFormat < version::v3);
}

void Framework::DeregisterAllMaps()
{
  m_activeMaps->Clear();
  m_model.Clear();
  m_storage.Clear();
}

void Framework::LoadBookmarks()
{
  m_bmManager.LoadBookmarks();
}

size_t Framework::AddBookmark(size_t categoryIndex, const m2::PointD & ptOrg, BookmarkData & bm)
{
  return m_bmManager.AddBookmark(categoryIndex, ptOrg, bm);
}

size_t Framework::MoveBookmark(size_t bmIndex, size_t curCatIndex, size_t newCatIndex)
{
  return m_bmManager.MoveBookmark(bmIndex, curCatIndex, newCatIndex);
}

void Framework::ReplaceBookmark(size_t catIndex, size_t bmIndex, BookmarkData const & bm)
{
  m_bmManager.ReplaceBookmark(catIndex, bmIndex, bm);
}

size_t Framework::AddCategory(string const & categoryName)
{
  return m_bmManager.CreateBmCategory(categoryName);
}

namespace
{
  class EqualCategoryName
  {
    string const & m_name;
  public:
    EqualCategoryName(string const & name) : m_name(name) {}
    bool operator() (BookmarkCategory const * cat) const
    {
      return (cat->GetName() == m_name);
    }
  };
}

BookmarkCategory * Framework::GetBmCategory(size_t index) const
{
  return m_bmManager.GetBmCategory(index);
}

bool Framework::DeleteBmCategory(size_t index)
{
  return m_bmManager.DeleteBmCategory(index);
}

void Framework::ShowBookmark(BookmarkAndCategory const & bnc)
{
  StopLocationFollow();

  // show ballon above
  Bookmark const * mark = static_cast<Bookmark const *>(GetBmCategory(bnc.first)->GetUserMark(bnc.second));

  double scale = mark->GetScale();
  if (scale == -1.0)
    scale = scales::GetUpperComfortScale();

  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, mark->GetPivot(), scale, true));
  ActivateUserMark(mark, true);
}

void Framework::ShowTrack(Track const & track)
{
  StopLocationFollow();
  ShowRect(track.GetLimitRect());
}

void Framework::ClearBookmarks()
{
  m_bmManager.ClearItems();
}

namespace
{

/// @return extension with a dot in lower case
string const GetFileExt(string const & filePath)
{
  string ext = my::GetFileExtension(filePath);
  transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
  return ext;
}

string const GetFileName(string const & filePath)
{
  string ret = filePath;
  my::GetNameFromFullPath(ret);
  return ret;
}

string const GenerateValidAndUniqueFilePathForKML(string const & fileName)
{
  string filePath = BookmarkCategory::RemoveInvalidSymbols(fileName);
  filePath = BookmarkCategory::GenerateUniqueFileName(GetPlatform().SettingsDir(), filePath);
  return filePath;
}

} // namespace

bool Framework::AddBookmarksFile(string const & filePath)
{
  string const fileExt = GetFileExt(filePath);
  string fileSavePath;
  if (fileExt == BOOKMARKS_FILE_EXTENSION)
  {
    fileSavePath = GenerateValidAndUniqueFilePathForKML(GetFileName(filePath));
    if (!my::CopyFileX(filePath, fileSavePath))
      return false;
  }
  else if (fileExt == KMZ_EXTENSION)
  {
    try
    {
      ZipFileReader::FileListT files;
      ZipFileReader::FilesList(filePath, files);
      string kmlFileName;
      for (size_t i = 0; i < files.size(); ++i)
      {
        if (GetFileExt(files[i].first) == BOOKMARKS_FILE_EXTENSION)
        {
          kmlFileName = files[i].first;
          break;
        }
      }
      if (kmlFileName.empty())
        return false;

      fileSavePath = GenerateValidAndUniqueFilePathForKML(kmlFileName);
      ZipFileReader::UnzipFile(filePath, kmlFileName, fileSavePath);
    }
    catch (RootException const & e)
    {
      LOG(LWARNING, ("Error unzipping file", filePath, e.Msg()));
      return false;
    }
  }
  else
  {
    LOG(LWARNING, ("Unknown file type", filePath));
    return false;
  }

  // Update freshly added bookmarks
  m_bmManager.LoadBookmark(fileSavePath);

  return true;
}

void Framework::PrepareToShutdown()
{
  DestroyDrapeEngine();
}

void Framework::SaveState()
{
  Settings::Set("ScreenClipRect", m_currentModelView.GlobalRect());
}

void Framework::LoadState()
{
  m2::AnyRectD rect;
  if (Settings::Get("ScreenClipRect", rect) && df::GetWorldRect().IsRectInside(rect.GetGlobalRect()))
    CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewAnyRect, _1, rect, false));
  else
    ShowAll();
}

void Framework::ShowAll()
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewAnyRect, _1, m2::AnyRectD(m_model.GetWorldRect()), false));
}

m2::PointD Framework::GetPixelCenter() const
{
  return m_currentModelView.PixelRect().Center();
}

m2::PointD const & Framework::GetViewportCenter() const
{
  return m_currentModelView.GetOrg();
}

void Framework::SetViewportCenter(m2::PointD const & pt)
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, pt, -1, true));
}

m2::RectD Framework::GetCurrentViewport() const
{
  return m_currentModelView.ClipRect();
}

void Framework::ShowRect(double lat, double lon, double zoom)
{
  m2::PointD center(MercatorBounds::FromLatLon(lat, lon));
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewCenter, _1, center, zoom, true));
}

void Framework::ShowRect(m2::RectD const & rect, int maxScale)
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewRect, _1, rect, true, maxScale, true));
}

void Framework::ShowRect(m2::AnyRectD const & rect)
{
  CallDrapeFunction(bind(&df::DrapeEngine::SetModelViewAnyRect, _1, rect, true));
}

void Framework::GetTouchRect(m2::PointD const & center, uint32_t pxRadius, m2::AnyRectD & rect)
{
  m_currentModelView.GetTouchRect(center, static_cast<double>(pxRadius), rect);
}

int Framework::AddViewportListener(TViewportChanged const & fn)
{
  ASSERT(m_drapeEngine, ());
  return m_drapeEngine->AddModelViewListener(fn);
}

void Framework::RemoveViewportListener(int slotID)
{
  ASSERT(m_drapeEngine, ());
  m_drapeEngine->RemoveModeViewListener(slotID);
}

void Framework::OnSize(int w, int h)
{
  CallDrapeFunction(bind(&df::DrapeEngine::Resize, _1, max(w, 2), max(h, 2)));
}

namespace
{

double ScaleModeToFactor(Framework::EScaleMode mode)
{
  double factors[] = { 2.0, 1.5, 0.5, 0.67 };
  return factors[mode];
}

} // namespace

void Framework::Scale(EScaleMode mode, bool isAnim)
{
  Scale(ScaleModeToFactor(mode), isAnim);
}

void Framework::Scale(Framework::EScaleMode mode, m2::PointD const & pxPoint, bool isAnim)
{
  Scale(ScaleModeToFactor(mode), pxPoint, isAnim);
}

void Framework::Scale(double factor, bool isAnim)
{
  Scale(factor, m_currentModelView.PixelRect().Center(), isAnim);
}

void Framework::Scale(double factor, m2::PointD const & pxPoint, bool isAnim)
{
  CallDrapeFunction(bind(&df::DrapeEngine::Scale, _1, factor, pxPoint, isAnim));
}

void Framework::TouchEvent(df::TouchEvent const & touch)
{
  CallDrapeFunction(bind(&df::DrapeEngine::AddTouchEvent, _1, touch));
}

int Framework::GetDrawScale() const
{
  return df::GetDrawTileScale(m_currentModelView);
}

bool Framework::IsCountryLoaded(m2::PointD const & pt) const
{
  // TODO (@gorshenin, @govako): the method's name is quite
  // obfuscating and should be fixed.

  // Correct, but slow version (check country polygon).
  string const fName = m_infoGetter->GetRegionFile(pt);
  if (fName.empty())
    return true;

  return m_model.IsLoaded(fName);
}

bool Framework::IsCountryLoadedByName(string const & name) const
{
  return m_model.IsLoaded(name);
}

void Framework::InvalidateRect(m2::RectD const & rect)
{
  CallDrapeFunction(bind(&df::DrapeEngine::InvalidateRect, _1, rect));
}

void Framework::StartInteractiveSearch(search::SearchParams const & params)
{
  using namespace search;

  m_lastSearch = params;
  m_lastSearch.SetForceSearch(false);
  m_lastSearch.SetSearchMode(SearchParams::IN_VIEWPORT_ONLY | SearchParams::SEARCH_WORLD);
  m_lastSearch.m_callback = [this](Results const & results)
  {
    if (!results.IsEndMarker())
    {
      GetPlatform().RunOnGuiThread([this, results]()
      {
        if (IsISActive())
          FillSearchResultsMarks(results);
      });
    }
  };
}

void Framework::UpdateUserViewportChanged()
{
  if (IsISActive())
  {
    (void)GetCurrentPosition(m_lastSearch.m_lat, m_lastSearch.m_lon);

    m_searchEngine->Search(m_lastSearch, GetCurrentViewport());
  }
}

void Framework::ClearAllCaches()
{
  m_model.ClearCaches();
  m_infoGetter->ClearCaches();
  m_searchEngine->ClearAllCaches();
}

void Framework::SetDownloadCountryListener(TDownloadCountryListener const & listener)
{
  m_downloadCountryListener = listener;
}

void Framework::OnDownloadMapCallback(storage::TIndex const & countryIndex)
{
  if (m_downloadCountryListener != nullptr)
    m_downloadCountryListener(countryIndex, static_cast<int>(MapOptions::Map));
  else
    m_activeMaps->DownloadMap(countryIndex, MapOptions::Map);
}

void Framework::OnDownloadMapRoutingCallback(storage::TIndex const & countryIndex)
{
  if (m_downloadCountryListener != nullptr)
    m_downloadCountryListener(countryIndex, static_cast<int>(MapOptions::MapWithCarRouting));
  else
    m_activeMaps->DownloadMap(countryIndex, MapOptions::MapWithCarRouting);
}

void Framework::OnDownloadRetryCallback(storage::TIndex const & countryIndex)
{
  if (m_downloadCountryListener != nullptr)
    m_downloadCountryListener(countryIndex, -1);
  else
    m_activeMaps->RetryDownloading(countryIndex);
}

void Framework::OnUpdateCountryIndex(storage::TIndex const & currentIndex, m2::PointF const & pt)
{
  storage::TIndex newCountryIndex = GetCountryIndex(m2::PointD(pt));
  if (!newCountryIndex.IsValid())
  {
    m_drapeEngine->SetInvalidCountryInfo();
    return;
  }

  if (currentIndex != newCountryIndex)
    UpdateCountryInfo(newCountryIndex, true /* isCurrentCountry */);
}

void Framework::UpdateCountryInfo(storage::TIndex const & countryIndex, bool isCurrentCountry)
{
  ASSERT(m_activeMaps != nullptr, ());

  if (!m_drapeEngine)
    return;

  string const & fileName = m_storage.CountryByIndex(countryIndex).GetFile().GetNameWithoutExt();
  if (m_model.IsLoaded(fileName))
  {
    m_drapeEngine->SetInvalidCountryInfo();
    return;
  }

  gui::CountryInfo countryInfo;

  countryInfo.m_countryIndex = countryIndex;
  countryInfo.m_currentCountryName = m_activeMaps->GetFormatedCountryName(countryIndex);
  countryInfo.m_mapSize = m_activeMaps->GetRemoteCountrySizes(countryIndex).first;
  countryInfo.m_routingSize = m_activeMaps->GetRemoteCountrySizes(countryIndex).second;
  countryInfo.m_countryStatus = m_activeMaps->GetCountryStatus(countryIndex);
  if (countryInfo.m_countryStatus == storage::TStatus::EDownloading)
  {
    storage::LocalAndRemoteSizeT progress = m_activeMaps->GetDownloadableCountrySize(countryIndex);
    countryInfo.m_downloadProgress = progress.first * 100 / progress.second;
  }

  m_drapeEngine->SetCountryInfo(countryInfo, isCurrentCountry);
}

void Framework::MemoryWarning()
{
  LOG(LINFO, ("MemoryWarning"));
  ClearAllCaches();
  SharedBufferManager::instance().clearReserved();
}

void Framework::EnterBackground()
{
  const ms::LatLon ll = MercatorBounds::ToLatLon(GetViewportCenter());
  alohalytics::Stats::Instance().LogEvent("Framework::EnterBackground", {{"zoom", strings::to_string(GetDrawScale())},
                                          {"foregroundSeconds", strings::to_string(
                                           static_cast<int>(my::Timer::LocalTime() - m_startForegroundTime))}},
                                          alohalytics::Location::FromLatLon(ll.lat, ll.lon));
  // Do not clear caches for Android. This function is called when main activity is paused,
  // but at the same time search activity (for example) is enabled.
  // TODO(AlexZ): Use onStart/onStop on Android to correctly detect app background and remove #ifndef.
#ifndef OMIM_OS_ANDROID
  ClearAllCaches();
#endif

  if (m_drapeEngine != nullptr)
    m_drapeEngine->SetRenderingEnabled(false);
}

void Framework::EnterForeground()
{
  m_startForegroundTime = my::Timer::LocalTime();

  // Drape can be not initialized here in case of the first launch
  if (m_drapeEngine != nullptr)
    m_drapeEngine->SetRenderingEnabled(true);
}

void Framework::InitCountryInfoGetter()
{
  ASSERT(!m_infoGetter.get(), ("InitCountryInfoGetter() must be called only once."));
  Platform const & platform = GetPlatform();
  try
  {
    m_infoGetter.reset(new storage::CountryInfoGetter(platform.GetReader(PACKED_POLYGONS_FILE),
                                                      platform.GetReader(COUNTRIES_FILE)));
  }
  catch (RootException const & e)
  {
    LOG(LCRITICAL, ("Can't load needed resources for storage::CountryInfoGetter:", e.Msg()));
  }
}

void Framework::InitSearchEngine()
{
  ASSERT(!m_searchEngine.get(), ("InitSearchEngine() must be called only once."));
  ASSERT(m_infoGetter.get(), ());
  Platform const & platform = GetPlatform();

  try
  {
    m_searchEngine.reset(new search::Engine(
        const_cast<Index &>(m_model.GetIndex()), platform.GetReader(SEARCH_CATEGORIES_FILE_NAME),
        *m_infoGetter, languages::GetCurrentOrig(), make_unique<search::SearchQueryFactory>()));
  }
  catch (RootException const & e)
  {
    LOG(LCRITICAL, ("Can't load needed resources for search::Engine:", e.Msg()));
  }
}

TIndex Framework::GetCountryIndex(m2::PointD const & pt) const
{
  return m_storage.FindIndexByFile(m_infoGetter->GetRegionFile(pt));
}

string Framework::GetCountryName(m2::PointD const & pt) const
{
  storage::CountryInfo info;
  m_infoGetter->GetRegionInfo(pt, info);
  return info.m_name;
}

string Framework::GetCountryName(string const & id) const
{
  storage::CountryInfo info;
  m_infoGetter->GetRegionInfo(id, info);
  return info.m_name;
}

void Framework::PrepareSearch()
{
  m_searchEngine->PrepareSearch(GetCurrentViewport());
}

bool Framework::Search(search::SearchParams const & params)
{
#ifdef FIXED_LOCATION
  search::SearchParams rParams(params);
  if (params.IsValidPosition())
  {
    m_fixedPos.GetLat(rParams.m_lat);
    m_fixedPos.GetLon(rParams.m_lon);
  }
#else
  search::SearchParams const & rParams = params;
#endif

  return m_searchEngine->Search(rParams, GetCurrentViewport());
}

bool Framework::GetCurrentPosition(double & lat, double & lon) const
{
  m2::PointD pos;
  MyPositionMarkPoint * myPosMark = UserMarkContainer::UserMarkForMyPostion();
  if (!myPosMark->HasPosition())
    return false;

  pos = myPosMark->GetPivot();

  lat = MercatorBounds::YToLat(pos.y);
  lon = MercatorBounds::XToLon(pos.x);
  return true;
}

void Framework::LoadSearchResultMetadata(search::Result & res) const
{
  if (res.m_metadata.m_isInitialized)
    return;

  FeatureID const id = res.GetFeatureID();
  if (id.IsValid())
  {
    Index::FeaturesLoaderGuard loader(m_model.GetIndex(), id.m_mwmId);

    FeatureType ft;
    loader.GetFeatureByIndex(id.m_index, ft);

    search::ProcessMetadata(ft, res.m_metadata);
  }
  res.m_metadata.m_isInitialized = true;
}

void Framework::ShowSearchResult(search::Result const & res)
{
  CancelInteractiveSearch();

  UserMarkControllerGuard guard(m_bmManager, UserMarkType::SEARCH_MARK);
  guard.m_controller.SetIsDrawable(false);
  guard.m_controller.Clear();
  guard.m_controller.SetIsVisible(true);

  int scale;
  m2::PointD center;

  using namespace search;
  using namespace feature;

  switch (res.GetResultType())
  {
    case Result::RESULT_FEATURE:
    {
      FeatureID const id = res.GetFeatureID();
      Index::FeaturesLoaderGuard guard(m_model.GetIndex(), id.m_mwmId);

      FeatureType ft;
      guard.GetFeatureByIndex(id.m_index, ft);

      scale = GetFeatureViewportScale(TypesHolder(ft));
      center = GetCenter(ft, scale);
      break;
    }

    case Result::RESULT_LATLON:
    case Result::RESULT_ADDRESS:
      scale = scales::GetUpperComfortScale();
      center = res.GetFeatureCenter();
      break;

    default:
      return;
  }

  StopLocationFollow();
  ShowRect(df::GetRectForDrawScale(scale, center));

  search::AddressInfo info;
  info.MakeFrom(res);

  SearchMarkPoint * mark = static_cast<SearchMarkPoint *>(guard.m_controller.CreateUserMark(center));
  mark->SetInfo(info);

  ActivateUserMark(mark, false);
}

size_t Framework::ShowAllSearchResults(search::Results const & results)
{
  using namespace search;

  size_t count = results.GetCount();
  switch (count)
  {
  case 1:
    {
      Result const & r = results.GetResult(0);
      if (!r.IsSuggest())
        ShowSearchResult(r);
      else
        count = 0;
      // do not put break here
    }
  case 0:
    return count;
  }

  m_fixedSearchResults = 0;
  FillSearchResultsMarks(results);
  m_fixedSearchResults = count;

  // Setup viewport according to results.
  m2::AnyRectD viewport = m_currentModelView.GlobalRect();
  m2::PointD const center = viewport.Center();

  double minDistance = numeric_limits<double>::max();
  int minInd = -1;
  for (size_t i = 0; i < count; ++i)
  {
    Result const & r = results.GetResult(i);
    if (r.HasPoint())
    {
      double const dist = center.SquareLength(r.GetFeatureCenter());
      if (dist < minDistance)
      {
        minDistance = dist;
        minInd = static_cast<int>(i);
      }
    }
  }

  if (minInd != -1)
  {
    m2::PointD const pt = results.GetResult(minInd).GetFeatureCenter();
    if (!viewport.IsPointInside(pt))
    {
      viewport.SetSizesToIncludePoint(pt);
      CallDrapeFunction(bind(&df::DrapeEngine::StopLocationFollow, _1));
    }
  }

  // Graphics engine can be recreated (on Android), so we always set up viewport here.
  ShowRect(viewport);

  return count;
}

void Framework::FillSearchResultsMarks(search::Results const & results)
{
  UserMarkControllerGuard guard(m_bmManager, UserMarkType::SEARCH_MARK);
  guard.m_controller.SetIsVisible(true);
  guard.m_controller.SetIsDrawable(true);
  guard.m_controller.Clear(m_fixedSearchResults);

  size_t const count = results.GetCount();
  for (size_t i = 0; i < count; ++i)
  {
    using namespace search;

    Result const & r = results.GetResult(i);
    if (r.HasPoint())
    {
      AddressInfo info;
      info.MakeFrom(r);

      m2::PointD const pt = r.GetFeatureCenter();
      SearchMarkPoint * mark = static_cast<SearchMarkPoint *>(guard.m_controller.CreateUserMark(pt));
      mark->SetInfo(info);
    }
  }
}

void Framework::CancelInteractiveSearch()
{
  m_lastSearch.Clear();
  UserMarkControllerGuard(m_bmManager, UserMarkType::SEARCH_MARK).m_controller.Clear();
  m_fixedSearchResults = 0;
}

bool Framework::GetDistanceAndAzimut(m2::PointD const & point,
                                     double lat, double lon, double north,
                                     string & distance, double & azimut)
{
#ifdef FIXED_LOCATION
  m_fixedPos.GetLat(lat);
  m_fixedPos.GetLon(lon);
  m_fixedPos.GetNorth(north);
#endif

  double const d = ms::DistanceOnEarth(lat, lon,
                                       MercatorBounds::YToLat(point.y),
                                       MercatorBounds::XToLon(point.x));

  // Distance may be less than 1.0
  (void) MeasurementUtils::FormatDistance(d, distance);

  if (north >= 0.0)
  {
    // We calculate azimut even when distance is very short (d ~ 0),
    // because return value has 2 states (near me or far from me).

    azimut = ang::Azimuth(MercatorBounds::FromLatLon(lat, lon), point, north);

    double const pi2 = 2.0*math::pi;
    if (azimut < 0.0)
      azimut += pi2;
    else if (azimut > pi2)
      azimut -= pi2;
  }

  // This constant and return value is using for arrow/flag choice.
  return (d < 25000.0);
}

void Framework::CreateDrapeEngine(ref_ptr<dp::OGLContextFactory> contextFactory, DrapeCreationParams && params)
{
  using TReadIDsFn = df::MapDataProvider::TReadIDsFn;
  using TReadFeaturesFn = df::MapDataProvider::TReadFeaturesFn;
  using TUpdateCountryIndexFn = df::MapDataProvider::TUpdateCountryIndexFn;
  using TIsCountryLoadedFn = df::MapDataProvider::TIsCountryLoadedFn;
  using TDownloadFn = df::MapDataProvider::TDownloadFn;

  TReadIDsFn idReadFn = [this](df::MapDataProvider::TReadCallback<FeatureID> const & fn, m2::RectD const & r, int scale) -> void
  {
    m_model.ForEachFeatureID(r, fn, scale);
  };

  TReadFeaturesFn featureReadFn = [this](df::MapDataProvider::TReadCallback<FeatureType> const & fn, vector<FeatureID> const & ids) -> void
  {
    m_model.ReadFeatures(fn, ids);
  };

  TUpdateCountryIndexFn updateCountryIndex = [this](storage::TIndex const & currentIndex, m2::PointF const & pt)
  {
    GetPlatform().RunOnGuiThread(bind(&Framework::OnUpdateCountryIndex, this, currentIndex, pt));
  };

  TIsCountryLoadedFn isCountryLoadedFn = bind(&Framework::IsCountryLoaded, this, _1);
  auto isCountryLoadedByNameFn = bind(&Framework::IsCountryLoadedByName, this, _1);

  TDownloadFn downloadMapFn = [this](storage::TIndex const & countryIndex)
  {
    GetPlatform().RunOnGuiThread(bind(&Framework::OnDownloadMapCallback, this, countryIndex));
  };

  TDownloadFn downloadMapWithoutRoutingFn = [this](storage::TIndex const & countryIndex)
  {
    GetPlatform().RunOnGuiThread(bind(&Framework::OnDownloadMapRoutingCallback, this, countryIndex));
  };

  TDownloadFn downloadRetryFn = [this](storage::TIndex const & countryIndex)
  {
    GetPlatform().RunOnGuiThread(bind(&Framework::OnDownloadRetryCallback, this, countryIndex));
  };

  df::DrapeEngine::Params p(contextFactory,
                            make_ref(&m_stringsBundle),
                            df::Viewport(0, 0, params.m_surfaceWidth, params.m_surfaceHeight),
                            df::MapDataProvider(idReadFn, featureReadFn, updateCountryIndex,
                                                isCountryLoadedFn, isCountryLoadedByNameFn,
                                                downloadMapFn, downloadMapWithoutRoutingFn,
                                                downloadRetryFn),
                            params.m_visualScale,
                            move(params.m_widgetsInitInfo),
                            make_pair(params.m_initialMyPositionState, params.m_hasMyPositionState));

  m_drapeEngine = make_unique_dp<df::DrapeEngine>(move(p));
  AddViewportListener([this](ScreenBase const & screen)
  {
    UpdateUserViewportChanged();
    m_currentModelView = screen;
  });
  m_drapeEngine->SetTapEventInfoListener(bind(&Framework::OnTapEvent, this, _1, _2, _3, _4));
  m_drapeEngine->SetUserPositionListener(bind(&Framework::OnUserPositionChanged, this, _1));
  OnSize(params.m_surfaceWidth, params.m_surfaceHeight);

  m_drapeEngine->SetMyPositionModeListener(m_myPositionListener);
  m_drapeEngine->InvalidateMyPosition();

  InvalidateUserMarks();

  // In case of the engine reinitialization simulate the last tap to show selection mark.
  if (m_lastTapEvent != nullptr)
  {
    UserMark const * mark = OnTapEventImpl(m_lastTapEvent->m_pxPoint, m_lastTapEvent->m_isLong,
                                           m_lastTapEvent->m_isMyPosition, m_lastTapEvent->m_feature);
    if (mark != nullptr)
      ActivateUserMark(mark, true);
  }

  // In case of the engine reinitialization recover route.
  if (m_routingSession.IsActive())
    InsertRoute(m_routingSession.GetRoute());
}

ref_ptr<df::DrapeEngine> Framework::GetDrapeEngine()
{
  return make_ref(m_drapeEngine);
}

void Framework::DestroyDrapeEngine()
{
  m_drapeEngine.reset();
}

void Framework::SetMapStyle(MapStyle mapStyle)
{
  // Store current map style before classificator reloading
  Settings::Set(kMapStyleKey, static_cast<int>(mapStyle));
  GetStyleReader().SetCurrentStyle(mapStyle);

  classificator::Load();
  drule::LoadRules();
  CallDrapeFunction(bind(&df::DrapeEngine::UpdateMapStyle, _1));

  InvalidateUserMarks();

  alohalytics::TStringMap details {{"mapStyle", strings::to_string(static_cast<int>(mapStyle))}};
  alohalytics::Stats::Instance().LogEvent("MapStyle_Changed", details);
}

MapStyle Framework::GetMapStyle() const
{
  return GetStyleReader().GetCurrentStyle();
}

void Framework::SetupMeasurementSystem()
{
  GetPlatform().SetupMeasurementSystem();

  Settings::Units units = Settings::Metric;
  Settings::Get("Units", units);

  m_routingSession.SetTurnNotificationsUnits(units);
}

void Framework::SetWidgetLayout(gui::TWidgetsLayoutInfo && layout)
{
  ASSERT(m_drapeEngine != nullptr, ());
  m_drapeEngine->SetWidgetLayout(move(layout));
}

gui::TWidgetsSizeInfo const & Framework::GetWidgetSizes()
{
  ASSERT(m_drapeEngine != nullptr, ());
  return m_drapeEngine->GetWidgetSizes();
}

string Framework::GetCountryCode(m2::PointD const & pt) const
{
  storage::CountryInfo info;
  m_infoGetter->GetRegionInfo(pt, info);
  return info.m_flag;
}

bool Framework::ShowMapForURL(string const & url)
{
  m2::PointD point;
  m2::RectD rect;
  string name;
  UserMark const * apiMark = 0;

  enum ResultT { FAILED, NEED_CLICK, NO_NEED_CLICK };
  ResultT result = FAILED;

  using namespace url_scheme;
  using namespace strings;

  if (StartsWith(url, "ge0"))
  {
    Ge0Parser parser;
    double zoom;
    ApiPoint pt;

    if (parser.Parse(url, pt, zoom))
    {
      point = MercatorBounds::FromLatLon(pt.m_lat, pt.m_lon);
      rect = df::GetRectForDrawScale(zoom, point);
      name = pt.m_name;
      result = NEED_CLICK;
    }
  }
  else if (StartsWith(url, "mapswithme://") || StartsWith(url, "mwm://"))
  {
    if (m_ParsedMapApi.SetUriAndParse(url))
    {
      if (!m_ParsedMapApi.GetViewportRect(rect))
        rect = df::GetWorldRect();

      // set up controller guard to show api marks
      {
        UserMarkControllerGuard guard(m_bmManager, UserMarkType::API_MARK);
        guard.m_controller.Clear();
        guard.m_controller.SetIsVisible(true);
        guard.m_controller.SetIsDrawable(true);
      }

      if ((apiMark = m_ParsedMapApi.GetSinglePoint()))
        result = NEED_CLICK;
      else
        result = NO_NEED_CLICK;
    }
    else
    {
      UserMarkControllerGuard guard(m_bmManager, UserMarkType::API_MARK);
      guard.m_controller.Clear();
      guard.m_controller.SetIsVisible(false);
    }
  }
  else  // Actually, we can parse any geo url scheme with correct coordinates.
  {
    Info info;
    ParseGeoURL(url, info);
    if (info.IsValid())
    {
      point = MercatorBounds::FromLatLon(info.m_lat, info.m_lon);
      rect = df::GetRectForDrawScale(info.m_zoom, point);
      result = NEED_CLICK;
    }
  }

  if (result != FAILED)
  {
    // always hide current balloon here
    DeactivateUserMark();

    // set viewport and stop follow mode if any
    StopLocationFollow();
    ShowRect(rect);

    if (result != NO_NEED_CLICK)
    {
      if (apiMark)
      {
        LOG(LINFO, ("Show API mark:", static_cast<ApiMarkPoint const *>(apiMark)->GetName()));

        ActivateUserMark(apiMark, false);
      }
      else
      {
        PoiMarkPoint * mark = GetAddressMark(point);
        if (!name.empty())
          mark->SetName(name);
        ActivateUserMark(mark, false);
      }
    }

    return true;
  }

  return false;
}

bool Framework::GetVisiblePOI(m2::PointD const & glbPoint, search::AddressInfo & info, feature::Metadata & metadata) const
{
  ASSERT(m_drapeEngine != nullptr, ());
  FeatureID id = m_drapeEngine->GetVisiblePOI(glbPoint);
  if (!id.IsValid())
    return false;

  GetVisiblePOI(id, info, metadata);
  return true;
}

m2::PointD Framework::GetVisiblePOI(FeatureID const & id, search::AddressInfo & info, feature::Metadata & metadata) const
{
  ASSERT(id.IsValid(), ());
  Index::FeaturesLoaderGuard guard(m_model.GetIndex(), id.m_mwmId);

  FeatureType ft;
  guard.GetFeatureByIndex(id.m_index, ft);

  ft.ParseMetadata();
  metadata = ft.GetMetadata();

  ASSERT_NOT_EQUAL(ft.GetFeatureType(), feature::GEOM_LINE, ());
  m2::PointD const center = feature::GetCenter(ft);

  GetAddressInfo(ft, center, info);

  return GtoP(center);
}

namespace
{

/// POI - is a point or area feature.
class DoFindClosestPOI
{
  m2::PointD const & m_pt;
  double m_distMeters;
  FeatureID m_id;

public:
  DoFindClosestPOI(m2::PointD const & pt, double tresholdMeters)
    : m_pt(pt), m_distMeters(tresholdMeters)
  {
  }

  void operator() (FeatureType & ft)
  {
    if (ft.GetFeatureType() == feature::GEOM_LINE)
      return;

    double const dist = MercatorBounds::DistanceOnEarth(m_pt, feature::GetCenter(ft));
    if (dist < m_distMeters)
    {
      m_distMeters = dist;
      m_id = ft.GetID();
    }
  }

  void LoadMetadata(model::FeaturesFetcher const & model, feature::Metadata & metadata) const
  {
    if (!m_id.IsValid())
      return;

    Index::FeaturesLoaderGuard guard(model.GetIndex(), m_id.m_mwmId);

    FeatureType ft;
    guard.GetFeatureByIndex(m_id.m_index, ft);

    ft.ParseMetadata();
    metadata = ft.GetMetadata();
  }
};

}

void Framework::FindClosestPOIMetadata(m2::PointD const & pt, feature::Metadata & metadata) const
{
  m2::RectD rect(pt, pt);
  double const inf = MercatorBounds::GetCellID2PointAbsEpsilon();
  rect.Inflate(inf, inf);

  DoFindClosestPOI doFind(pt, 1.1 /* search radius in meters */);
  m_model.ForEachFeature(rect, doFind, scales::GetUpperScale() /* scale level for POI */);

  doFind.LoadMetadata(m_model, metadata);
}

BookmarkAndCategory Framework::FindBookmark(UserMark const * mark) const
{
  BookmarkAndCategory empty = MakeEmptyBookmarkAndCategory();
  BookmarkAndCategory result = empty;
  for (size_t i = 0; i < GetBmCategoriesCount(); ++i)
  {
    if (mark->GetContainer() == GetBmCategory(i))
    {
      result.first = i;
      break;
    }
  }

  ASSERT(result.first != empty.first, ());
  BookmarkCategory const * cat = GetBmCategory(result.first);
  for (size_t i = 0; i < cat->GetUserMarkCount(); ++i)
  {
    if (mark == cat->GetUserMark(i))
    {
      result.second = i;
      break;
    }
  }

  ASSERT(result != empty, ());
  return result;
}

PoiMarkPoint * Framework::GetAddressMark(m2::PointD const & globalPoint) const
{
  search::AddressInfo info;
  GetAddressInfoForGlobalPoint(globalPoint, info);
  PoiMarkPoint * mark = UserMarkContainer::UserMarkForPoi();
  mark->SetPtOrg(globalPoint);
  mark->SetInfo(info);
  return mark;
}

void Framework::ActivateUserMark(UserMark const * mark, bool needAnim)
{
  if (!m_activateUserMarkFn)
      return;

  if (mark)
  {
    m_activateUserMarkFn(mark->Copy());
    m2::PointD pt = mark->GetPivot();
    df::SelectionShape::ESelectedObject object = df::SelectionShape::OBJECT_USER_MARK;
    UserMark::Type type = mark->GetMarkType();
    if (type == UserMark::Type::MY_POSITION)
      object = df::SelectionShape::OBJECT_MY_POSITION;
    else if (type == UserMark::Type::POI)
      object = df::SelectionShape::OBJECT_POI;

    CallDrapeFunction(bind(&df::DrapeEngine::SelectObject, _1, object, pt, needAnim));
  }
  else
  {
    m_activateUserMarkFn(nullptr);
    CallDrapeFunction(bind(&df::DrapeEngine::DeselectObject, _1));
  }
}

void Framework::DeactivateUserMark()
{
  CallDrapeFunction(bind(&df::DrapeEngine::DeselectObject, _1));
}

bool Framework::HasActiveUserMark()
{
  if (m_drapeEngine == nullptr)
    return false;

  return m_drapeEngine->GetSelectedObject() != df::SelectionShape::OBJECT_EMPTY;
}

void Framework::InvalidateUserMarks()
{
  m_bmManager.InitBookmarks();

  vector<UserMarkType> const types = { UserMarkType::SEARCH_MARK, UserMarkType::API_MARK, UserMarkType::DEBUG_MARK };
  for (size_t typeIndex = 0; typeIndex < types.size(); typeIndex++)
  {
    UserMarkControllerGuard guard(m_bmManager, types[typeIndex]);
    guard.m_controller.Update();
  }
}

void Framework::OnTapEvent(m2::PointD pxPoint, bool isLong, bool isMyPosition, FeatureID const & feature)
{
  // Back up last tap event to recover selection in case of Drape reinitialization.
  if (!m_lastTapEvent)
    m_lastTapEvent = make_unique<TapEventData>();
  m_lastTapEvent->m_pxPoint = pxPoint;
  m_lastTapEvent->m_isLong = isLong;
  m_lastTapEvent->m_isMyPosition = isMyPosition;
  m_lastTapEvent->m_feature = feature;

  UserMark const * mark = OnTapEventImpl(pxPoint, isLong, isMyPosition, feature);

  {
    alohalytics::TStringMap details {{"isLongPress", isLong ? "1" : "0"}};
    if (mark)
      mark->FillLogEvent(details);
    alohalytics::Stats::Instance().LogEvent("$GetUserMark", details);
  }

  ActivateUserMark(mark, true);
}

void Framework::ResetLastTapEvent()
{
  m_lastTapEvent.reset();
}

void Framework::InvalidateRendering()
{
  if (m_drapeEngine != nullptr)
    m_drapeEngine->Invalidate();
}

UserMark const * Framework::OnTapEventImpl(m2::PointD pxPoint, bool isLong, bool isMyPosition, FeatureID const & feature)
{
  if (isMyPosition)
  {
    search::AddressInfo info;
    info.m_name = m_stringsBundle.GetString("my_position");
    MyPositionMarkPoint * myPostition = UserMarkContainer::UserMarkForMyPostion();
    myPostition->SetInfo(info);

    return myPostition;
  }

  df::VisualParams const & vp = df::VisualParams::Instance();

  m2::AnyRectD rect;
  uint32_t const touchRadius = vp.GetTouchRectRadius();
  m_currentModelView.GetTouchRect(pxPoint, touchRadius, rect);

  m2::AnyRectD bmSearchRect;
  double const bmAddition = BM_TOUCH_PIXEL_INCREASE * vp.GetVisualScale();
  double const pxWidth  =  touchRadius;
  double const pxHeight = touchRadius + bmAddition;
  m_currentModelView.GetTouchRect(pxPoint + m2::PointD(0, bmAddition),
                                  pxWidth, pxHeight, bmSearchRect);
  UserMark const * mark = m_bmManager.FindNearestUserMark(
        [&rect, &bmSearchRect](UserMarkType type) -> m2::AnyRectD const &
        {
          return (type == UserMarkType::BOOKMARK_MARK ? bmSearchRect : rect);
        });

  if (mark != nullptr)
    return mark;

  bool needMark = false;
  m2::PointD pxPivot;
  search::AddressInfo info;
  feature::Metadata metadata;

  if (feature.IsValid())
  {
    pxPivot = GetVisiblePOI(feature, info, metadata);
    needMark = true;
  }
  else if (isLong)
  {
    GetAddressInfoForPixelPoint(pxPoint, info);
    pxPivot = pxPoint;
    needMark = true;
  }

  if (needMark)
  {
    PoiMarkPoint * poiMark = UserMarkContainer::UserMarkForPoi();
    poiMark->SetPtOrg(m_currentModelView.PtoG(pxPivot));
    poiMark->SetInfo(info);
    poiMark->SetMetadata(move(metadata));
    return poiMark;
  }

  return nullptr;
}

void Framework::PredictLocation(double & lat, double & lon, double accuracy,
                                double bearing, double speed, double elapsedSeconds)
{
  double offsetInM = speed * elapsedSeconds;
  double angle = my::DegToRad(90.0 - bearing);

  m2::PointD mercatorPt = MercatorBounds::MetresToXY(lon, lat, accuracy).Center();
  mercatorPt = MercatorBounds::GetSmPoint(mercatorPt, offsetInM * cos(angle), offsetInM * sin(angle));
  lon = MercatorBounds::XToLon(mercatorPt.x);
  lat = MercatorBounds::YToLat(mercatorPt.y);
}

StringsBundle const & Framework::GetStringsBundle()
{
  return m_stringsBundle;
}

string Framework::CodeGe0url(Bookmark const * bmk, bool addName)
{
  double lat = MercatorBounds::YToLat(bmk->GetPivot().y);
  double lon = MercatorBounds::XToLon(bmk->GetPivot().x);
  return CodeGe0url(lat, lon, bmk->GetScale(), addName ? bmk->GetName() : "");
}

string Framework::CodeGe0url(double lat, double lon, double zoomLevel, string const & name)
{
  size_t const resultSize = MapsWithMe_GetMaxBufferSize(name.size());

  string res(resultSize, 0);
  int const len = MapsWithMe_GenShortShowMapUrl(lat, lon, zoomLevel, name.c_str(), &res[0], res.size());

  ASSERT_LESS_OR_EQUAL(len, res.size(), ());
  res.resize(len);

  return res;
}

string Framework::GenerateApiBackUrl(ApiMarkPoint const & point)
{
  string res = m_ParsedMapApi.GetGlobalBackUrl();
  if (!res.empty())
  {
    double lat, lon;
    point.GetLatLon(lat, lon);
    res += "pin?ll=" + strings::to_string(lat) + "," + strings::to_string(lon);
    if (!point.GetName().empty())
      res += "&n=" + UrlEncode(point.GetName());
    if (!point.GetID().empty())
      res += "&id=" + UrlEncode(point.GetID());
  }
  return res;
}

bool Framework::IsDataVersionUpdated()
{
  int64_t storedVersion;
  if (Settings::Get("DataVersion", storedVersion))
  {
    return storedVersion < m_storage.GetCurrentDataVersion();
  }
  // no key in the settings, assume new version
  return true;
}

void Framework::UpdateSavedDataVersion()
{
  Settings::Set("DataVersion", m_storage.GetCurrentDataVersion());
}

void Framework::BuildRoute(m2::PointD const & finish, uint32_t timeoutSec)
{
  ASSERT_THREAD_CHECKER(m_threadChecker, ("BuildRoute"));
  ASSERT(m_drapeEngine != nullptr, ());

  m2::PointD start;
  if (!m_drapeEngine->GetMyPosition(start))
  {
    CallRouteBuilded(IRouter::NoCurrentPosition, vector<storage::TIndex>(), vector<storage::TIndex>());
    return;
  }

  BuildRoute(start, finish, timeoutSec);
}

void Framework::BuildRoute(m2::PointD const & start, m2::PointD const & finish, uint32_t timeoutSec)
{
  ASSERT_THREAD_CHECKER(m_threadChecker, ("BuildRoute"));
  ASSERT(m_drapeEngine != nullptr, ());

  if (IsRoutingActive())
    CloseRouting();

  SetLastUsedRouter(m_currentRouterType);
  m_routingSession.SetUserCurrentPosition(start);

  auto readyCallback = [this] (Route const & route, IRouter::ResultCode code)
  {
    vector<storage::TIndex> absentCountries;
    vector<storage::TIndex> absentRoutingIndexes;
    if (code == IRouter::NoError)
    {
      double const kRouteScaleMultiplier = 1.5;

      InsertRoute(route);
      m2::RectD routeRect = route.GetPoly().GetLimitRect();
      routeRect.Scale(kRouteScaleMultiplier);
      ShowRect(routeRect, -1);
    }
    else
    {
      for (string const & name : route.GetAbsentCountries())
      {
        storage::TIndex fileIndex = m_storage.FindIndexByFile(name);
        if (m_storage.GetLatestLocalFile(fileIndex))
          absentRoutingIndexes.push_back(fileIndex);
        else
          absentCountries.push_back(fileIndex);
      }

      if (code != IRouter::NeedMoreMaps)
        RemoveRoute(true /* deactivateFollowing */);
    }
    CallRouteBuilded(code, absentCountries, absentRoutingIndexes);
  };

  m_routingSession.BuildRoute(start, finish, readyCallback, m_progressCallback, timeoutSec);
}

void Framework::FollowRoute()
{
  ASSERT(m_drapeEngine != nullptr, ());

  int const scale = (m_currentRouterType == RouterType::Pedestrian) ?
                     scales::GetUpperComfortScale() :
                     scales::GetNavigationScale();

  m_drapeEngine->FollowRoute(scale);
  m_drapeEngine->SetRoutePoint(m2::PointD(), true /* isStart */, false /* isValid */);
}

bool Framework::DisableFollowMode()
{
  bool const disabled = m_routingSession.DisableFollowMode();
  if (disabled && m_drapeEngine != nullptr)
    m_drapeEngine->DeactivateRouteFollowing();

  return disabled;
}

void Framework::SetRouter(RouterType type)
{
  ASSERT_THREAD_CHECKER(m_threadChecker, ("SetRouter"));

  if (m_currentRouterType == type)
    return;
  SetRouterImpl(type);
}

routing::RouterType Framework::GetRouter() const
{
  return m_currentRouterType;
}

void Framework::SetRouterImpl(RouterType type)
{
  unique_ptr<IRouter> router;
  unique_ptr<OnlineAbsentCountriesFetcher> fetcher;

  auto countryFileGetter = [this](m2::PointD const & p) -> string
  {
    // TODO (@gorshenin): fix CountryInfoGetter to return CountryFile
    // instances instead of plain strings.
    return m_infoGetter->GetRegionFile(p);
  };

  if (type == RouterType::Pedestrian)
  {
    router = CreatePedestrianAStarBidirectionalRouter(m_model.GetIndex(), countryFileGetter);
    m_routingSession.SetRoutingSettings(routing::GetPedestrianRoutingSettings());
  }
  else
  {
    auto localFileGetter = [this](string const & countryFile) -> shared_ptr<LocalCountryFile>
    {
      return m_storage.GetLatestLocalFile(CountryFile(countryFile));
    };

    router.reset(new OsrmRouter(&m_model.GetIndex(), countryFileGetter));
    fetcher.reset(new OnlineAbsentCountriesFetcher(countryFileGetter, localFileGetter));
    m_routingSession.SetRoutingSettings(routing::GetCarRoutingSettings());
  }

  m_routingSession.SetRouter(move(router), move(fetcher));
  m_currentRouterType = type;
}

void Framework::RemoveRoute(bool deactivateFollowing)
{
  if (m_drapeEngine != nullptr)
    m_drapeEngine->RemoveRoute(deactivateFollowing);
}

void Framework::CloseRouting()
{
  m_routingSession.Reset();
  RemoveRoute(true /* deactivateFollowing */);
}

void Framework::InsertRoute(Route const & route)
{
  if (m_drapeEngine == nullptr)
    return;

  if (route.GetPoly().GetSize() < 2)
  {
    LOG(LWARNING, ("Invalid track - only", route.GetPoly().GetSize(), "point(s)."));
    return;
  }

  vector<double> turns;
  if (m_currentRouterType == RouterType::Vehicle)
    route.GetTurnsDistances(turns);

  dp::Color routeColor;
  if (m_currentRouterType == RouterType::Pedestrian)
    routeColor = dp::Color(5, 105, 175, 204);
  else
    routeColor = dp::Color(30, 150, 240, 204);

  m_drapeEngine->AddRoute(route.GetPoly(), turns, routeColor);
}

void Framework::CheckLocationForRouting(GpsInfo const & info)
{
  if (!IsRoutingActive())
    return;

  RoutingSession::State state = m_routingSession.OnLocationPositionChanged(info, m_model.GetIndex());
  if (state == RoutingSession::RouteNeedRebuild)
  {
    auto readyCallback = [this] (Route const & route, IRouter::ResultCode code)
    {
      if (code == IRouter::NoError)
      {
        RemoveRoute(false /* deactivateFollowing */);
        InsertRoute(route);
      }
    };

    m_routingSession.RebuildRoute(MercatorBounds::FromLatLon(info.m_latitude, info.m_longitude),
                                  readyCallback, m_progressCallback, 0 /* timeoutSec */);
  }
}

void Framework::MatchLocationToRoute(location::GpsInfo & location, location::RouteMatchingInfo & routeMatchingInfo) const
{
  if (!IsRoutingActive())
    return;

  m_routingSession.MatchLocationToRoute(location, routeMatchingInfo);
}

void Framework::CallRouteBuilded(IRouter::ResultCode code, vector<storage::TIndex> const & absentCountries, vector<storage::TIndex> const & absentRoutingFiles)
{
  if (code == IRouter::Cancelled)
    return;
  m_routingCallback(code, absentCountries, absentRoutingFiles);
}

string Framework::GetRoutingErrorMessage(IRouter::ResultCode code)
{
  string messageID = "";
  switch (code)
  {
  case IRouter::NoCurrentPosition:
    messageID = "routing_failed_unknown_my_position";
    break;
  case IRouter::InconsistentMWMandRoute: // the same as RouteFileNotExist
  case IRouter::RouteFileNotExist:
    messageID = "routing_failed_has_no_routing_file";
    break;
  case IRouter::StartPointNotFound:
    messageID = "routing_failed_start_point_not_found";
    break;
  case IRouter::EndPointNotFound:
    messageID = "routing_failed_dst_point_not_found";
    break;
  case IRouter::PointsInDifferentMWM:
    messageID = "routing_failed_cross_mwm_building";
    break;
  case IRouter::RouteNotFound:
    messageID = "routing_failed_route_not_found";
    break;
  case IRouter::InternalError:
    messageID = "routing_failed_internal_error";
    break;
  default:
    ASSERT(false, ());
  }

  return m_stringsBundle.GetString(messageID);
}

RouterType Framework::GetBestRouter(m2::PointD const & startPoint, m2::PointD const & finalPoint)
{
  if (MercatorBounds::DistanceOnEarth(startPoint, finalPoint) < kKeepPedestrianDistanceMeters)
  {
    if (GetLastUsedRouter() == RouterType::Pedestrian)
      return RouterType::Pedestrian;

    // Return on a short distance the vehicle router flag only if we are already have routing files.
    auto countryFileGetter = [this](m2::PointD const & pt)
    {
      return m_infoGetter->GetRegionFile(pt);
    };
    if (!OsrmRouter::CheckRoutingAbility(startPoint, finalPoint, countryFileGetter,
                                         &m_model.GetIndex()))
    {
      return RouterType::Pedestrian;
    }
  }
  return RouterType::Vehicle;
}

RouterType Framework::GetLastUsedRouter() const
{
  string routerType;
  Settings::Get(kRouterTypeKey, routerType);
  return (routerType == routing::ToString(RouterType::Pedestrian) ? RouterType::Pedestrian : RouterType::Vehicle);
}

void Framework::SetLastUsedRouter(RouterType type)
{
  Settings::Set(kRouterTypeKey, routing::ToString(type));
}

void Framework::SetRouteStartPoint(m2::PointD const & pt, bool isValid)
{
  if (m_drapeEngine != nullptr)
    m_drapeEngine->SetRoutePoint(pt, true /* isStart */, isValid);
}

void Framework::SetRouteFinishPoint(m2::PointD const & pt, bool isValid)
{
  if (m_drapeEngine != nullptr)
    m_drapeEngine->SetRoutePoint(pt, false /* isStart */, isValid);
}

redpajama_set_name: RedPajamaGithub